From e1a249c07a94ef7da088de6224f1c6f012c7d439 Mon Sep 17 00:00:00 2001 From: csimonapastore Date: Tue, 17 Jun 2025 23:08:21 +0200 Subject: [PATCH] Adding crud operations for users --- MainProject.Tests/MainProject.Tests.csproj | 6 +- .../Services/UserService_Tests.cs | 188 +++++++++++++++++- MainProject/Controllers/RoleController.cs | 2 +- MainProject/Controllers/UserController.cs | 180 +++++++++++++++++ MainProject/MainProject.csproj | 34 ++-- .../Api/Data/User/CreateUserRequestData.cs | 4 +- .../Api/Data/User/UpdateUserRequestData.cs | 12 ++ .../Api/Request/User/UpdateUserRequest.cs | 14 ++ MainProject/Services/UserService.cs | 81 ++++++++ 9 files changed, 493 insertions(+), 28 deletions(-) create mode 100644 MainProject/Models/Api/Data/User/UpdateUserRequestData.cs create mode 100644 MainProject/Models/Api/Request/User/UpdateUserRequest.cs diff --git a/MainProject.Tests/MainProject.Tests.csproj b/MainProject.Tests/MainProject.Tests.csproj index b083669..975aa5a 100644 --- a/MainProject.Tests/MainProject.Tests.csproj +++ b/MainProject.Tests/MainProject.Tests.csproj @@ -14,11 +14,11 @@ runtime; build; native; contentfiles; analyzers; buildtransitive all - + - - + + diff --git a/MainProject.Tests/Services/UserService_Tests.cs b/MainProject.Tests/Services/UserService_Tests.cs index 80f5ae1..ce5f670 100644 --- a/MainProject.Tests/Services/UserService_Tests.cs +++ b/MainProject.Tests/Services/UserService_Tests.cs @@ -110,7 +110,7 @@ public class UserService_Tests if (_userService != null) { var user = await _userService.GetUserByUsernameAndPassword(_user.Email, "WrongPassword"); - Assert.IsTrue(user == null); + Assert.IsNull(user); } else { @@ -133,7 +133,7 @@ public class UserService_Tests if (_userService != null) { var user = await _userService.GetUserByUsernameAndPassword(_user.Email, password); - Assert.IsTrue(user != null); + Assert.IsNotNull(user); } else { @@ -250,7 +250,7 @@ public class UserService_Tests { var user = await _userService.GetUserByIdAsync(_user.Id); Assert.IsNotNull(user); - Assert.IsTrue(user.Id == _user?.Id); + Assert.AreEqual(user.Id, _user?.Id); } else { @@ -274,7 +274,7 @@ public class UserService_Tests { var user = await _userService.GetUserByGuidAsync(_user.Guid ?? String.Empty); Assert.IsNotNull(user); - Assert.IsTrue(user.Guid == _user?.Guid); + Assert.AreEqual(user.Guid, _user?.Guid); } else { @@ -288,6 +288,186 @@ public class UserService_Tests } } + [TestMethod] + public async Task UpdateUserAsync_Success() + { + try + { + UpdateUserRequestData data = new UpdateUserRequestData() + { + FirstName = "ChangedUserFirstName", + LastName = "ChangedUserLastName" + }; + + if (_userService != null) + { + Assert.IsNotNull(_user); + var user = await _userService.UpdateUserAsync(data, _user!); + Assert.IsInstanceOfType(user, typeof(User)); + Assert.IsNotNull(user); + Assert.AreEqual(data.FirstName, user.FirstName); + Assert.AreEqual(data.LastName, user.LastName); + _user = user; + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex}"); + } + } + + [TestMethod] + public async Task UpdateUserAsync_Exception() + { + try + { + UpdateUserRequestData data = new UpdateUserRequestData() + { + FirstName = "ChangedUserFirstName", + LastName = "ChangedUserLastName" + }; + + var exceptionUserService = TestUtils.CreateUserServiceException(); + + if (exceptionUserService != null) + { + Assert.IsNotNull(_user); + var user = await exceptionUserService.UpdateUserAsync(data, _user!); + Assert.Fail($"Expected exception instead of response: {user?.Guid}"); + + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + } + } + + + [TestMethod] + public async Task UpdateUserPasswordAsync_Success() + { + try + { + if (_userService != null) + { + Assert.IsNotNull(_user); + var oldPassword = _user.Password; + var user = await _userService.UpdateUserPasswordAsync(_user!, "this-is-a-new-password"); + Assert.IsInstanceOfType(user, typeof(User)); + Assert.IsNotNull(user); + Assert.IsTrue(user.Password != oldPassword); + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex}"); + } + } + + [TestMethod] + public async Task UpdateUserPasswordAsync_Exception() + { + try + { + var exceptionUserService = TestUtils.CreateUserServiceException(); + + if (exceptionUserService != null) + { + Assert.IsNotNull(_user); + var user = await exceptionUserService.UpdateUserPasswordAsync(_user!, "this-is-a-new-password"); + Assert.Fail($"Expected exception instead of response: {user?.Guid}"); + + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + } + } + + [TestMethod] + public async Task UpdateUserRoleAsync_Success() + { + try + { + if (_userService != null) + { + Assert.IsNotNull(_user); + Role role = new() + { + Name = "NewRole", + IsNotEditable = false, + Guid = Guid.NewGuid().ToString() + }; + + var oldRole = _user.Role; + var user = await _userService.UpdateUserRoleAsync(_user!, role); + Assert.IsInstanceOfType(user, typeof(User)); + Assert.IsNotNull(user); + Assert.IsTrue(user.Role?.Id != oldRole?.Id); + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Console.WriteLine(ex.InnerException); + Assert.Fail($"An exception was thrown: {ex}"); + } + } + + [TestMethod] + public async Task UpdateUserRoleAsync_Exception() + { + try + { + var exceptionUserService = TestUtils.CreateUserServiceException(); + + if (exceptionUserService != null) + { + Assert.IsNotNull(_user); + Role role = new() + { + Name = "NewRole", + IsNotEditable = false, + Guid = Guid.NewGuid().ToString() + }; + var user = await exceptionUserService.UpdateUserRoleAsync(_user!, role); + Assert.Fail($"Expected exception instead of response: {user?.Guid}"); + + } + else + { + Assert.Fail($"UserService is null"); + } + } + catch (Exception ex) + { + Assert.IsInstanceOfType(ex, typeof(Exception)); + } + } + [TestMethod] public async Task DeleteUser() { diff --git a/MainProject/Controllers/RoleController.cs b/MainProject/Controllers/RoleController.cs index 5b369a1..ef276d1 100644 --- a/MainProject/Controllers/RoleController.cs +++ b/MainProject/Controllers/RoleController.cs @@ -206,7 +206,7 @@ namespace BasicDotnetTemplate.MainProject.Controllers return NotFound(); } - await this._roleService.DeleteRoleAsync(role); + await this._roleService.DeleteRoleAsync(role); return Success(String.Empty); } diff --git a/MainProject/Controllers/UserController.cs b/MainProject/Controllers/UserController.cs index cd9502b..0822cf5 100644 --- a/MainProject/Controllers/UserController.cs +++ b/MainProject/Controllers/UserController.cs @@ -128,7 +128,187 @@ namespace BasicDotnetTemplate.MainProject.Controllers } + [JwtAuthorization()] + [HttpPut("update/{guid}")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType>(StatusCodes.Status400BadRequest)] + [ProducesResponseType>(StatusCodes.Status500InternalServerError)] + public async Task UpdateUserAsync([FromBody] UpdateUserRequest request, string guid) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(_requestNotWellFormed); + } + if (request == null || request.Data == null || + String.IsNullOrEmpty(request.Data.FirstName) || + String.IsNullOrEmpty(request.Data.LastName) + ) + { + return BadRequest(_requestNotWellFormed); + } + var user = await this._userService.GetUserByGuidAsync(guid); + if(user == null) + { + return NotFound(); + } + + user = await this._userService.UpdateUserAsync(request.Data, user); + + var userDto = _mapper?.Map(user); + + return Success(String.Empty, userDto); + + } + catch (Exception exception) + { + var message = this._somethingWentWrong; + if (!String.IsNullOrEmpty(exception.Message)) + { + message += $". {exception.Message}"; + } + return InternalServerError(message); + } + + } + + [JwtAuthorization()] + [HttpPut("update/{guid}/password")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType>(StatusCodes.Status400BadRequest)] + [ProducesResponseType>(StatusCodes.Status500InternalServerError)] + public async Task UpdateUserPasswordAsync(string guid, string newPassword) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(_requestNotWellFormed); + } + + if (String.IsNullOrEmpty(newPassword)) + { + return BadRequest(_requestNotWellFormed); + } + + var user = await this._userService.GetUserByGuidAsync(guid); + if(user == null) + { + return NotFound(); + } + + user = await this._userService.UpdateUserPasswordAsync(user, newPassword); + + var userDto = _mapper?.Map(user); + + return Success(String.Empty, userDto); + + } + catch (Exception exception) + { + var message = this._somethingWentWrong; + if (!String.IsNullOrEmpty(exception.Message)) + { + message += $". {exception.Message}"; + } + return InternalServerError(message); + } + + } + + [JwtAuthorization()] + [HttpPut("update/{guid}/role")] + [ProducesResponseType(StatusCodes.Status201Created)] + [ProducesResponseType>(StatusCodes.Status400BadRequest)] + [ProducesResponseType>(StatusCodes.Status500InternalServerError)] + public async Task UpdateUserRoleAsync(string guid, string roleGuid) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(_requestNotWellFormed); + } + + if (String.IsNullOrEmpty(roleGuid)) + { + return BadRequest(_requestNotWellFormed); + } + + var role = await this._roleService.GetRoleForUser(roleGuid); + if (role == null) + { + return BadRequest("Role not found"); + } + + var user = await this._userService.GetUserByGuidAsync(guid); + if(user == null) + { + return NotFound(); + } + + user = await this._userService.UpdateUserRoleAsync(user, role); + + var userDto = _mapper?.Map(user); + + return Success(String.Empty, userDto); + + } + catch (Exception exception) + { + var message = this._somethingWentWrong; + if (!String.IsNullOrEmpty(exception.Message)) + { + message += $". {exception.Message}"; + } + return InternalServerError(message); + } + + } + + [JwtAuthorization()] + [HttpDelete("{guid}")] + [ProducesResponseType(StatusCodes.Status200OK)] + [ProducesResponseType>(StatusCodes.Status404NotFound)] + [ProducesResponseType>(StatusCodes.Status400BadRequest)] + [ProducesResponseType>(StatusCodes.Status500InternalServerError)] + public async Task DeleteUserByGuidAsync(string guid) + { + try + { + if (!ModelState.IsValid) + { + return BadRequest(_requestNotWellFormed); + } + + if (String.IsNullOrEmpty(guid)) + { + return BadRequest(_requestNotWellFormed); + } + var user = await this._userService.GetUserByGuidAsync(guid); + + if (user == null || String.IsNullOrEmpty(user.Guid)) + { + return NotFound(); + } + + await this._userService.DeleteUserAsync(user); + + return Success(String.Empty); + } + catch (Exception exception) + { + var message = this._somethingWentWrong; + if (!String.IsNullOrEmpty(exception.Message)) + { + message += $". {exception.Message}"; + } + return InternalServerError(message); + } + + } } } \ No newline at end of file diff --git a/MainProject/MainProject.csproj b/MainProject/MainProject.csproj index 801b05c..3fecdf7 100644 --- a/MainProject/MainProject.csproj +++ b/MainProject/MainProject.csproj @@ -15,21 +15,21 @@ all - - - - - + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - - - + + + runtime; build; native; contentfiles; analyzers; buildtransitive all - + @@ -37,14 +37,14 @@ - - - - - - - - + + + + + + + + diff --git a/MainProject/Models/Api/Data/User/CreateUserRequestData.cs b/MainProject/Models/Api/Data/User/CreateUserRequestData.cs index 2202def..01fb31c 100644 --- a/MainProject/Models/Api/Data/User/CreateUserRequestData.cs +++ b/MainProject/Models/Api/Data/User/CreateUserRequestData.cs @@ -1,9 +1,7 @@ namespace BasicDotnetTemplate.MainProject.Models.Api.Data.User; -public class CreateUserRequestData +public class CreateUserRequestData : UpdateUserRequestData { - public string FirstName { get; set; } = String.Empty; - public string LastName { get; set; } = String.Empty; public string Email { get; set; } = String.Empty; public string Password { get; set; } = String.Empty; public string? RoleGuid { get; set; } diff --git a/MainProject/Models/Api/Data/User/UpdateUserRequestData.cs b/MainProject/Models/Api/Data/User/UpdateUserRequestData.cs new file mode 100644 index 0000000..a5772af --- /dev/null +++ b/MainProject/Models/Api/Data/User/UpdateUserRequestData.cs @@ -0,0 +1,12 @@ +namespace BasicDotnetTemplate.MainProject.Models.Api.Data.User; + +public class UpdateUserRequestData +{ + public string FirstName { get; set; } = String.Empty; + public string LastName { get; set; } = String.Empty; + +} + + + + diff --git a/MainProject/Models/Api/Request/User/UpdateUserRequest.cs b/MainProject/Models/Api/Request/User/UpdateUserRequest.cs new file mode 100644 index 0000000..c2bf2df --- /dev/null +++ b/MainProject/Models/Api/Request/User/UpdateUserRequest.cs @@ -0,0 +1,14 @@ +using BasicDotnetTemplate.MainProject.Models.Api.Data.User; + +namespace BasicDotnetTemplate.MainProject.Models.Api.Request.User; + +public class UpdateUserRequest +{ +#nullable enable + public UpdateUserRequestData? Data { get; set; } +#nullable disable +} + + + + diff --git a/MainProject/Services/UserService.cs b/MainProject/Services/UserService.cs index 54ecef4..2f04ce5 100644 --- a/MainProject/Services/UserService.cs +++ b/MainProject/Services/UserService.cs @@ -16,7 +16,10 @@ public interface IUserService Task GetUserByUsernameAndPassword(string email, string password); Task CheckIfEmailIsValid(string email, string? guid = ""); Task CreateUserAsync(CreateUserRequestData data, Role role); + Task UpdateUserAsync(UpdateUserRequestData data, User user); Task DeleteUserAsync(User user); + Task UpdateUserPasswordAsync(User user, string newPassword); + Task UpdateUserRoleAsync(User user, Role newRole); } public class UserService : BaseService, IUserService @@ -134,6 +137,31 @@ public class UserService : BaseService, IUserService return user; } + public async Task UpdateUserAsync(UpdateUserRequestData data, User user) + { + using var transaction = await _sqlServerContext.Database.BeginTransactionAsync(); + + try + { + user.FirstName = data.FirstName ?? user.FirstName; + user.LastName = data.LastName ?? user.LastName; + user.UpdateTime = DateTime.UtcNow; + user.UpdateUserId = this.GetCurrentUserId(); + + _sqlServerContext.Users.Update(user); + await _sqlServerContext.SaveChangesAsync(); + await transaction.CommitAsync(); + } + catch (Exception exception) + { + Logger.Error(exception, $"[UserService][UpdateUserAsync] | {transaction.TransactionId}"); + await transaction.RollbackAsync(); + throw new UpdateException($"An error occurred while updating the user for transaction ID {transaction.TransactionId}.", exception); + } + + return user; + } + public async Task DeleteUserAsync(User user) { bool? deleted = false; @@ -151,6 +179,59 @@ public class UserService : BaseService, IUserService return deleted; } + public async Task UpdateUserPasswordAsync(User user, string newPassword) + { + using var transaction = await _sqlServerContext.Database.BeginTransactionAsync(); + try + { + var salt = _appSettings.EncryptionSettings?.Salt ?? String.Empty; + var pepper = CryptUtils.GeneratePepper(); + var iterations = _appSettings.EncryptionSettings?.Iterations ?? 10; + + user.PasswordSalt = salt; + user.PasswordPepper = pepper; + user.PasswordIterations = iterations; + user.Password = CryptUtils.GeneratePassword(newPassword, salt, iterations, pepper); + user.UpdateTime = DateTime.UtcNow; + user.UpdateUserId = this.GetCurrentUserId(); + + _sqlServerContext.Users.Update(user); + await _sqlServerContext.SaveChangesAsync(); + await transaction.CommitAsync(); + } + catch (Exception exception) + { + Logger.Error(exception, $"[UserService][UpdateUserPasswordAsync] | {transaction.TransactionId}"); + await transaction.RollbackAsync(); + throw new UpdateException($"An error occurred while updating the user for transaction ID {transaction.TransactionId}.", exception); + } + + return user; + } + + public async Task UpdateUserRoleAsync(User user, Role newRole) + { + using var transaction = await _sqlServerContext.Database.BeginTransactionAsync(); + + try + { + user.Role = newRole; + user.UpdateTime = DateTime.UtcNow; + user.UpdateUserId = this.GetCurrentUserId(); + + _sqlServerContext.Users.Update(user); + await _sqlServerContext.SaveChangesAsync(); + await transaction.CommitAsync(); + } + catch (Exception exception) + { + Logger.Error(exception, $"[UserService][UpdateUserRoleAsync] | {transaction.TransactionId}"); + await transaction.RollbackAsync(); + throw new UpdateException($"An error occurred while updating the user for transaction ID {transaction.TransactionId}.", exception); + } + + return user; + } }