| | 219 | {{{ |
| | 220 | // POST: /Account/AddTopic |
| | 221 | [HttpPost] |
| | 222 | [ValidateAntiForgeryToken] |
| | 223 | [Authorize] |
| | 224 | public ActionResult AddTopic(TopicSuggestionCreateModel model) |
| | 225 | { |
| | 226 | var currentUserId = User.Identity.GetUserId(); |
| | 227 | var mentor = db.Users.OfType<Mentor>().FirstOrDefault(m => m.Id == currentUserId); |
| | 228 | if (mentor == null) return new HttpUnauthorizedResult(); |
| | 229 | |
| | 230 | if (!ModelState.IsValid) |
| | 231 | { |
| | 232 | var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToArray(); |
| | 233 | return Json(new { success = false, errors }); |
| | 234 | } |
| | 235 | |
| | 236 | var topic = new TopicSuggestion |
| | 237 | { |
| | 238 | MentorId = currentUserId, |
| | 239 | Title = model.Title, |
| | 240 | Description = model.Description, |
| | 241 | CreatedAt = DateTime.UtcNow |
| | 242 | }; |
| | 243 | |
| | 244 | db.TopicSuggestions.Add(topic); |
| | 245 | db.SaveChanges(); |
| | 246 | |
| | 247 | return Json(new |
| | 248 | { |
| | 249 | success = true, |
| | 250 | topic = new { topic.Id, topic.Title, topic.Description, topic.IsAssigned, topic.AssignedStudentId } |
| | 251 | }); |
| | 252 | } |
| | 253 | |
| | 254 | // POST: /Account/EditTopic |
| | 255 | [HttpPost] |
| | 256 | [ValidateAntiForgeryToken] |
| | 257 | [Authorize] |
| | 258 | public ActionResult EditTopic(int id, TopicSuggestionCreateModel model) |
| | 259 | { |
| | 260 | var currentUserId = User.Identity.GetUserId(); |
| | 261 | var topic = db.TopicSuggestions.Find(id); |
| | 262 | if (topic == null) return HttpNotFound(); |
| | 263 | if (topic.MentorId != currentUserId) return new HttpUnauthorizedResult(); |
| | 264 | |
| | 265 | if (!ModelState.IsValid) |
| | 266 | { |
| | 267 | var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToArray(); |
| | 268 | return Json(new { success = false, errors }); |
| | 269 | } |
| | 270 | |
| | 271 | topic.Title = model.Title; |
| | 272 | topic.Description = model.Description; |
| | 273 | db.SaveChanges(); |
| | 274 | |
| | 275 | return Json(new { success = true }); |
| | 276 | } |
| | 277 | |
| | 278 | // POST: /Account/DeleteTopic |
| | 279 | [HttpPost] |
| | 280 | [ValidateAntiForgeryToken] |
| | 281 | [Authorize] |
| | 282 | public ActionResult DeleteTopic(int id) |
| | 283 | { |
| | 284 | var currentUserId = User.Identity.GetUserId(); |
| | 285 | var topic = db.TopicSuggestions.Find(id); |
| | 286 | if (topic == null) return HttpNotFound(); |
| | 287 | if (topic.MentorId != currentUserId) return new HttpUnauthorizedResult(); |
| | 288 | |
| | 289 | if (topic.IsAssigned) |
| | 290 | { |
| | 291 | return Json(new { success = false, message = "Cannot delete an assigned topic." }); |
| | 292 | } |
| | 293 | |
| | 294 | db.TopicSuggestions.Remove(topic); |
| | 295 | db.SaveChanges(); |
| | 296 | |
| | 297 | return Json(new { success = true }); |
| | 298 | } |
| | 299 | }}} |
| | 300 | |
| | 306 | |
| | 307 | {{{ |
| | 308 | // POST: /Account/AssignTopic |
| | 309 | [HttpPost] |
| | 310 | [ValidateAntiForgeryToken] |
| | 311 | [Authorize] |
| | 312 | public async Task<ActionResult> AssignTopic(int topicId, string studentId) |
| | 313 | { |
| | 314 | var currentUserId = User.Identity.GetUserId(); |
| | 315 | var topic = db.TopicSuggestions.Include(t => t.Mentor).FirstOrDefault(t => t.Id == topicId); |
| | 316 | if (topic == null) return HttpNotFound(); |
| | 317 | if (topic.MentorId != currentUserId) return new HttpUnauthorizedResult(); |
| | 318 | if (topic.IsAssigned) return Json(new { success = false, message = "Topic already assigned." }); |
| | 319 | |
| | 320 | var student = db.Users.OfType<Student>().FirstOrDefault(s => s.Id == studentId); |
| | 321 | if (student == null) return Json(new { success = false, message = "Student not found." }); |
| | 322 | |
| | 323 | topic.IsAssigned = true; |
| | 324 | topic.AssignedStudentId = studentId; |
| | 325 | topic.AssignedAt = DateTime.UtcNow; |
| | 326 | db.SaveChanges(); |
| | 327 | |
| | 328 | // create an on-site message to student so it shows in their notifications/inbox |
| | 329 | var msg = new Message |
| | 330 | { |
| | 331 | FromUserId = currentUserId, |
| | 332 | ToUserId = studentId, |
| | 333 | Subject = $"Ви е доделенa тема: {topic.Title}", |
| | 334 | Body = $"Ви е доделена тема од менторот {topic.Mentor.Name} {topic.Mentor.Surname}: {topic.Title}\n\n{topic.Description}", |
| | 335 | Timestamp = DateTime.UtcNow, |
| | 336 | IsRead = false |
| | 337 | }; |
| | 338 | db.Messages.Add(msg); |
| | 339 | db.SaveChanges(); |
| | 340 | |
| | 341 | // Email the student (best-effort) |
| | 342 | try |
| | 343 | { |
| | 344 | if (!string.IsNullOrWhiteSpace(student.Email)) |
| | 345 | { |
| | 346 | var subject = $"Доделена ви е тема: {topic.Title}"; |
| | 347 | var body = $@" |
| | 348 | <p>Почитуван/а {HttpUtility.HtmlEncode(student.Name)} {HttpUtility.HtmlEncode(student.Surname)},</p> |
| | 349 | <p>Менторот <strong>{HttpUtility.HtmlEncode(topic.Mentor.Name)} {HttpUtility.HtmlEncode(topic.Mentor.Surname)}</strong> ви додели тема:</p> |
| | 350 | <h4>{HttpUtility.HtmlEncode(topic.Title)}</h4> |
| | 351 | <p>{HttpUtility.HtmlEncode(topic.Description)}</p> |
| | 352 | <hr/><p>Ова е автоматска порака.</p>"; |
| | 353 | await _emailService.SendEmailAsync(student.Email, subject, body); |
| | 354 | } |
| | 355 | } |
| | 356 | catch (Exception ex) |
| | 357 | { |
| | 358 | System.Diagnostics.Debug.WriteLine("Email send failed (AssignTopic): " + ex); |
| | 359 | } |
| | 360 | |
| | 361 | return Json(new { success = true }); |
| | 362 | } |
| | 363 | |
| | 364 | // POST: /Account/UnassignTopic |
| | 365 | [HttpPost] |
| | 366 | [ValidateAntiForgeryToken] |
| | 367 | [Authorize] |
| | 368 | public async Task<ActionResult> UnassignTopic(int topicId) |
| | 369 | { |
| | 370 | var currentUserId = User.Identity.GetUserId(); |
| | 371 | var topic = db.TopicSuggestions.Include(t => t.Mentor).Include(t => t.AssignedStudent).FirstOrDefault(t => t.Id == topicId); |
| | 372 | if (topic == null) return HttpNotFound(); |
| | 373 | if (topic.MentorId != currentUserId) return new HttpUnauthorizedResult(); |
| | 374 | if (!topic.IsAssigned) return Json(new { success = false, message = "Topic not assigned." }); |
| | 375 | |
| | 376 | var student = topic.AssignedStudent; |
| | 377 | topic.IsAssigned = false; |
| | 378 | topic.AssignedStudentId = null; |
| | 379 | topic.AssignedAt = null; |
| | 380 | db.SaveChanges(); |
| | 381 | |
| | 382 | // notify student with message |
| | 383 | if (student != null) |
| | 384 | { |
| | 385 | var msg = new Message |
| | 386 | { |
| | 387 | FromUserId = currentUserId, |
| | 388 | ToUserId = student.Id, |
| | 389 | Subject = $"Тема {topic.Title} е оневозможена", |
| | 390 | Body = $"Менторот {topic.Mentor.Name} {topic.Mentor.Surname} ја оневозможи или одзеде темата {topic.Title}.", |
| | 391 | Timestamp = DateTime.UtcNow, |
| | 392 | IsRead = false |
| | 393 | }; |
| | 394 | db.Messages.Add(msg); |
| | 395 | db.SaveChanges(); |
| | 396 | |
| | 397 | try |
| | 398 | { |
| | 399 | if (!string.IsNullOrWhiteSpace(student.Email)) |
| | 400 | { |
| | 401 | var subject = $"Тема {topic.Title} е одземена/оневозможена"; |
| | 402 | var body = $@" |
| | 403 | <p>Почитуван/а {HttpUtility.HtmlEncode(student.Name)} {HttpUtility.HtmlEncode(student.Surname)},</p> |
| | 404 | <p>Менторот <strong>{HttpUtility.HtmlEncode(topic.Mentor.Name)} {HttpUtility.HtmlEncode(topic.Mentor.Surname)}</strong> ја одзеде темата <strong>{HttpUtility.HtmlEncode(topic.Title)}</strong>.</p> |
| | 405 | <hr/><p>Ова е автоматска порака.</p>"; |
| | 406 | await _emailService.SendEmailAsync(student.Email, subject, body); |
| | 407 | } |
| | 408 | } |
| | 409 | catch (Exception ex) |
| | 410 | { |
| | 411 | System.Diagnostics.Debug.WriteLine("Email send failed (UnassignTopic): " + ex); |
| | 412 | } |
| | 413 | } |
| | 414 | |
| | 415 | return Json(new { success = true }); |
| | 416 | } |
| | 417 | }}} |
| | 424 | {{{ |
| | 425 | // GET: /Account/GetMentorTopics (returns JSON list of available topics for a mentor) |
| | 426 | [Authorize] |
| | 427 | public JsonResult GetMentorTopics(string mentorId) |
| | 428 | { |
| | 429 | var topics = db.TopicSuggestions |
| | 430 | .Where(t => t.MentorId == mentorId && !t.IsAssigned) |
| | 431 | .OrderByDescending(t => t.CreatedAt) |
| | 432 | .Select(t => new { t.Id, t.Title, t.Description }) |
| | 433 | .ToList(); |
| | 434 | |
| | 435 | return Json(new { success = true, topics }, JsonRequestBehavior.AllowGet); |
| | 436 | } |
| | 437 | |
| | 438 | [Authorize] |
| | 439 | public JsonResult GetTopicCandidates(int topicId) |
| | 440 | { |
| | 441 | var topic = db.TopicSuggestions.Find(topicId); |
| | 442 | if (topic == null) return Json(new { success = false, message = "Topic not found" }, JsonRequestBehavior.AllowGet); |
| | 443 | |
| | 444 | // candidates: students who contacted this mentor and optionally selected this topic, fallback to all students who contacted |
| | 445 | var candidates = db.MentorContacts |
| | 446 | .Where(c => c.MentorId == topic.MentorId) |
| | 447 | .Include(c => c.Student) |
| | 448 | .GroupBy(c => c.StudentId) |
| | 449 | .Select(g => new |
| | 450 | { |
| | 451 | id = g.Key, |
| | 452 | name = g.Select(x => x.Student.Name + " " + x.Student.Surname).FirstOrDefault() |
| | 453 | }) |
| | 454 | .Where(x => x.id != null) |
| | 455 | .ToList(); |
| | 456 | |
| | 457 | // ensure distinct and non-null |
| | 458 | candidates = candidates.GroupBy(x => x.id).Select(g => g.First()).ToList(); |
| | 459 | |
| | 460 | return Json(new { success = true, candidates }, JsonRequestBehavior.AllowGet); |
| | 461 | } |
| | 462 | }}} |
| | 463 | |