wiki:UseCaseImplementationsFinal

Version 9 (modified by 231067, 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)

Download all attachments as: .zip

Note: See TracWiki for help on using the wiki.