| Version 9 (modified by , 3 days ago) ( diff ) |
|---|
Финална имплементација на случаи на употреба
На следната табела се прикажани финалните кориснички сценарија:
| ID | Use Case Scenario | Actor |
| 14 | Најава преку OAuth2 (Google, Microsoft, GitHub) | Корисник |
| 15 | Поставување на предлог теми за проект | Ментор |
| 16 | Доделување теми на студент | Ментор |
| 17 | Контакт со ментор (променет) | Студент |
И дополнително се имплементирани E-mail нотификации, поврзани со inbox на самиот сајт.
Корисник
Најава преку OAuth2
Кога прв пат ќе ја отвори страната, корисникот е редиректиран кон страната за најава. Долу, има дополнителни три копчиња за најава со Google, Microsoft и GitHub.
Да го земеме како пример Google. Корисникот ја одбира опцијата за најава со Google и е редиректиран кон најавата поставена од Google:
Откако ќе го одбере својот Google профил и успешно помине најавата, тогаш корисникот може да продолжи со регистрација. Тука се пополуваат полињата како име, презиме и мејл:
Сега што останува е корисникот да пополни биографија, предмети, интереси, доколку е студент тогаш индекс, година, семестар, итн. Но тие се опционални, и регистрацијата преку Google е завршена.
Сега, секогаш кога корисникот сака да се најави на Најди Ментор, може преку својот Google профил.
var googleId = System.Configuration.ConfigurationManager.AppSettings["GoogleClientId"];
var googleSecret = System.Configuration.ConfigurationManager.AppSettings["GoogleClientSecret"];
if (!string.IsNullOrWhiteSpace(googleId) && !string.IsNullOrWhiteSpace(googleSecret))
{
app.UseGoogleAuthentication(new GoogleOAuth2AuthenticationOptions
{
ClientId = googleId,
ClientSecret = googleSecret,
CallbackPath = new PathString("/signin-google"),
CookieManager = cookieManager
});
}
// POST: /Account/ExternalLogin
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public ActionResult ExternalLogin(string provider, string returnUrl)
{
// Request a redirect to the external login provider
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl });
return new ChallengeResult(provider, redirectUrl);
}
// POST: /Account/ExternalLoginConfirmation
[HttpPost]
[AllowAnonymous]
[ValidateAntiForgeryToken]
public async Task<ActionResult> ExternalLoginConfirmation(WebApplication1.Models.ExternalLoginConfirmationViewModel model, string returnUrl)
{
if (User.Identity.IsAuthenticated)
{
return RedirectToAction("Index", "Manage");
}
if (!ModelState.IsValid)
{
return View(model);
}
// Obtain external login info again
var info = await AuthenticationManager.GetExternalLoginInfoAsync();
if (info == null)
{
ModelState.AddModelError("", "Не можам да ги вчитам информации за надворешната најава.");
return View(model);
}
ApplicationUser user;
if (model.UserType == "Student")
{
user = new Student
{
UserName = model.Email,
Email = model.Email,
Name = model.Name,
Surname = model.Surname,
Biography = model.Biography,
Index = model.Index,
Major = model.Major,
Cycle = model.Cycle,
Semester = model.Semester
};
}
else // Mentor
{
user = new Mentor
{
UserName = model.Email,
Email = model.Email,
Name = model.Name,
Surname = model.Surname,
Biography = model.Biography,
Timeslots = model.Timeslots,
TypesOfProject = model.TypesOfProject,
Available = model.Available,
ImageURL = null
};
}
// Create user
var result = await UserManager.CreateAsync(user);
if (!result.Succeeded)
{
AddErrors(result);
return View(model);
}
// Add the external login
result = await UserManager.AddLoginAsync(user.Id, info.Login);
if (!result.Succeeded)
{
AddErrors(result);
return View(model);
}
if (model.UserType == "Student")
{
var student = db.Users
.OfType<Student>()
.Include(s => s.Subjects)
.Include(s => s.Topics)
.SingleOrDefault(u => u.Id == user.Id);
if (student != null)
{
// clear any placeholder (should be empty for a brand new user) and add incoming items
student.Subjects.Clear();
student.Topics.Clear();
foreach (var subjName in model.Subjects ?? Enumerable.Empty<string>())
student.Subjects.Add(new Subject { Name = subjName, UserId = student.Id });
foreach (var topicName in model.Topics ?? Enumerable.Empty<string>())
student.Topics.Add(new Topic { Name = topicName, UserId = student.Id });
db.SaveChanges();
}
}
else // Mentor
{
var mentor = db.Users
.OfType<Mentor>()
.Include(m => m.Subjects)
.Include(m => m.Topics)
.SingleOrDefault(u => u.Id == user.Id);
if (mentor != null)
{
mentor.Subjects.Clear();
mentor.Topics.Clear();
foreach (var subjName in model.Subjects ?? Enumerable.Empty<string>())
mentor.Subjects.Add(new Subject { Name = subjName, UserId = mentor.Id });
foreach (var topicName in model.Topics ?? Enumerable.Empty<string>())
mentor.Topics.Add(new Topic { Name = topicName, UserId = mentor.Id });
db.SaveChanges();
}
}
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LinkLogin(string provider)
{
// Request redirect to external login provider to link to current user
return new ChallengeResult(provider, Url.Action("LinkLoginCallback", "Account"), User.Identity.GetUserId());
}
[AllowAnonymous]
public async Task<ActionResult> LinkLoginCallback()
{
var loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync(XsrfKey, User.Identity.GetUserId());
if (loginInfo == null)
{
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
}
var result = await UserManager.AddLoginAsync(User.Identity.GetUserId(), loginInfo.Login);
if (result.Succeeded)
{
return RedirectToAction("Manage", new { Message = ManageMessageId.AddLoginSuccess });
}
return RedirectToAction("Manage", new { Message = ManageMessageId.Error });
}
Ментор
Поставување на предлог теми за проект
Менторот може да постави предлог теми за проект на својот профил. Најпрво треба да го отвори прозорецот за уредување на својот профил и да го најде следното поле:
Откако ќе додаде тема, ќе може да ја уреди, избрише или додели на некој студент, притоа студентот мора да стапил во контакт со менторот претходно на самата страна и да има договор помеѓу студентот и менторот за таа тема. Доколку темата е доделена, менторот може да ја одземе.
// POST: /Account/AddTopic
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult AddTopic(TopicSuggestionCreateModel model)
{
var currentUserId = User.Identity.GetUserId();
var mentor = db.Users.OfType<Mentor>().FirstOrDefault(m => m.Id == currentUserId);
if (mentor == null) return new HttpUnauthorizedResult();
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToArray();
return Json(new { success = false, errors });
}
var topic = new TopicSuggestion
{
MentorId = currentUserId,
Title = model.Title,
Description = model.Description,
CreatedAt = DateTime.UtcNow
};
db.TopicSuggestions.Add(topic);
db.SaveChanges();
return Json(new
{
success = true,
topic = new { topic.Id, topic.Title, topic.Description, topic.IsAssigned, topic.AssignedStudentId }
});
}
// POST: /Account/EditTopic
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult EditTopic(int id, TopicSuggestionCreateModel model)
{
var currentUserId = User.Identity.GetUserId();
var topic = db.TopicSuggestions.Find(id);
if (topic == null) return HttpNotFound();
if (topic.MentorId != currentUserId) return new HttpUnauthorizedResult();
if (!ModelState.IsValid)
{
var errors = ModelState.Values.SelectMany(v => v.Errors).Select(e => e.ErrorMessage).ToArray();
return Json(new { success = false, errors });
}
topic.Title = model.Title;
topic.Description = model.Description;
db.SaveChanges();
return Json(new { success = true });
}
// POST: /Account/DeleteTopic
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public ActionResult DeleteTopic(int id)
{
var currentUserId = User.Identity.GetUserId();
var topic = db.TopicSuggestions.Find(id);
if (topic == null) return HttpNotFound();
if (topic.MentorId != currentUserId) return new HttpUnauthorizedResult();
if (topic.IsAssigned)
{
return Json(new { success = false, message = "Cannot delete an assigned topic." });
}
db.TopicSuggestions.Remove(topic);
db.SaveChanges();
return Json(new { success = true });
}
Доделување теми на студент
Менторот може да додели тема на студент преку dropdown кое е пополнето со сите студенти кои го контактирале менторот:
// POST: /Account/AssignTopic
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public async Task<ActionResult> AssignTopic(int topicId, string studentId)
{
var currentUserId = User.Identity.GetUserId();
var topic = db.TopicSuggestions.Include(t => t.Mentor).FirstOrDefault(t => t.Id == topicId);
if (topic == null) return HttpNotFound();
if (topic.MentorId != currentUserId) return new HttpUnauthorizedResult();
if (topic.IsAssigned) return Json(new { success = false, message = "Topic already assigned." });
var student = db.Users.OfType<Student>().FirstOrDefault(s => s.Id == studentId);
if (student == null) return Json(new { success = false, message = "Student not found." });
topic.IsAssigned = true;
topic.AssignedStudentId = studentId;
topic.AssignedAt = DateTime.UtcNow;
db.SaveChanges();
// create an on-site message to student so it shows in their notifications/inbox
var msg = new Message
{
FromUserId = currentUserId,
ToUserId = studentId,
Subject = $"Ви е доделенa тема: {topic.Title}",
Body = $"Ви е доделена тема од менторот {topic.Mentor.Name} {topic.Mentor.Surname}: {topic.Title}\n\n{topic.Description}",
Timestamp = DateTime.UtcNow,
IsRead = false
};
db.Messages.Add(msg);
db.SaveChanges();
// Email the student (best-effort)
try
{
if (!string.IsNullOrWhiteSpace(student.Email))
{
var subject = $"Доделена ви е тема: {topic.Title}";
var body = $@"
<p>Почитуван/а {HttpUtility.HtmlEncode(student.Name)} {HttpUtility.HtmlEncode(student.Surname)},</p>
<p>Менторот <strong>{HttpUtility.HtmlEncode(topic.Mentor.Name)} {HttpUtility.HtmlEncode(topic.Mentor.Surname)}</strong> ви додели тема:</p>
<h4>{HttpUtility.HtmlEncode(topic.Title)}</h4>
<p>{HttpUtility.HtmlEncode(topic.Description)}</p>
<hr/><p>Ова е автоматска порака.</p>";
await _emailService.SendEmailAsync(student.Email, subject, body);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Email send failed (AssignTopic): " + ex);
}
return Json(new { success = true });
}
// POST: /Account/UnassignTopic
[HttpPost]
[ValidateAntiForgeryToken]
[Authorize]
public async Task<ActionResult> UnassignTopic(int topicId)
{
var currentUserId = User.Identity.GetUserId();
var topic = db.TopicSuggestions.Include(t => t.Mentor).Include(t => t.AssignedStudent).FirstOrDefault(t => t.Id == topicId);
if (topic == null) return HttpNotFound();
if (topic.MentorId != currentUserId) return new HttpUnauthorizedResult();
if (!topic.IsAssigned) return Json(new { success = false, message = "Topic not assigned." });
var student = topic.AssignedStudent;
topic.IsAssigned = false;
topic.AssignedStudentId = null;
topic.AssignedAt = null;
db.SaveChanges();
// notify student with message
if (student != null)
{
var msg = new Message
{
FromUserId = currentUserId,
ToUserId = student.Id,
Subject = $"Тема {topic.Title} е оневозможена",
Body = $"Менторот {topic.Mentor.Name} {topic.Mentor.Surname} ја оневозможи или одзеде темата {topic.Title}.",
Timestamp = DateTime.UtcNow,
IsRead = false
};
db.Messages.Add(msg);
db.SaveChanges();
try
{
if (!string.IsNullOrWhiteSpace(student.Email))
{
var subject = $"Тема {topic.Title} е одземена/оневозможена";
var body = $@"
<p>Почитуван/а {HttpUtility.HtmlEncode(student.Name)} {HttpUtility.HtmlEncode(student.Surname)},</p>
<p>Менторот <strong>{HttpUtility.HtmlEncode(topic.Mentor.Name)} {HttpUtility.HtmlEncode(topic.Mentor.Surname)}</strong> ја одзеде темата <strong>{HttpUtility.HtmlEncode(topic.Title)}</strong>.</p>
<hr/><p>Ова е автоматска порака.</p>";
await _emailService.SendEmailAsync(student.Email, subject, body);
}
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine("Email send failed (UnassignTopic): " + ex);
}
}
return Json(new { success = true });
}
Секој корисник може да ги види предлог темите на менторот на неговиот профил. Може да види наслов, опис, доколку се доделени или не. Ако е менторот, или пак студентот на кој е доделена темата, тогаш има посебно поле „Доделена на студент:“
// GET: /Account/GetMentorTopics (returns JSON list of available topics for a mentor)
[Authorize]
public JsonResult GetMentorTopics(string mentorId)
{
var topics = db.TopicSuggestions
.Where(t => t.MentorId == mentorId && !t.IsAssigned)
.OrderByDescending(t => t.CreatedAt)
.Select(t => new { t.Id, t.Title, t.Description })
.ToList();
return Json(new { success = true, topics }, JsonRequestBehavior.AllowGet);
}
[Authorize]
public JsonResult GetTopicCandidates(int topicId)
{
var topic = db.TopicSuggestions.Find(topicId);
if (topic == null) return Json(new { success = false, message = "Topic not found" }, JsonRequestBehavior.AllowGet);
// candidates: students who contacted this mentor and optionally selected this topic, fallback to all students who contacted
var candidates = db.MentorContacts
.Where(c => c.MentorId == topic.MentorId)
.Include(c => c.Student)
.GroupBy(c => c.StudentId)
.Select(g => new
{
id = g.Key,
name = g.Select(x => x.Student.Name + " " + x.Student.Surname).FirstOrDefault()
})
.Where(x => x.id != null)
.ToList();
// ensure distinct and non-null
candidates = candidates.GroupBy(x => x.id).Select(g => g.First()).ToList();
return Json(new { success = true, candidates }, JsonRequestBehavior.AllowGet);
}
Студент
Контакт со ментор (променет)
Контактирање на ментор од страна на студент останува исто, само е додадено поле за избор на предлог тема, која студентот може да ја избере но е опционална.
Студентот стиска на копчето за контакт кај профилот на менторот и се отвара следниот прозорец:
По испраќање на контакт барање на менторот, менторот добива нотификација како и претходно, само што сега е излистана и темата која ја одбрал студентот. Ова е со цел да знае професорот на кого да ја додели темата понатаму, но тоа мора да го направи преку својот профил, во случај да има повеќе заинтересирани студенти.
Нотификации преку E-mail
Воведени се нотификација преку E-mail. освен што стигаат во постоечкиот Inbox, сега сите нотификации стигаат и преку E-mail на корисникот.
Пример:
Attachments (11)
- најава.png (31.4 KB ) - added by 3 days ago.
- најава2.png (26.6 KB ) - added by 3 days ago.
- најава3.png (30.8 KB ) - added by 3 days ago.
- теми.png (53.5 KB ) - added by 3 days ago.
- доделитема.png (15.2 KB ) - added by 3 days ago.
- темипрофил.png (34.3 KB ) - added by 3 days ago.
- темипрофил2.png (46.1 KB ) - added by 3 days ago.
- нотиф.png (52.5 KB ) - added by 3 days ago.
- нотиф2.png (53.3 KB ) - added by 3 days ago.
- контакт.png (32.5 KB ) - added by 3 days ago.
- контактнотиф.png (38.9 KB ) - added by 3 days ago.
Download all attachments as: .zip











