From edce26972fd2795f1b7f7ccb273238a867bb731a Mon Sep 17 00:00:00 2001 From: Francis Pion Date: Mon, 29 Apr 2024 16:36:58 -0400 Subject: [PATCH] Implemented user profile. (#4) * Implemented user profile. * Removed comments. --- .../Accounts/UserExtensions.cs | 21 ++++- .../Logitar.Master.Application.csproj | 1 + .../Accounts/AccountPhone.cs | 25 +++++ .../Accounts/UserProfile.cs | 43 +++++++++ .../Controllers/AccountController.cs | 10 ++ .../Accounts/UserExtensionsTests.cs | 94 +++++++++---------- 6 files changed, 142 insertions(+), 52 deletions(-) create mode 100644 backend/src/Logitar.Master.Contracts/Accounts/AccountPhone.cs create mode 100644 backend/src/Logitar.Master.Contracts/Accounts/UserProfile.cs diff --git a/backend/src/Logitar.Master.Application/Accounts/UserExtensions.cs b/backend/src/Logitar.Master.Application/Accounts/UserExtensions.cs index 32bac4d..0552d8f 100644 --- a/backend/src/Logitar.Master.Application/Accounts/UserExtensions.cs +++ b/backend/src/Logitar.Master.Application/Accounts/UserExtensions.cs @@ -1,7 +1,6 @@ using Logitar.Master.Contracts.Accounts; using Logitar.Portal.Contracts; using Logitar.Portal.Contracts.Users; -using System.Globalization; namespace Logitar.Master.Application.Accounts; @@ -36,4 +35,24 @@ public static bool IsProfileCompleted(this User user) CustomAttribute? customAttribute = user.CustomAttributes.SingleOrDefault(x => x.Key == ProfileCompletedOnKey); return customAttribute == null ? null : DateTime.Parse(customAttribute.Value); } + + public static UserProfile ToUserProfile(this User user) => new() + { + CreatedOn = user.CreatedOn, + CompletedOn = user.GetProfileCompleted() ?? default, + UpdatedOn = user.UpdatedOn, + PasswordChangedOn = user.PasswordChangedOn, + AuthenticatedOn = user.AuthenticatedOn, + MultiFactorAuthenticationMode = user.GetMultiFactorAuthenticationMode() ?? MultiFactorAuthenticationMode.None, + EmailAddress = user.Email?.Address ?? user.UniqueName, + Phone = AccountPhone.TryCreate(user.Phone), + FirstName = user.FirstName ?? string.Empty, + MiddleName = user.MiddleName, + LastName = user.LastName ?? string.Empty, + FullName = user.FullName ?? string.Empty, + Birthdate = user.Birthdate, + Gender = user.Gender, + Locale = user.Locale ?? new Locale(), + TimeZone = user.TimeZone ?? string.Empty + }; } diff --git a/backend/src/Logitar.Master.Application/Logitar.Master.Application.csproj b/backend/src/Logitar.Master.Application/Logitar.Master.Application.csproj index e635421..5868bc0 100644 --- a/backend/src/Logitar.Master.Application/Logitar.Master.Application.csproj +++ b/backend/src/Logitar.Master.Application/Logitar.Master.Application.csproj @@ -23,6 +23,7 @@ + diff --git a/backend/src/Logitar.Master.Contracts/Accounts/AccountPhone.cs b/backend/src/Logitar.Master.Contracts/Accounts/AccountPhone.cs new file mode 100644 index 0000000..75f043c --- /dev/null +++ b/backend/src/Logitar.Master.Contracts/Accounts/AccountPhone.cs @@ -0,0 +1,25 @@ +using Logitar.Identity.Contracts.Users; + +namespace Logitar.Master.Contracts.Accounts; + +public record AccountPhone +{ + public string? CountryCode { get; set; } + public string Number { get; set; } + + public AccountPhone() : this(string.Empty) + { + } + + public AccountPhone(IPhone phone) : this(phone.Number, phone.CountryCode) + { + } + + public AccountPhone(string number, string? countryCode = null) + { + Number = number; + CountryCode = countryCode; + } + + public static AccountPhone? TryCreate(IPhone? phone) => phone == null ? null : new(phone); +} diff --git a/backend/src/Logitar.Master.Contracts/Accounts/UserProfile.cs b/backend/src/Logitar.Master.Contracts/Accounts/UserProfile.cs new file mode 100644 index 0000000..096ca21 --- /dev/null +++ b/backend/src/Logitar.Master.Contracts/Accounts/UserProfile.cs @@ -0,0 +1,43 @@ +using Logitar.Portal.Contracts; + +namespace Logitar.Master.Contracts.Accounts; + +public record UserProfile +{ + public DateTime CreatedOn { get; set; } + public DateTime CompletedOn { get; set; } + public DateTime UpdatedOn { get; set; } + + public DateTime? PasswordChangedOn { get; set; } + public DateTime? AuthenticatedOn { get; set; } + public MultiFactorAuthenticationMode MultiFactorAuthenticationMode { get; set; } + + public string EmailAddress { get; set; } + public AccountPhone? Phone { get; set; } + + public string FirstName { get; set; } + public string? MiddleName { get; set; } + public string LastName { get; set; } + public string FullName { get; set; } + + public DateTime? Birthdate { get; set; } + public string? Gender { get; set; } + public Locale Locale { get; set; } + public string TimeZone { get; set; } + + public UserProfile() : this(string.Empty, string.Empty, string.Empty, string.Empty, new Locale(), string.Empty) + { + } + + public UserProfile(string emailAddress, string firstName, string lastName, string fullName, Locale locale, string timeZone) + { + EmailAddress = emailAddress; + + FirstName = firstName; + LastName = lastName; + FullName = fullName; + + Locale = locale; + TimeZone = timeZone; + } +} diff --git a/backend/src/Logitar.Master/Controllers/AccountController.cs b/backend/src/Logitar.Master/Controllers/AccountController.cs index ee20bbe..7570144 100644 --- a/backend/src/Logitar.Master/Controllers/AccountController.cs +++ b/backend/src/Logitar.Master/Controllers/AccountController.cs @@ -6,6 +6,8 @@ using Logitar.Master.Extensions; using Logitar.Master.Models.Account; using Logitar.Portal.Contracts.Sessions; +using Logitar.Portal.Contracts.Users; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace Logitar.Master.Controllers; @@ -62,4 +64,12 @@ public async Task> TokenAsync([FromBody] GetToken return Ok(response); } + + [HttpGet("/profile")] + [Authorize] + public ActionResult GetProfile() + { + User user = HttpContext.GetUser() ?? throw new InvalidOperationException("An authenticated user is required."); + return Ok(user.ToUserProfile()); + } } diff --git a/backend/tests/Logitar.Master.Application.UnitTests/Accounts/UserExtensionsTests.cs b/backend/tests/Logitar.Master.Application.UnitTests/Accounts/UserExtensionsTests.cs index 2d6d268..734bfe1 100644 --- a/backend/tests/Logitar.Master.Application.UnitTests/Accounts/UserExtensionsTests.cs +++ b/backend/tests/Logitar.Master.Application.UnitTests/Accounts/UserExtensionsTests.cs @@ -1,5 +1,6 @@ using Bogus; using Logitar.Master.Contracts.Accounts; +using Logitar.Portal.Contracts; using Logitar.Portal.Contracts.Users; using Microsoft.VisualStudio.TestPlatform.ObjectModel; @@ -103,55 +104,46 @@ public void SetMultiFactorAuthenticationMode_it_should_the_correct_custom_attrib // Assert.True(payload.IsVerified); //} - //[Theory(DisplayName = "ToUserProfile: it should return the correct user profile.")] - //[InlineData(MultiFactorAuthenticationMode.Email, null)] - //[InlineData(MultiFactorAuthenticationMode.Phone, "+15148454636")] - //public void ToUserProfile_it_should_return_the_correct_user_profile(MultiFactorAuthenticationMode mfaMode, string? phone) - //{ - // User user = new(_faker.Person.UserName) - // { - // CreatedOn = DateTime.UtcNow.AddDays(-1), - // PasswordChangedOn = DateTime.UtcNow.AddHours(-1), - // Email = new Email(_faker.Person.Email), - // FirstName = _faker.Person.FirstName, - // LastName = _faker.Person.LastName, - // Birthdate = DateTime.UtcNow.AddYears(-20), - // Gender = _faker.Person.Gender.ToString(), - // Locale = new Logitar.Portal.Contracts.Locale("fr-CA"), - // TimeZone = "America/Montreal" - // }; - // user.CustomAttributes.Add(new(nameof(MultiFactorAuthenticationMode), mfaMode.ToString())); - - // DateTime profileCompletedOn = DateTime.UtcNow; - // user.CustomAttributes.Add(new("ProfileCompletedOn", profileCompletedOn.ToString("O", CultureInfo.InvariantCulture))); - - // if (phone != null) - // { - // user.Phone = new Logitar.Portal.Contracts.Users.Phone("CA", phone, extension: null, phone); - // } - - // UserProfile profile = user.ToUserProfile(); - // Assert.Equal(user.CreatedOn, profile.RegisteredOn); - // Assert.Equal(profileCompletedOn, profile.CompletedOn); - // Assert.Equal(user.Email.Address, profile.EmailAddress); - // Assert.Equal(user.PasswordChangedOn, profile.PasswordChangedOn); - // Assert.Equal(mfaMode, profile.MultiFactorAuthenticationMode); - // Assert.Equal(user.FirstName, profile.FirstName); - // Assert.Equal(user.LastName, profile.LastName); - // Assert.Equal(user.Birthdate, profile.Birthdate); - // Assert.Equal(user.Gender, profile.Gender); - // Assert.Equal(user.Locale, profile.Locale); - // Assert.Equal(user.TimeZone, profile.TimeZone); - - // if (phone == null) - // { - // Assert.Null(user.Phone); - // } - // else - // { - // Assert.NotNull(profile.Phone); - // Assert.Equal("CA", profile.Phone.CountryCode); - // Assert.Equal(phone, profile.Phone.Number); - // } - //} + [Fact(DisplayName = "ToUserProfile: it should return the correct user profile.")] + public void ToUserProfile_it_should_return_the_correct_user_profile() + { + DateTime completedOn = DateTime.Now.AddHours(-6); + User user = new(_faker.Person.UserName) + { + CreatedOn = DateTime.Now.AddDays(-1), + UpdatedOn = DateTime.Now, + PasswordChangedOn = DateTime.Now.AddMinutes(-10), + AuthenticatedOn = DateTime.Now.AddHours(-1), + Email = new Email(_faker.Person.Email), + Phone = new Phone(countryCode: "CA", "(514) 845-4636", extension: null, "+15148454636"), + FirstName = _faker.Person.FirstName, + LastName = _faker.Person.LastName, + FullName = _faker.Person.FullName, + Birthdate = _faker.Person.DateOfBirth, + Gender = _faker.Person.Gender.ToString().ToLower(), + Locale = new Locale("fr-CA"), + TimeZone = "America/Montreal" + }; + user.CustomAttributes.Add(new CustomAttribute("MultiFactorAuthenticationMode", MultiFactorAuthenticationMode.Phone.ToString())); + user.CustomAttributes.Add(new CustomAttribute("ProfileCompletedOn", completedOn.ToString("O", CultureInfo.InvariantCulture))); + UserProfile profile = user.ToUserProfile(); + Assert.NotNull(profile.Phone); + + Assert.Equal(user.CreatedOn, profile.CreatedOn); + Assert.Equal(completedOn, profile.CompletedOn); + Assert.Equal(user.UpdatedOn, profile.UpdatedOn); + Assert.Equal(user.PasswordChangedOn, profile.PasswordChangedOn); + Assert.Equal(user.AuthenticatedOn, profile.AuthenticatedOn); + Assert.Equal(MultiFactorAuthenticationMode.Phone, profile.MultiFactorAuthenticationMode); + Assert.Equal(user.Email.Address, profile.EmailAddress); + Assert.Equal(user.Phone.CountryCode, profile.Phone.CountryCode); + Assert.Equal(user.Phone.Number, profile.Phone.Number); + Assert.Equal(user.FirstName, profile.FirstName); + Assert.Equal(user.LastName, profile.LastName); + Assert.Equal(user.FullName, profile.FullName); + Assert.Equal(user.Birthdate, profile.Birthdate); + Assert.Equal(user.Gender, profile.Gender); + Assert.Equal(user.Locale, profile.Locale); + Assert.Equal(user.TimeZone, profile.TimeZone); + } }