diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/IUserGateway.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/IUserGateway.cs index 42350e8..0aeb332 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/IUserGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/IUserGateway.cs @@ -7,4 +7,7 @@ public interface IUserGateway { Task GetUsers(UserFilterParams filterParams, int currentPage); Task> GetRoles(); + Task CreateUser(UserDto user); + Task UpdateUser(UserDto user); + Task DeleteUser(string userId); } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/UserGateway.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/UserGateway.cs index b6e7a15..81161c1 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/UserGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/UserGateway.cs @@ -9,6 +9,32 @@ namespace GameIdeas.BlazorApp.Pages.Users.Gateways; public class UserGateway(IHttpClientService httpClient) : IUserGateway { + public async Task CreateUser(UserDto user) + { + try + { + return await httpClient.PostAsync(Endpoints.User.Create, user) + ?? throw new InvalidOperationException(ResourcesKey.ErrorCreateUser); + } + catch (Exception) + { + throw new UserCreationException(ResourcesKey.ErrorCreateUser); + } + } + + public async Task DeleteUser(string userId) + { + try + { + return await httpClient.DeleteAsync(Endpoints.User.Delete(userId)) + ?? throw new InvalidOperationException(ResourcesKey.ErrorDeleteUser); + } + catch (Exception) + { + throw new UserCreationException(ResourcesKey.ErrorDeleteUser); + } + } + public async Task> GetRoles() { try @@ -42,4 +68,17 @@ public class UserGateway(IHttpClientService httpClient) : IUserGateway throw new UserNotFoundException(ResourcesKey.ErrorFetchUsers); } } + + public async Task UpdateUser(UserDto user) + { + try + { + return await httpClient.PutAsync(Endpoints.User.Update(user.Id ?? string.Empty), user) + ?? throw new InvalidOperationException(ResourcesKey.ErrorUpdateUser); + } + catch (Exception) + { + throw new UserCreationException(ResourcesKey.ErrorUpdateUser); + } + } } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs index 46d63d5..53c7180 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Constants/Endpoints.cs @@ -25,5 +25,9 @@ public static class Endpoints { public static string Fetch(UserFilterDto filter) => $"api/User?{UrlHelper.BuildUrlParams(filter)}"; public const string Roles = "api/User/Roles"; + public const string Create = "api/User/Create"; + public static string Delete(string userId) => $"api/User/Delete/{userId}"; + public static string Update(string userId) => $"api/User/Update/{userId}"; + } } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Exceptions/UserCreationException.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Exceptions/UserCreationException.cs new file mode 100644 index 0000000..4e4f945 --- /dev/null +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Shared/Exceptions/UserCreationException.cs @@ -0,0 +1,3 @@ +namespace GameIdeas.BlazorApp.Shared.Exceptions; + +public class UserCreationException(string message) : Exception(message); diff --git a/src/GameIdeas/GameIdeas.Resources/CreateStaticResourceKey.cs b/src/GameIdeas/GameIdeas.Resources/CreateStaticResourceKey.cs index c4e7c03..71ca1ab 100644 --- a/src/GameIdeas/GameIdeas.Resources/CreateStaticResourceKey.cs +++ b/src/GameIdeas/GameIdeas.Resources/CreateStaticResourceKey.cs @@ -58,6 +58,9 @@ public class Translations (TranslationService translationService) public string ErrorFetchUsers => translationService.Translate(nameof(ErrorFetchUsers)); public string ErrorFetchRoles => translationService.Translate(nameof(ErrorFetchRoles)); public string MissingField => translationService.Translate(nameof(MissingField)); + public string ErrorCreateUser => translationService.Translate(nameof(ErrorCreateUser)); + public string ErrorUpdateUser => translationService.Translate(nameof(ErrorUpdateUser)); + public string ErrorDeleteUser => translationService.Translate(nameof(ErrorDeleteUser)); } public static class ResourcesKey @@ -124,4 +127,7 @@ public static class ResourcesKey public static string ErrorFetchUsers => _instance?.ErrorFetchUsers ?? throw new InvalidOperationException("ResourcesKey.ErrorFetchUsers is not initialized."); public static string ErrorFetchRoles => _instance?.ErrorFetchRoles ?? throw new InvalidOperationException("ResourcesKey.ErrorFetchRoles is not initialized."); public static string MissingField => _instance?.MissingField ?? throw new InvalidOperationException("ResourcesKey.MissingField is not initialized."); + public static string ErrorCreateUser => _instance?.ErrorCreateUser ?? throw new InvalidOperationException("ResourcesKey.ErrorCreateUser is not initialized."); + public static string ErrorUpdateUser => _instance?.ErrorUpdateUser ?? throw new InvalidOperationException("ResourcesKey.ErrorUpdateUser is not initialized."); + public static string ErrorDeleteUser => _instance?.ErrorDeleteUser ?? throw new InvalidOperationException("ResourcesKey.ErrorDeleteUser is not initialized."); } \ No newline at end of file diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs index f289c36..ba76f3d 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs @@ -10,7 +10,8 @@ namespace GameIdeas.WebAPI.Controllers; [ApiController] [Route("api/[controller]")] public class UserController( - IUserService userService, + IUserReadService userReadService, + IUserWriteService userWriteService, ILoggerFactory loggerFactory) : Controller { private readonly ILogger logger = loggerFactory.CreateLogger(); @@ -20,7 +21,7 @@ public class UserController( { try { - return Ok(await userService.Login(model)); + return Ok(await userReadService.Login(model)); } catch (UserInvalidException e) { @@ -45,7 +46,7 @@ public class UserController( { try { - return Ok(await userService.GetRoles()); + return Ok(await userReadService.GetRoles()); } catch (Exception e) { @@ -60,7 +61,7 @@ public class UserController( { try { - return Ok(await userService.GetUsers(filter)); + return Ok(await userReadService.GetUsers(filter)); } catch (Exception e) { @@ -68,4 +69,49 @@ public class UserController( return StatusCode(500, e.Message); } } + + [Authorize(Roles = GlobalConstants.ADMINISTRATOR)] + [HttpPost("Create")] + public async Task> CreateUser([FromBody] UserDto user) + { + try + { + return Created("/Create", await userWriteService.CreateUser(user)); + } + catch (Exception e) + { + logger.LogError(e, "Internal error while create user"); + return StatusCode(500, e.Message); + } + } + + [Authorize(Roles = GlobalConstants.ADMINISTRATOR)] + [HttpPut("Update/{userId}")] + public async Task> UpdateUser(string userId, [FromBody] UserDto user) + { + try + { + return Created("/Update", await userWriteService.UpdateUser(userId, user)); + } + catch (Exception e) + { + logger.LogError(e, "Internal error while update user"); + return StatusCode(500, e.Message); + } + } + + [Authorize(Roles = GlobalConstants.ADMINISTRATOR)] + [HttpDelete("Delete/{userId}")] + public async Task> DeleteUser(string userId) + { + try + { + return Created("/Delete", await userWriteService.DeleteUser(userId)); + } + catch (Exception e) + { + logger.LogError(e, "Internal error while delete user"); + return StatusCode(500, e.Message); + } + } } diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json b/src/GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json index 9ee40a8..4ce3ca9 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Files/GameIdeas.fr.json @@ -34,7 +34,7 @@ "RequestFailedStatusFormat": "Erreur lors de la réponse, code {0}", "ErrorFetchCategories": "Erreur lors de la récupération des catégories", "PlaceholderAdd": "Ajouter un nouveau", - "ErrorCreateGame": "Erreur lors de la Création d'un jeu", + "ErrorCreateGame": "Erreur lors de la création d'un jeu", "InvalidTitle": "Le titre est incorrect", "InvalidInterest": "L'interêt est incorrect", "Unknown": "Inconnu", @@ -53,5 +53,8 @@ "Roles": "Rôles", "ErrorFetchUsers": "Erreur lors de la récupération des utilisateurs", "ErrorFetchRoles": "Erreur lors de la récupération des rôles", - "MissingField": "Un champs est manquant" + "MissingField": "Un champs est manquant", + "ErrorCreateUser": "Erreur lors de la création d'un utilisateur", + "ErrorUpdateUser": "Erreur lors de la mise à jour d'un utilisateur", + "ErrorDeleteUser": "Erreur lors de la suppression d'un utilisateur" } \ No newline at end of file diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index a110899..1bd9aa5 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -64,7 +64,8 @@ services.AddAuthorization(); services.AddSingleton(); services.AddSingleton(); -services.AddScoped(); +services.AddScoped(); +services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped(); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserReadService.cs similarity index 86% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserService.cs rename to src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserReadService.cs index 3e826a0..cd8ac81 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserReadService.cs @@ -2,7 +2,7 @@ namespace GameIdeas.WebAPI.Services.Users; -public interface IUserService +public interface IUserReadService { Task Login(UserDto user); Task> GetRoles(); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserWriteService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserWriteService.cs new file mode 100644 index 0000000..ce5d70a --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/IUserWriteService.cs @@ -0,0 +1,10 @@ +using GameIdeas.Shared.Dto; + +namespace GameIdeas.WebAPI.Services.Users; + +public interface IUserWriteService +{ + Task CreateUser(UserDto user); + Task UpdateUser(string userId, UserDto user); + Task DeleteUser(string userId); +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs similarity index 98% rename from src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserService.cs rename to src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs index 1b5dc5f..f60d4bb 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserReadService.cs @@ -14,10 +14,10 @@ using System.Text; namespace GameIdeas.WebAPI.Services.Users; -public class UserService( +public class UserReadService( UserManager userManager, GameIdeasContext context, - IMapper mapper) : IUserService + IMapper mapper) : IUserReadService { public async Task> GetRoles() { diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs new file mode 100644 index 0000000..404a883 --- /dev/null +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs @@ -0,0 +1,83 @@ +using AutoMapper; +using GameIdeas.Resources; +using GameIdeas.Shared.Dto; +using GameIdeas.Shared.Model; +using GameIdeas.WebAPI.Exceptions; +using Microsoft.AspNetCore.Identity; + +namespace GameIdeas.WebAPI.Services.Users; + +public class UserWriteService( + UserManager userManager) : IUserWriteService +{ + public async Task CreateUser(UserDto user) + { + if (user.Username == null || + user.Password == null || + user.Role == null) + { + throw new UserInvalidException(ResourcesKey.MissingField); + } + + User userToCreate = new() { UserName = user.Username }; + + var result = await userManager.CreateAsync(userToCreate, user.Password); + + if (result.Succeeded) + { + await userManager.AddToRoleAsync(userToCreate, user.Role.Name); + } + else + { + throw new UserInvalidException(string.Join("; ", result.Errors)); + } + + return userToCreate.Id; + } + + public async Task DeleteUser(string userId) + { + if (userId == null) + { + throw new ArgumentNullException(ResourcesKey.MissingField); + } + + var user = await userManager.FindByIdAsync(userId) + ?? throw new UserInvalidException("User not found"); + + await userManager.DeleteAsync(user); + return userId; + } + + public async Task UpdateUser(string userId, UserDto user) + { + if (userId == null) + { + throw new ArgumentNullException(ResourcesKey.MissingField); + } + + var userToUpdate = await userManager.FindByIdAsync(userId) + ?? throw new UserInvalidException("User not found"); + + if (user.Username != null) + { + userToUpdate.UserName = user.Username; + await userManager.UpdateAsync(userToUpdate); + } + + if (user.Password != null) + { + await userManager.RemovePasswordAsync(userToUpdate); + await userManager.AddPasswordAsync(userToUpdate, user.Password); + } + + if (user.Role != null) + { + var roles = await userManager.GetRolesAsync(userToUpdate); + await userManager.RemoveFromRolesAsync(userToUpdate, roles); + await userManager.AddToRoleAsync(userToUpdate, user.Role.Name); + } + + return userToUpdate.Id; + } +}