From 757e9db08db400da52faa8dc04b569759d88d652 Mon Sep 17 00:00:00 2001 From: Egamorf Date: Sun, 27 Apr 2025 19:56:17 +0200 Subject: [PATCH] Crud on users --- .../Pages/UserMenu/Components/Login.razor | 2 +- .../Pages/UserMenu/UserMenu.razor.cs | 4 +- .../Pages/Users/Components/UserRow.razor | 21 +++--- .../Pages/Users/Components/UserRow.razor.cs | 11 +-- .../Pages/Users/Components/UserRow.razor.css | 14 +++- .../Pages/Users/Components/UserValidator.cs | 31 ++++++++- .../Pages/Users/Gateways/IUserGateway.cs | 6 +- .../Pages/Users/Gateways/UserGateway.cs | 12 ++-- .../Pages/Users/Users.razor | 8 ++- .../Pages/Users/Users.razor.cs | 69 +++++++++++++++++-- .../Pages/Users/Users.razor.css | 8 ++- .../Constants/GlobalConstants.cs | 2 + src/GameIdeas/GameIdeas.Shared/Dto/IdDto.cs | 6 ++ .../Controllers/UserController.cs | 15 ++-- .../20250420160158_SeedDefaultUser.cs | 4 +- .../Server/GameIdeas.WebAPI/Program.cs | 11 +++ .../Services/Users/UserWriteService.cs | 2 +- 17 files changed, 182 insertions(+), 44 deletions(-) create mode 100644 src/GameIdeas/GameIdeas.Shared/Dto/IdDto.cs diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/UserMenu/Components/Login.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/UserMenu/Components/Login.razor index c9044f0..e7be564 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/UserMenu/Components/Login.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/UserMenu/Components/Login.razor @@ -10,7 +10,7 @@
@ResourcesKey.EnterPassword
-
diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/UserMenu/UserMenu.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/UserMenu/UserMenu.razor.cs index cd40676..b339592 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/UserMenu/UserMenu.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/UserMenu/UserMenu.razor.cs @@ -6,13 +6,15 @@ namespace GameIdeas.BlazorApp.Pages.UserMenu; public partial class UserMenu { [Inject] private IAuthGateway AuthGateway { get; set; } = default!; + [Inject] private NavigationManager NavigationManager { get; set; } = default!; private bool ContentVisile = false; private async Task HandleLogoutClicked() { - await AuthGateway.Logout(); ContentVisile = false; + await AuthGateway.Logout(); + NavigationManager.NavigateTo("/Games"); } private void HandleAccountClicked() diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor index cbaa834..760e5a1 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor @@ -1,29 +1,34 @@ -@using GameIdeas.BlazorApp.Shared.Components.Select +@using Blazored.FluentValidation +@using GameIdeas.BlazorApp.Shared.Components.Select @using GameIdeas.BlazorApp.Shared.Components.Select.Models @using GameIdeas.BlazorApp.Shared.Constants @using GameIdeas.Shared.Dto +
@Icons.Account
- +
- +
- + @(User.Role?.Name ?? ResourcesKey.Unknown)
- - + @if (CanEdit) + { + + } +
\ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor.cs index b95f65e..ebb1ea3 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor.cs @@ -1,3 +1,4 @@ +using FluentValidation; using GameIdeas.BlazorApp.Shared.Components.Select; using GameIdeas.BlazorApp.Shared.Components.Select.Models; using GameIdeas.Shared.Dto; @@ -10,9 +11,11 @@ public partial class UserRow { [Parameter] public UserDto User { get; set; } = new(); [Parameter] public List Roles { get; set; } = []; - [Parameter] public bool CanEdit { get; set; } = false; + [Parameter] public bool CanEdit { get; set; } = true; + [Parameter] public bool IsEditing { get; set; } = false; [Parameter] public EventCallback OnRemove { get; set; } [Parameter] public EventCallback OnSubmit { get; set; } + [Parameter] public IValidator Validator { get; set; } = default!; private SelectParams SelectRoleParams = new(); private EditContext? EditContext; @@ -53,7 +56,7 @@ public partial class UserRow return; } - CanEdit = false; + IsEditing = false; await OnSubmit.InvokeAsync(User); User.Password = null; } @@ -61,14 +64,14 @@ public partial class UserRow private void HandleEditClicked() { - if (CanEdit) + if (IsEditing) { User.Username = OriginalUser?.Username; User.Role = OriginalUser?.Role; User.Password = null; } - CanEdit = !CanEdit; + IsEditing = !IsEditing; } private async Task HandleRemoveClicked() diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor.css b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor.css index 9b7d817..b567a84 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor.css +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserRow.razor.css @@ -22,11 +22,17 @@ width: fit-content; } -input[disabled], input, input::placeholder { +::deep .input-name, +::deep .input-name[disabled], +::deep .input-password, +::deep .input-password[disabled], +::deep .input-password::placeholder { color: var(--white); } -input, .role-label { +::deep .input-name, +::deep .input-password, +.role-label { border: none; outline: none; background: none; @@ -38,7 +44,9 @@ input, .role-label { background: rgb(0, 0, 0, 0.2); } - input[disabled], .disabled { + ::deep .input-name[disabled], + ::deep .input-password[disabled], + .disabled { border: none; background: none; } diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserValidator.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserValidator.cs index 7550c83..987f254 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserValidator.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Components/UserValidator.cs @@ -4,9 +4,9 @@ using GameIdeas.Shared.Dto; namespace GameIdeas.BlazorApp.Pages.Users.Components; -public class UserValidator : AbstractValidator +public class UserCreateValidator : AbstractValidator { - public UserValidator() + public UserCreateValidator() { RuleFor(user => user.Username) .NotEmpty() @@ -21,3 +21,30 @@ public class UserValidator : AbstractValidator .WithMessage(ResourcesKey.MissingField); } } + +public class UserUpdateValidator : AbstractValidator +{ + public UserUpdateValidator() + { + When(user => user.Password == null && user.Role == null, () => + { + RuleFor(user => user.Username) + .NotEmpty() + .WithMessage(ResourcesKey.MissingField); + }); + + When(user => user.Username == null && user.Role == null, () => + { + RuleFor(user => user.Password) + .NotEmpty() + .WithMessage(ResourcesKey.MissingField); + }); + + When(user => user.Username == null && user.Password == null, () => + { + RuleFor(user => user.Role) + .NotEmpty() + .WithMessage(ResourcesKey.MissingField); + }); + } +} 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 0aeb332..8cb0195 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/IUserGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/IUserGateway.cs @@ -7,7 +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); + 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 81161c1..2f89712 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/UserGateway.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Gateways/UserGateway.cs @@ -9,11 +9,11 @@ namespace GameIdeas.BlazorApp.Pages.Users.Gateways; public class UserGateway(IHttpClientService httpClient) : IUserGateway { - public async Task CreateUser(UserDto user) + public async Task CreateUser(UserDto user) { try { - return await httpClient.PostAsync(Endpoints.User.Create, user) + return await httpClient.PostAsync(Endpoints.User.Create, user) ?? throw new InvalidOperationException(ResourcesKey.ErrorCreateUser); } catch (Exception) @@ -22,11 +22,11 @@ public class UserGateway(IHttpClientService httpClient) : IUserGateway } } - public async Task DeleteUser(string userId) + public async Task DeleteUser(string userId) { try { - return await httpClient.DeleteAsync(Endpoints.User.Delete(userId)) + return await httpClient.DeleteAsync(Endpoints.User.Delete(userId)) ?? throw new InvalidOperationException(ResourcesKey.ErrorDeleteUser); } catch (Exception) @@ -69,11 +69,11 @@ public class UserGateway(IHttpClientService httpClient) : IUserGateway } } - public async Task UpdateUser(UserDto user) + public async Task UpdateUser(UserDto user) { try { - return await httpClient.PutAsync(Endpoints.User.Update(user.Id ?? string.Empty), user) + return await httpClient.PutAsync(Endpoints.User.Update(user.Id ?? string.Empty), user) ?? throw new InvalidOperationException(ResourcesKey.ErrorUpdateUser); } catch (Exception) diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor index 85cc787..d337781 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor @@ -16,17 +16,21 @@
+ Items="Roles.ToList()" GetLabel="@(role => role.Name)" Theme="SelectTheme.Filter" />
+ + + +
@if (!IsLoading) { @foreach (var user in UserList.Users ?? []) { - + } } else diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor.cs b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor.cs index 8a7677f..f6d9bbb 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor.cs +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor.cs @@ -16,6 +16,7 @@ public partial class Users private UserListDto UserList = new(); private IEnumerable Roles = []; private int CurrentPage = 1; + private UserDto UserAdd = new(); protected override async Task OnInitializedAsync() { @@ -43,12 +44,72 @@ public partial class Users IsLoading = false; } } - private Task HandleSubmitUser(UserDto args) + + private async Task HandleSubmitUser(UserDto user) { - throw new NotImplementedException(); + try + { + IsLoading = true; + + await UserGateway.CreateUser(user); + await FetchData(false); + } + catch (Exception) + { + throw; + } + finally + { + IsLoading = false; + } + + UserAdd = new(); } - private Task HandleRemoveUser(UserDto args) + + private async Task HandleUpdateUser(UserDto user) { - throw new NotImplementedException(); + try + { + IsLoading = true; + + await UserGateway.UpdateUser(user); + await FetchData(false); + } + catch (Exception) + { + throw; + } + finally + { + IsLoading = false; + } + } + + private async Task HandleRemoveUser(UserDto user) + { + if (user.Id == null) + { + return; + } + + try + { + IsLoading = true; + + await UserGateway.DeleteUser(user.Id); + await FetchData(false); + } + catch (Exception) + { + throw; + } + finally + { + IsLoading = false; + } + } + private void HandleResetUser(UserDto args) + { + UserAdd = new(); } } \ No newline at end of file diff --git a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor.css b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor.css index 18bbb60..0bbbd2d 100644 --- a/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor.css +++ b/src/GameIdeas/Client/GameIdeas.BlazorApp/Pages/Users/Users.razor.css @@ -4,13 +4,19 @@ gap: 8px; } -::deep .search-container .select-container { +::deep .search-container, ::deep .select-container { box-sizing: border-box; max-width: 200px; } .container { padding: 20px 200px; + display: grid; + grid-gap: 20px; +} + +.line { + border: 1px solid var(--line); } .content { diff --git a/src/GameIdeas/GameIdeas.Shared/Constants/GlobalConstants.cs b/src/GameIdeas/GameIdeas.Shared/Constants/GlobalConstants.cs index 3801e02..9d9176d 100644 --- a/src/GameIdeas/GameIdeas.Shared/Constants/GlobalConstants.cs +++ b/src/GameIdeas/GameIdeas.Shared/Constants/GlobalConstants.cs @@ -7,7 +7,9 @@ public class GlobalConstants public readonly static Guid MEMBER_ID = Guid.Parse("{BCE14DEA-1748-4A76-8485-ADEE83DF5EFD}"); public const string ADMINISTRATOR = "Administrateur"; + public const string ADMINISTRATOR_NORMALIZED = "ADMINISTRATEUR"; public const string MEMBER = "Membre"; + public const string MEMBER_NORMALIZED = "MEMBRE"; public const string ADMIN_MEMBER = $"{ADMINISTRATOR}, {MEMBER}"; public const int JWT_DURATION_HOUR = 12; diff --git a/src/GameIdeas/GameIdeas.Shared/Dto/IdDto.cs b/src/GameIdeas/GameIdeas.Shared/Dto/IdDto.cs new file mode 100644 index 0000000..5b05142 --- /dev/null +++ b/src/GameIdeas/GameIdeas.Shared/Dto/IdDto.cs @@ -0,0 +1,6 @@ +namespace GameIdeas.Shared.Dto; + +public class IdDto +{ + public string? Id { get; set; } +} diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs index ba76f3d..90a03ac 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Controllers/UserController.cs @@ -72,11 +72,12 @@ public class UserController( [Authorize(Roles = GlobalConstants.ADMINISTRATOR)] [HttpPost("Create")] - public async Task> CreateUser([FromBody] UserDto user) + public async Task> CreateUser([FromBody] UserDto user) { try { - return Created("/Create", await userWriteService.CreateUser(user)); + var id = new IdDto() { Id = await userWriteService.CreateUser(user) }; + return Created("/Create", id); } catch (Exception e) { @@ -87,11 +88,12 @@ public class UserController( [Authorize(Roles = GlobalConstants.ADMINISTRATOR)] [HttpPut("Update/{userId}")] - public async Task> UpdateUser(string userId, [FromBody] UserDto user) + public async Task> UpdateUser(string userId, [FromBody] UserDto user) { try { - return Created("/Update", await userWriteService.UpdateUser(userId, user)); + var id = new IdDto() { Id = await userWriteService.UpdateUser(userId, user) }; + return Created("/Update", id); } catch (Exception e) { @@ -102,11 +104,12 @@ public class UserController( [Authorize(Roles = GlobalConstants.ADMINISTRATOR)] [HttpDelete("Delete/{userId}")] - public async Task> DeleteUser(string userId) + public async Task> DeleteUser(string userId) { try { - return Created("/Delete", await userWriteService.DeleteUser(userId)); + var id = new IdDto() { Id = await userWriteService.DeleteUser(userId) }; + return Created("/Delete", id); } catch (Exception e) { diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250420160158_SeedDefaultUser.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250420160158_SeedDefaultUser.cs index 85a47ed..2a321a9 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250420160158_SeedDefaultUser.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Migrations/20250420160158_SeedDefaultUser.cs @@ -19,13 +19,13 @@ namespace GameIdeas.WebAPI.Migrations { GlobalConstants.ADMINISTRATOR_ID.ToString(), GlobalConstants.ADMINISTRATOR, - GlobalConstants.ADMINISTRATOR.Normalize(), + GlobalConstants.ADMINISTRATOR_NORMALIZED, Guid.NewGuid().ToString() }, { GlobalConstants.MEMBER_ID.ToString(), GlobalConstants.MEMBER, - GlobalConstants.MEMBER.Normalize(), + GlobalConstants.MEMBER_NORMALIZED, Guid.NewGuid().ToString() } }); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs index 1bd9aa5..2378126 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Program.cs @@ -59,6 +59,17 @@ services.AddAuthentication(options => }; }); +services.Configure(options => +{ + // Default Password settings. + options.Password.RequireDigit = false; + options.Password.RequireLowercase = false; + options.Password.RequireNonAlphanumeric = false; + options.Password.RequireUppercase = false; + options.Password.RequiredLength = 6; + options.Password.RequiredUniqueChars = 1; +}); + services.AddAuthorization(); services.AddSingleton(); diff --git a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs index 404a883..88c4524 100644 --- a/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs +++ b/src/GameIdeas/Server/GameIdeas.WebAPI/Services/Users/UserWriteService.cs @@ -29,7 +29,7 @@ public class UserWriteService( } else { - throw new UserInvalidException(string.Join("; ", result.Errors)); + throw new UserInvalidException(string.Join("; ", result.Errors.Select(e => $"{e.Code} {e.Description}"))); } return userToCreate.Id;