From eab694d7ec834495b2d8d35b248ccee1a42dc257 Mon Sep 17 00:00:00 2001 From: Johan Reitan Date: Fri, 19 Sep 2025 13:05:57 +0200 Subject: [PATCH 1/2] user done --- api.tests/UserEndpointTests/GetUserTests.cs | 1 + .../DTOs/GetObjects/UsersSuccessDTO.cs | 3 +- exercise.wwwapi/DTOs/Login/LoginSuccessDTO.cs | 3 +- .../DTOs/Register/RegisterSuccessDTO.cs | 3 +- exercise.wwwapi/DTOs/Users/PatchUserDTO.cs | 22 +++++ exercise.wwwapi/DTOs/{ => Users}/UserDTO.cs | 2 +- exercise.wwwapi/Endpoints/UserEndpoints.cs | 96 ++++++------------- exercise.wwwapi/Factories/UserFactory.cs | 4 +- exercise.wwwapi/Program.cs | 5 +- .../UserValidators/UserUpdateValidator.cs | 3 +- 10 files changed, 66 insertions(+), 76 deletions(-) create mode 100644 exercise.wwwapi/DTOs/Users/PatchUserDTO.cs rename exercise.wwwapi/DTOs/{ => Users}/UserDTO.cs (97%) diff --git a/api.tests/UserEndpointTests/GetUserTests.cs b/api.tests/UserEndpointTests/GetUserTests.cs index def65af..d47b931 100644 --- a/api.tests/UserEndpointTests/GetUserTests.cs +++ b/api.tests/UserEndpointTests/GetUserTests.cs @@ -1,6 +1,7 @@ using exercise.wwwapi.DTOs; using exercise.wwwapi.DTOs.GetUsers; using exercise.wwwapi.DTOs.Login; +using exercise.wwwapi.DTOs.Users; using exercise.wwwapi.Endpoints; using System.Text.Json; diff --git a/exercise.wwwapi/DTOs/GetObjects/UsersSuccessDTO.cs b/exercise.wwwapi/DTOs/GetObjects/UsersSuccessDTO.cs index 6719cbf..c3f71b8 100644 --- a/exercise.wwwapi/DTOs/GetObjects/UsersSuccessDTO.cs +++ b/exercise.wwwapi/DTOs/GetObjects/UsersSuccessDTO.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using exercise.wwwapi.DTOs.Users; +using System.Text.Json.Serialization; namespace exercise.wwwapi.DTOs.GetUsers; diff --git a/exercise.wwwapi/DTOs/Login/LoginSuccessDTO.cs b/exercise.wwwapi/DTOs/Login/LoginSuccessDTO.cs index 468d3fb..1e8b736 100644 --- a/exercise.wwwapi/DTOs/Login/LoginSuccessDTO.cs +++ b/exercise.wwwapi/DTOs/Login/LoginSuccessDTO.cs @@ -1,4 +1,5 @@ -using System.Text.Json.Serialization; +using exercise.wwwapi.DTOs.Users; +using System.Text.Json.Serialization; namespace exercise.wwwapi.DTOs.Login; diff --git a/exercise.wwwapi/DTOs/Register/RegisterSuccessDTO.cs b/exercise.wwwapi/DTOs/Register/RegisterSuccessDTO.cs index b6b2d43..671a8e8 100644 --- a/exercise.wwwapi/DTOs/Register/RegisterSuccessDTO.cs +++ b/exercise.wwwapi/DTOs/Register/RegisterSuccessDTO.cs @@ -1,4 +1,5 @@ -using System.ComponentModel.DataAnnotations.Schema; +using exercise.wwwapi.DTOs.Users; +using System.ComponentModel.DataAnnotations.Schema; namespace exercise.wwwapi.DTOs.Register; diff --git a/exercise.wwwapi/DTOs/Users/PatchUserDTO.cs b/exercise.wwwapi/DTOs/Users/PatchUserDTO.cs new file mode 100644 index 0000000..fac1d50 --- /dev/null +++ b/exercise.wwwapi/DTOs/Users/PatchUserDTO.cs @@ -0,0 +1,22 @@ +using exercise.wwwapi.Enums; +using System.Text.Json.Serialization; + +namespace exercise.wwwapi.DTOs.Users +{ + public class PatchUserDTO + { + public string? Email { get; set; } + + public string? Password { get; set; } + + public string? FirstName { get; set; } + + public string? LastName { get; set; } + public string? Bio { get; set; } + public string? Github { get; set; } + public string? Username { get; set; } + public string? Mobile { get; set; } + public Specialism? Specialism { get; set; } + public Role? Role { get; set; } + } +} diff --git a/exercise.wwwapi/DTOs/UserDTO.cs b/exercise.wwwapi/DTOs/Users/UserDTO.cs similarity index 97% rename from exercise.wwwapi/DTOs/UserDTO.cs rename to exercise.wwwapi/DTOs/Users/UserDTO.cs index cee7428..2d90265 100644 --- a/exercise.wwwapi/DTOs/UserDTO.cs +++ b/exercise.wwwapi/DTOs/Users/UserDTO.cs @@ -4,7 +4,7 @@ using System.ComponentModel.DataAnnotations.Schema; using System.Text.Json.Serialization; -namespace exercise.wwwapi.DTOs; +namespace exercise.wwwapi.DTOs.Users; public class UserDTO { diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 28bbf04..094a693 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -34,10 +34,10 @@ public static void ConfigureAuthApi(this WebApplication app) users.MapPost("/", Register).WithSummary("Create user"); //OKOKOK users.MapGet("/by_cohortcourse/{id}", GetUsersByCohortCourse).WithSummary("Get all users from a cohortCourse"); //OKOKOK users.MapGet("/by_cohort/{id}", GetUsersByCohort).WithSummary("Get all users from a cohort"); //OKOKOK - users.MapGet("/", GetUsers).WithSummary("Get all users or filter by first name, last name or full name"); - users.MapGet("/{id}", GetUserById).WithSummary("Get user by user id"); - app.MapPost("/login", Login).WithSummary("Localhost Login"); - users.MapPatch("/{id}", UpdateUser).RequireAuthorization().WithSummary("Update a user"); + users.MapGet("/", GetUsers).WithSummary("Get all users or filter by first name, last name or full name");//OKOKOK + users.MapGet("/{id}", GetUserById).WithSummary("Get user by user id"); //OKOKOK + app.MapPost("/login", Login).WithSummary("Localhost Login"); //OKOKOK + users.MapPatch("/{id}", UpdateUser).RequireAuthorization().WithSummary("Update a user");//OKOKOK users.MapDelete("/{id}", DeleteUser).RequireAuthorization().WithSummary("Delete a user"); } @@ -45,7 +45,7 @@ public static void ConfigureAuthApi(this WebApplication app) private static async Task GetUsers(IRepository userRepository, string? searchTerm, ClaimsPrincipal claimPrincipal) { - var results = (await userRepository.GetAllAsync(u => u.Notes)).ToList(); + var results = await userRepository.GetWithIncludes(a => a.Include(u => u.Notes)); if (!string.IsNullOrWhiteSpace(searchTerm)) { @@ -56,19 +56,9 @@ private static async Task GetUsers(IRepository userRepository, st var userRole = claimPrincipal.Role(); var authorizedAsTeacher = AuthorizeTeacher(claimPrincipal); - var userData = new UsersSuccessDTO - { - Users = results.Select(user => authorizedAsTeacher - ? UserFactory.GetUserDTO(user, PrivilegeLevel.Teacher) - : UserFactory.GetUserDTO(user, PrivilegeLevel.Student)) - .ToList() - }; + - var response = new ResponseDTO - { - Status = "success", - Data = userData - }; + var response = results.Select(u => new UserDTO(u)).ToList(); return TypedResults.Ok(response); } @@ -168,15 +158,17 @@ private static async Task Register(PostUserDTO request, IRepository Login(LoginRequestDTO request, IRepository userRepository, IConfigurationSettings configurationSettings) { - var allUsers = await userRepository.GetAllAsync(); - var user = allUsers.FirstOrDefault(u => u.Email == request.Email); - if (user == null) + var response = await userRepository.GetWithIncludes(x => x.Where(u => u.Email == request.Email)); + if (response.Count == 0) { return Results.BadRequest(new Payload { - Status = "User does not exist", Data = new { email = "Invalid email and/or password provided" } + Status = "User does not exist", + Data = new { email = "Invalid email and/or password provided" } }); } + var user = response.FirstOrDefault(); + if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash)) { @@ -189,7 +181,7 @@ private static async Task Login(LoginRequestDTO request, IRepository + var result = new ResponseDTO { Status = "Success", Data = new LoginSuccessDTO @@ -198,41 +190,24 @@ private static async Task Login(LoginRequestDTO request, IRepository GetUserById(IRepository userRepository, int id, ClaimsPrincipal claimsPrincipal) { - var user = await userRepository.GetByIdAsync( - id, - user => user.Notes - ); - if (user == null) + var response = await userRepository.GetByIdWithIncludes(x => x.Include(u => u.Notes), id); + + if (response == null) { return TypedResults.NotFound(); } - var response = new ResponseDTO - { - Status = "success", - Data = UserFactory.GetUserDTO(user, PrivilegeLevel.Student) - }; + var result = new UserDTO(response); - var userRole = claimsPrincipal.Role(); - if (userRole == "Teacher") - { - response.Data.Notes = user.Notes.Select(note => new NoteDTO - { - Id = note.Id, - Title = note.Title, - Content = note.Content, - CreatedAt = note.CreatedAt, - UpdatedAt = note.UpdatedAt - }).ToList(); - } + return TypedResults.Ok(response); } @@ -242,16 +217,13 @@ public static async Task GetUserById(IRepository userRepository, [ProducesResponseType(StatusCodes.Status400BadRequest)] [ProducesResponseType(StatusCodes.Status401Unauthorized)] public static async Task UpdateUser(IRepository userRepository, int id, - UpdateUserRequestDTO request, - IValidator validator, ClaimsPrincipal claimsPrinciple + PatchUserDTO request, + IValidator validator, ClaimsPrincipal claimsPrinciple ) { // Only teacher can edit protected fields var authorized = AuthorizeTeacher(claimsPrinciple); - if (!authorized && (request.StartDate is not null - || request.EndDate is not null - || request.CohortId is not null - || request.Specialism is not null + if (!authorized && (request.Specialism is not null || request.Role is not null)) { return Results.Unauthorized(); @@ -316,22 +288,12 @@ public static async Task UpdateUser(IRepository userRepository, i userRepository.Update(user); await userRepository.SaveAsync(); - var response = new ResponseDTO() + var result = await userRepository.GetByIdWithIncludes(x => x.Include(u => u.Notes), id); + + var response = new ResponseDTO() { Status = "success", - Data = new UpdateUserSuccessDTO() - { - Id = user.Id, - Email = user.Email, - FirstName = user.FirstName, - LastName = user.LastName, - Bio = user.Bio, - Github = user.Github, - Username = user.Username, - Mobile = user.Mobile, - Specialism = (Specialism)user.Specialism, - Role = (Role)user.Role, - } + Data = new UserDTO(result) }; return TypedResults.Ok(response); @@ -350,7 +312,7 @@ public static async Task DeleteUser(IRepository userRepository, i return Results.Unauthorized(); } - var user = await userRepository.GetByIdAsync(id); + var user = await userRepository.GetByIdWithIncludes(null, id); if (user == null) { return TypedResults.NotFound(); @@ -378,7 +340,7 @@ private static string CreateToken(User user, IConfigurationSettings configuratio new(ClaimTypes.Role, user.Role.ToString()) }; - var tokenKey = Environment.GetEnvironmentVariable(Globals.EnvironmentEnvVariable) == "Staging" + var tokenKey = Environment.GetEnvironmentVariable(Globals.EnvironmentEnvVariable) == "Staging" ? Globals.TestTokenKey : Globals.TokenKey; var rawToken = configurationSettings.GetValue(tokenKey); diff --git a/exercise.wwwapi/Factories/UserFactory.cs b/exercise.wwwapi/Factories/UserFactory.cs index ed00d41..da1f3b0 100644 --- a/exercise.wwwapi/Factories/UserFactory.cs +++ b/exercise.wwwapi/Factories/UserFactory.cs @@ -1,5 +1,5 @@ -using exercise.wwwapi.DTOs; -using exercise.wwwapi.DTOs.Notes; +using exercise.wwwapi.DTOs.Notes; +using exercise.wwwapi.DTOs.Users; using exercise.wwwapi.Enums; using exercise.wwwapi.Models; using System.Numerics; diff --git a/exercise.wwwapi/Program.cs b/exercise.wwwapi/Program.cs index ccfca37..9c7b569 100644 --- a/exercise.wwwapi/Program.cs +++ b/exercise.wwwapi/Program.cs @@ -50,7 +50,7 @@ // Register validators builder.Services.AddScoped, UserRegisterValidator>(); -builder.Services.AddScoped, UserUpdateValidator>(); +builder.Services.AddScoped, UserUpdateValidator>(); builder.Services.AddScoped, CreateNoteValidator>(); builder.Services.AddScoped, UpdateNoteValidator>(); @@ -197,4 +197,5 @@ public partial class Program { -} // needed for testing - please ignore \ No newline at end of file +} // needed for testing - please ignore + diff --git a/exercise.wwwapi/Validators/UserValidators/UserUpdateValidator.cs b/exercise.wwwapi/Validators/UserValidators/UserUpdateValidator.cs index 173346e..cba1cac 100644 --- a/exercise.wwwapi/Validators/UserValidators/UserUpdateValidator.cs +++ b/exercise.wwwapi/Validators/UserValidators/UserUpdateValidator.cs @@ -1,9 +1,10 @@ using exercise.wwwapi.DTOs.UpdateUser; +using exercise.wwwapi.DTOs.Users; using FluentValidation; namespace exercise.wwwapi.Validators.UserValidators; -public class UserUpdateValidator : AbstractValidator +public class UserUpdateValidator : AbstractValidator { public UserUpdateValidator() { From fb2f0d2ae48d96a347935e717c43fa10e5cdb6f9 Mon Sep 17 00:00:00 2001 From: johanreitan Date: Fri, 19 Sep 2025 13:12:22 +0200 Subject: [PATCH 2/2] Clarify data fetching in Login method Added comment to clarify the purpose of the where-statement in the Login method. --- exercise.wwwapi/Endpoints/UserEndpoints.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/exercise.wwwapi/Endpoints/UserEndpoints.cs b/exercise.wwwapi/Endpoints/UserEndpoints.cs index 094a693..e1defab 100644 --- a/exercise.wwwapi/Endpoints/UserEndpoints.cs +++ b/exercise.wwwapi/Endpoints/UserEndpoints.cs @@ -158,7 +158,7 @@ private static async Task Register(PostUserDTO request, IRepository Login(LoginRequestDTO request, IRepository userRepository, IConfigurationSettings configurationSettings) { - var response = await userRepository.GetWithIncludes(x => x.Where(u => u.Email == request.Email)); + var response = await userRepository.GetWithIncludes(x => x.Where(u => u.Email == request.Email)); // uses where-statement to filter data before fetching if (response.Count == 0) { return Results.BadRequest(new Payload