From 0c06b19e6e1f678181df782f1f018b75a2b7dc5b Mon Sep 17 00:00:00 2001 From: Maxime BATISTA Date: Wed, 13 Dec 2023 17:31:29 +0100 Subject: [PATCH 1/5] update, remove and add new users into the server --- .gitignore | 2 +- AdminPanel.csproj | 5 ++- App.razor | 6 ++- Components/UserComponent.razor | 4 +- Models/Account.cs | 10 +++-- Pages/UserListPanel.razor | 82 ++++++++++++++++++++++++---------- Pages/UserListPanel.razor.cs | 72 +++++++++++++++++++++++------ Pages/UserListPanel.razor.css | 8 ++++ Program.cs | 19 +++++++- Services/HttpUsersService.cs | 64 ++++++++++++++++++++++++++ Services/IUsersService.cs | 15 +++++++ Services/UsersServiceStub.cs | 65 +++++++++++++++++++++++++++ Shared/MainLayout.razor | 4 ++ Shared/NavMenu.razor.cs | 9 ++++ _Imports.razor | 1 + wwwroot/index.html | 6 +++ 16 files changed, 324 insertions(+), 48 deletions(-) create mode 100644 Pages/UserListPanel.razor.css create mode 100644 Services/HttpUsersService.cs create mode 100644 Services/IUsersService.cs create mode 100644 Services/UsersServiceStub.cs create mode 100644 Shared/NavMenu.razor.cs diff --git a/.gitignore b/.gitignore index 8d9d548..de41017 100644 --- a/.gitignore +++ b/.gitignore @@ -60,7 +60,7 @@ dlldata.c *.pgc *.pgd *.rsp -*.sbr +*.sbr *.tlb *.tli *.tlh diff --git a/AdminPanel.csproj b/AdminPanel.csproj index 86d5318..d5bc199 100644 --- a/AdminPanel.csproj +++ b/AdminPanel.csproj @@ -7,7 +7,7 @@ service-worker-assets.js - + @@ -19,7 +19,8 @@ - + + diff --git a/App.razor b/App.razor index 623580d..9ad29dc 100644 --- a/App.razor +++ b/App.razor @@ -1,4 +1,8 @@ - + + + + + diff --git a/Components/UserComponent.razor b/Components/UserComponent.razor index 7f59922..b33f1f9 100644 --- a/Components/UserComponent.razor +++ b/Components/UserComponent.razor @@ -2,11 +2,11 @@ @if (@User.IsAdmin) { -

Administrator @User.UserName

+

Administrator @User.Name

} else { -

@User.UserName

+

@User.Name

}

email: @User.Email

id: @User.Id

diff --git a/Models/Account.cs b/Models/Account.cs index 0226997..47b163e 100644 --- a/Models/Account.cs +++ b/Models/Account.cs @@ -1,5 +1,9 @@ namespace AdminPanel.Models; -public record User(string UserName, string Email, int Id, bool IsAdmin) { - -} +public class User +{ + public required string Name { get; set; } + public required string Email { get; set; } + public uint Id { get; set; } + public bool IsAdmin { get; set; } +} \ No newline at end of file diff --git a/Pages/UserListPanel.razor b/Pages/UserListPanel.razor index d72c80e..3590a63 100644 --- a/Pages/UserListPanel.razor +++ b/Pages/UserListPanel.razor @@ -1,32 +1,66 @@ @page "/users" - - -@using AdminPanel.Components; -@using AdminPanel.Models; +@using AdminPanel.Models User Panel

User Panel

-
- @if (Users == null) - { -

Fetching Data...

- } - else - foreach (User user in Users) - { -
- - -
- see more -
+
+ + + + + + + + + Cancel + Add Account + + + +
+ + Add Account +
+
+ + Remove Selection +
+ - -

Ratio

- - -
- } + + + + + + + + + + + Is Administrator : + + + + + + + + + + + + + +
\ No newline at end of file diff --git a/Pages/UserListPanel.razor.cs b/Pages/UserListPanel.razor.cs index 243fb9f..562262f 100644 --- a/Pages/UserListPanel.razor.cs +++ b/Pages/UserListPanel.razor.cs @@ -1,34 +1,80 @@ -using System.Net.Http.Json; +using System.ComponentModel; using AdminPanel.Models; +using AdminPanel.Services; +using Microsoft.AspNetCore.Components; +using Microsoft.AspNetCore.Components.Web; +using MudBlazor; namespace AdminPanel.Pages { public partial class UserListPanel { - public List? Users { get; private set; } + [Inject] public ISnackbar Snackbar { get; private init; } + [Inject] public IUsersService Service { get; private init; } - public UserListPanel() + private HashSet SelectedItems { get; set; } = new(); + + private string? FormAccountName { get; set; } + private string? FormAccountEmail { get; set; } + private string? FormAccountPassword { get; set; } + private bool FormAccountIsAdmin { get; set; } + + public bool IsAddingUser { get; set; } + + + private async Task> LoadUsersFromServer(GridState state) { - HttpClient client = new() + var (count, users) = await Service.ListUsers((uint)(state.Page * state.PageSize), (uint)state.PageSize); + return new GridData { - BaseAddress = new Uri("http://localhost:8080") + TotalItems = (int)count, + Items = users }; - RetrieveUsers(client); } - public void OnAccessToUserSpaceClicked(User user) + + private async void OnAccountEdited(User user) { + Console.WriteLine(user.IsAdmin); + try + { + await Service.UpdateUser(user); + } + catch (Exception) + { + Snackbar.Add("Server responded with errors, your given input may be incorrect.\nIf you entered a new email, verify that the email is not used by another member."); + } } - private async void RetrieveUsers(HttpClient client) + private async void ConfirmAddAccount(MouseEventArgs e) { - using HttpResponseMessage response = await client.GetAsync("/api/admin/list-users?start=0&n=100"); + // We no longer add an account if it is confirmed + IsAddingUser = false; - response.EnsureSuccessStatusCode(); + try + { + await Service.AddUser(FormAccountName!, FormAccountEmail!, FormAccountPassword!, + FormAccountIsAdmin); + StateHasChanged(); + } + catch (Exception) + { + Snackbar.Add("Server responded with errors, your given input may be incorrect."); + } + } - Users = (await response.Content.ReadFromJsonAsync>())!; - StateHasChanged(); - Console.WriteLine(Users); + private async void RemoveSelection(MouseEventArgs e) + { + var items = SelectedItems.ToList().ConvertAll(u => u.Id); + Console.WriteLine(items.Count); + try + { + await Service.RemoveUsers(items); + } + catch (Exception) + { + Snackbar.Add("Server responded with errors"); + } } } } \ No newline at end of file diff --git a/Pages/UserListPanel.razor.css b/Pages/UserListPanel.razor.css new file mode 100644 index 0000000..d29687f --- /dev/null +++ b/Pages/UserListPanel.razor.css @@ -0,0 +1,8 @@ + + +#add-account-form { + background-color: white; + visibility: hidden; + color: #ffba00; +} + diff --git a/Program.cs b/Program.cs index d4e1597..e633b92 100644 --- a/Program.cs +++ b/Program.cs @@ -1,11 +1,26 @@ using AdminPanel; +using AdminPanel.Services; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +using MudBlazor.Services; var builder = WebAssemblyHostBuilder.CreateDefault(args); builder.RootComponents.Add("#app"); builder.RootComponents.Add("head::after"); -builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Services + .AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -await builder.Build().RunAsync(); +var client = new HttpClient +{ + BaseAddress = new Uri("http://localhost:8080") + +}; + +builder.Services.AddScoped(sp => new HttpUsersService(client)); +//builder.Services.AddScoped(); + +builder.Services.AddMudServices(); + + +await builder.Build().RunAsync(); \ No newline at end of file diff --git a/Services/HttpUsersService.cs b/Services/HttpUsersService.cs new file mode 100644 index 0000000..cf3bccf --- /dev/null +++ b/Services/HttpUsersService.cs @@ -0,0 +1,64 @@ +using System.Net.Http.Json; +using AdminPanel.Models; + +namespace AdminPanel.Services; + +public class HttpUsersService : IUsersService +{ + private HttpClient Client { get; } + + public HttpUsersService(HttpClient client) + { + this.Client = client; + } + + private record ListUsersResponse(List Users, uint TotalCount); + + public async Task<(uint, List)> ListUsers(uint from, uint len) + { + var httpResponse = await Client.GetAsync($"/api/admin/list-users?start={from}&n={len}"); + httpResponse.EnsureSuccessStatusCode(); + + var response = await httpResponse.Content.ReadFromJsonAsync() + ?? throw new Exception("Received non-parseable json from server"); + + return (response.TotalCount, response.Users); + } + + private record AddUserRequest(string Username, string Email, string Password, bool IsAdmin); + + private record AddUserResponse(uint Id); + + public async Task AddUser(string username, string email, string password, bool isAdmin) + { + var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/add", new AddUserRequest(username, email, password, isAdmin)); + httpResponse.EnsureSuccessStatusCode(); + + var response = await httpResponse.Content.ReadFromJsonAsync() + ?? throw new Exception("Received non-parseable json from server"); + + return new User + { + Name = username, + Email = email, + IsAdmin = isAdmin, + Id = response.Id + }; + } + + private record RemoveUsersRequest(uint[] Identifiers); + + public async Task RemoveUsers(IEnumerable userIds) + { + var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/remove-all", new RemoveUsersRequest(userIds.ToArray())); + httpResponse.EnsureSuccessStatusCode(); + } + + private record UpdateUserRequest(string Email, string Username, bool IsAdmin); + + public async Task UpdateUser(User user) + { + var httpResponse = await Client.PostAsJsonAsync($"/api/admin/user/{user.Id}/update", new UpdateUserRequest(user.Email, user.Name, user.IsAdmin)); + httpResponse.EnsureSuccessStatusCode(); + } +} \ No newline at end of file diff --git a/Services/IUsersService.cs b/Services/IUsersService.cs new file mode 100644 index 0000000..8611121 --- /dev/null +++ b/Services/IUsersService.cs @@ -0,0 +1,15 @@ +using AdminPanel.Models; + +namespace AdminPanel.Services +{ + public interface IUsersService + { + public Task<(uint, List)> ListUsers(uint from, uint len); + + public Task AddUser(string username, string email, string password, bool isAdmin); + + public Task RemoveUsers(IEnumerable userId); + + public Task UpdateUser(User user); + } +} diff --git a/Services/UsersServiceStub.cs b/Services/UsersServiceStub.cs new file mode 100644 index 0000000..59958e8 --- /dev/null +++ b/Services/UsersServiceStub.cs @@ -0,0 +1,65 @@ +using AdminPanel.Models; +using System.Runtime.CompilerServices; + +namespace AdminPanel.Services +{ + public class UsersServiceStub : IUsersService + { + private Dictionary Users { get; } = new[] + { + new User + { + Name = "Mathis", + Email = "mathis@gmail.com", + Id = 0, + IsAdmin = true + }, + new User + { + Name = "Maeva", + Email = "maeva@gmail.com", + Id = 1, + IsAdmin = false + }, + }.ToDictionary(u => u.Id); + + public Task AddUser(string username, string email, string password, bool isAdmin) + { + User user = new User + { + Email = email, + Name = username, + IsAdmin = isAdmin, + Id = (uint) Users.Count + }; + Users[user.Id] = user; + return Task.FromResult(user); + } + + public async Task<(uint, List)> ListUsers(uint from, uint len) + { + //simulate a 1sec long request + await Task.Delay(1000); + var slice = Users.Values + .ToList() + .GetRange((int)from, (int)((from + len > Users.Count) ? Users.Count - from : len)); + + return ((uint)Users.Count, slice); + } + + public Task RemoveUsers(IEnumerable userIds) + { + foreach (var id in userIds) + { + Users.Remove(id); + } + return Task.CompletedTask; + } + + public Task UpdateUser(User user) + { + Users[user.Id] = user; + return Task.CompletedTask; + } + } +} \ No newline at end of file diff --git a/Shared/MainLayout.razor b/Shared/MainLayout.razor index 0b3cad5..dc88df5 100644 --- a/Shared/MainLayout.razor +++ b/Shared/MainLayout.razor @@ -1,4 +1,8 @@ @inherits LayoutComponentBase +@using MudBlazor + + +
- - + + CommittedItemChanges="OnAccountUpdated"> diff --git a/Pages/UserListPanel.razor.cs b/Pages/UserListPanel.razor.cs index 562262f..7d4904d 100644 --- a/Pages/UserListPanel.razor.cs +++ b/Pages/UserListPanel.razor.cs @@ -1,5 +1,4 @@ -using System.ComponentModel; -using AdminPanel.Models; +using AdminPanel.Models; using AdminPanel.Services; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; @@ -12,6 +11,9 @@ namespace AdminPanel.Pages [Inject] public ISnackbar Snackbar { get; private init; } [Inject] public IUsersService Service { get; private init; } + private MudDataGrid Grid { get; set; } + + private HashSet SelectedItems { get; set; } = new(); private string? FormAccountName { get; set; } @@ -33,7 +35,7 @@ namespace AdminPanel.Pages } - private async void OnAccountEdited(User user) + private async void OnAccountUpdated(User user) { Console.WriteLine(user.IsAdmin); try @@ -42,7 +44,8 @@ namespace AdminPanel.Pages } catch (Exception) { - Snackbar.Add("Server responded with errors, your given input may be incorrect.\nIf you entered a new email, verify that the email is not used by another member."); + Snackbar.Add( + "Server responded with errors, your given input may be incorrect.\nIf you entered a new email, verify that the email is not used by another member."); } } @@ -55,7 +58,7 @@ namespace AdminPanel.Pages { await Service.AddUser(FormAccountName!, FormAccountEmail!, FormAccountPassword!, FormAccountIsAdmin); - StateHasChanged(); + await Grid.ReloadServerData(); } catch (Exception) { @@ -70,11 +73,17 @@ namespace AdminPanel.Pages try { await Service.RemoveUsers(items); + await Grid.ReloadServerData(); } catch (Exception) { Snackbar.Add("Server responded with errors"); } } + + private Func VerifyLength(uint min, uint max) + { + return s => (s.Length >= min && s.Length <= max) ? null : $"length is incorrect (must be between {min} and {max})"; + } } } \ No newline at end of file From f871249669617ca3fc75e2af84156f06ca132dea Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 17 Jan 2024 10:13:02 +0100 Subject: [PATCH 3/5] add loggers --- Models/Account.cs | 5 ++++ Pages/UserListPanel.razor | 53 +++++++++++++++++++++++------------- Pages/UserListPanel.razor.cs | 33 ++++++++++++++++------ Program.cs | 4 ++- Services/HttpUsersService.cs | 4 +-- Services/IUsersService.cs | 2 +- Services/UsersServiceStub.cs | 6 ++-- 7 files changed, 72 insertions(+), 35 deletions(-) diff --git a/Models/Account.cs b/Models/Account.cs index 47b163e..4c02b81 100644 --- a/Models/Account.cs +++ b/Models/Account.cs @@ -6,4 +6,9 @@ public class User public required string Email { get; set; } public uint Id { get; set; } public bool IsAdmin { get; set; } + + public override string ToString() + { + return $"{nameof(Name)}: {Name}, {nameof(Email)}: {Email}, {nameof(Id)}: {Id}, {nameof(IsAdmin)}: {IsAdmin}"; + } } \ No newline at end of file diff --git a/Pages/UserListPanel.razor b/Pages/UserListPanel.razor index b03faff..51834a3 100644 --- a/Pages/UserListPanel.razor +++ b/Pages/UserListPanel.razor @@ -7,22 +7,22 @@

User Panel

- + - - + @@ -33,15 +33,7 @@ -
- - Add Account -
-
- - Remove Selection -
- + + + +
+ + Add Account +
+
+ + Remove Selection +
+ + Accounts + + +
+ diff --git a/Pages/UserListPanel.razor.cs b/Pages/UserListPanel.razor.cs index 7d4904d..c9ebb9b 100644 --- a/Pages/UserListPanel.razor.cs +++ b/Pages/UserListPanel.razor.cs @@ -8,11 +8,15 @@ namespace AdminPanel.Pages { public partial class UserListPanel { - [Inject] public ISnackbar Snackbar { get; private init; } - [Inject] public IUsersService Service { get; private init; } + [Inject] private ISnackbar Snackbar { get; init; } + [Inject] private IUsersService Service { get; init; } + [Inject] private ILogger Logger { get; init; } private MudDataGrid Grid { get; set; } + + private string? SearchString { get; set; } + private HashSet SelectedItems { get; set; } = new(); @@ -26,7 +30,8 @@ namespace AdminPanel.Pages private async Task> LoadUsersFromServer(GridState state) { - var (count, users) = await Service.ListUsers((uint)(state.Page * state.PageSize), (uint)state.PageSize); + Logger.LogDebug($"Loading users from server, state = {state} searchString = {SearchString}"); + var (count, users) = await Service.ListUsers((uint)(state.Page * state.PageSize), (uint)state.PageSize, SearchString); return new GridData { TotalItems = (int)count, @@ -37,7 +42,7 @@ namespace AdminPanel.Pages private async void OnAccountUpdated(User user) { - Console.WriteLine(user.IsAdmin); + Logger.LogDebug($"Account updated : {user}"); try { await Service.UpdateUser(user); @@ -56,8 +61,9 @@ namespace AdminPanel.Pages try { - await Service.AddUser(FormAccountName!, FormAccountEmail!, FormAccountPassword!, + var user = await Service.AddUser(FormAccountName!, FormAccountEmail!, FormAccountPassword!, FormAccountIsAdmin); + Logger.LogDebug($"Added user : {user}"); await Grid.ReloadServerData(); } catch (Exception) @@ -68,11 +74,11 @@ namespace AdminPanel.Pages private async void RemoveSelection(MouseEventArgs e) { - var items = SelectedItems.ToList().ConvertAll(u => u.Id); - Console.WriteLine(items.Count); + var users = SelectedItems.ToList().ConvertAll(u => u.Id); + Logger.LogDebug($"Removing users : {users}"); try { - await Service.RemoveUsers(items); + await Service.RemoveUsers(users); await Grid.ReloadServerData(); } catch (Exception) @@ -83,7 +89,16 @@ namespace AdminPanel.Pages private Func VerifyLength(uint min, uint max) { - return s => (s.Length >= min && s.Length <= max) ? null : $"length is incorrect (must be between {min} and {max})"; + return s => s.Length >= min && s.Length <= max ? null : $"length is incorrect (must be between {min} and {max})"; + } + + private void ValidateSearch(KeyboardEventArgs e) + { + if (e.Key == "Enter") + { + Grid.ReloadServerData(); + Logger.LogDebug($"Searching for {SearchString}"); + } } } } \ No newline at end of file diff --git a/Program.cs b/Program.cs index e633b92..b8036fc 100644 --- a/Program.cs +++ b/Program.cs @@ -1,4 +1,5 @@ using AdminPanel; +using AdminPanel.Pages; using AdminPanel.Services; using Microsoft.AspNetCore.Components.Web; using Microsoft.AspNetCore.Components.WebAssembly.Hosting; @@ -11,10 +12,11 @@ builder.RootComponents.Add("head::after"); builder.Services .AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); +builder.Logging.SetMinimumLevel(LogLevel.Debug); + var client = new HttpClient { BaseAddress = new Uri("http://localhost:8080") - }; builder.Services.AddScoped(sp => new HttpUsersService(client)); diff --git a/Services/HttpUsersService.cs b/Services/HttpUsersService.cs index cf3bccf..f7621f7 100644 --- a/Services/HttpUsersService.cs +++ b/Services/HttpUsersService.cs @@ -14,9 +14,9 @@ public class HttpUsersService : IUsersService private record ListUsersResponse(List Users, uint TotalCount); - public async Task<(uint, List)> ListUsers(uint from, uint len) + public async Task<(uint, List)> ListUsers(uint from, uint len, string? searchString = null) { - var httpResponse = await Client.GetAsync($"/api/admin/list-users?start={from}&n={len}"); + var httpResponse = await Client.GetAsync($"/api/admin/list-users?start={from}&n={len}&search={searchString ?? ""}"); httpResponse.EnsureSuccessStatusCode(); var response = await httpResponse.Content.ReadFromJsonAsync() diff --git a/Services/IUsersService.cs b/Services/IUsersService.cs index 8611121..c523caf 100644 --- a/Services/IUsersService.cs +++ b/Services/IUsersService.cs @@ -4,7 +4,7 @@ namespace AdminPanel.Services { public interface IUsersService { - public Task<(uint, List)> ListUsers(uint from, uint len); + public Task<(uint, List)> ListUsers(uint from, uint len, string? searchString = null); public Task AddUser(string username, string email, string password, bool isAdmin); diff --git a/Services/UsersServiceStub.cs b/Services/UsersServiceStub.cs index 59958e8..d30845e 100644 --- a/Services/UsersServiceStub.cs +++ b/Services/UsersServiceStub.cs @@ -1,5 +1,4 @@ using AdminPanel.Models; -using System.Runtime.CompilerServices; namespace AdminPanel.Services { @@ -36,13 +35,14 @@ namespace AdminPanel.Services return Task.FromResult(user); } - public async Task<(uint, List)> ListUsers(uint from, uint len) + public async Task<(uint, List)> ListUsers(uint from, uint len, string? searchString = null) { //simulate a 1sec long request await Task.Delay(1000); var slice = Users.Values .ToList() - .GetRange((int)from, (int)((from + len > Users.Count) ? Users.Count - from : len)); + .FindAll(a => searchString == null || a.Name.Contains(searchString) || a.Email.Contains(searchString)) + .GetRange((int)from, (int)(from + len > Users.Count ? Users.Count - from : len)); return ((uint)Users.Count, slice); } From 182911eac1be5944b74268220b00387b94162817 Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 17 Jan 2024 16:09:40 +0100 Subject: [PATCH 4/5] remove empty NavMenu.razor.cs --- Shared/NavMenu.razor.cs | 9 --------- 1 file changed, 9 deletions(-) delete mode 100644 Shared/NavMenu.razor.cs diff --git a/Shared/NavMenu.razor.cs b/Shared/NavMenu.razor.cs deleted file mode 100644 index 433f552..0000000 --- a/Shared/NavMenu.razor.cs +++ /dev/null @@ -1,9 +0,0 @@ -namespace AdminPanel.Shared -{ - public partial class NavMenu - { - - - - } -} From a05f2486b9fd59edd122bbde06212e4da472878f Mon Sep 17 00:00:00 2001 From: "maxime.batista" Date: Wed, 17 Jan 2024 17:37:27 +0100 Subject: [PATCH 5/5] Show server errors --- Pages/UserListPanel.razor.cs | 53 ++++++++++++++++++--------- Program.cs | 2 +- Properties/launchSettings.json | 8 ++-- Services/HttpUsersService.cs | 67 ++++++++++++++++++++++++---------- Services/ServiceException.cs | 18 +++++++++ 5 files changed, 106 insertions(+), 42 deletions(-) create mode 100644 Services/ServiceException.cs diff --git a/Pages/UserListPanel.razor.cs b/Pages/UserListPanel.razor.cs index c9ebb9b..f4eff47 100644 --- a/Pages/UserListPanel.razor.cs +++ b/Pages/UserListPanel.razor.cs @@ -1,7 +1,9 @@ -using AdminPanel.Models; +using System.Text; +using AdminPanel.Models; using AdminPanel.Services; using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Web; +using Microsoft.Extensions.Primitives; using MudBlazor; namespace AdminPanel.Pages @@ -13,10 +15,10 @@ namespace AdminPanel.Pages [Inject] private ILogger Logger { get; init; } private MudDataGrid Grid { get; set; } - - + + private string? SearchString { get; set; } - + private HashSet SelectedItems { get; set; } = new(); @@ -31,7 +33,8 @@ namespace AdminPanel.Pages private async Task> LoadUsersFromServer(GridState state) { Logger.LogDebug($"Loading users from server, state = {state} searchString = {SearchString}"); - var (count, users) = await Service.ListUsers((uint)(state.Page * state.PageSize), (uint)state.PageSize, SearchString); + var (count, users) = + await Service.ListUsers((uint)(state.Page * state.PageSize), (uint)state.PageSize, SearchString); return new GridData { TotalItems = (int)count, @@ -47,10 +50,9 @@ namespace AdminPanel.Pages { await Service.UpdateUser(user); } - catch (Exception) + catch (ServiceException err) { - Snackbar.Add( - "Server responded with errors, your given input may be incorrect.\nIf you entered a new email, verify that the email is not used by another member."); + ShowErrors(err); } } @@ -66,9 +68,25 @@ namespace AdminPanel.Pages Logger.LogDebug($"Added user : {user}"); await Grid.ReloadServerData(); } - catch (Exception) + catch (ServiceException err) { - Snackbar.Add("Server responded with errors, your given input may be incorrect."); + ShowErrors(err); + } + } + + private void ShowErrors(ServiceException e) + { + foreach (var erronedArgument in e.ArgumentMessages) + { + var sb = new StringBuilder(erronedArgument.Key); + sb.Append(" :"); + + foreach (var message in erronedArgument.Value) + { + sb.Append("\n\t-"); + sb.Append(message); + } + Snackbar.Add(sb.ToString()); } } @@ -81,24 +99,23 @@ namespace AdminPanel.Pages await Service.RemoveUsers(users); await Grid.ReloadServerData(); } - catch (Exception) + catch (ServiceException err) { - Snackbar.Add("Server responded with errors"); + ShowErrors(err); } } private Func VerifyLength(uint min, uint max) { - return s => s.Length >= min && s.Length <= max ? null : $"length is incorrect (must be between {min} and {max})"; + return s => s.Length >= min && s.Length <= max + ? null + : $"length is incorrect (must be between {min} and {max})"; } private void ValidateSearch(KeyboardEventArgs e) { - if (e.Key == "Enter") - { - Grid.ReloadServerData(); - Logger.LogDebug($"Searching for {SearchString}"); - } + Grid.ReloadServerData(); + Logger.LogDebug($"Searching for {SearchString}"); } } } \ No newline at end of file diff --git a/Program.cs b/Program.cs index b8036fc..91dd8e2 100644 --- a/Program.cs +++ b/Program.cs @@ -12,7 +12,7 @@ builder.RootComponents.Add("head::after"); builder.Services .AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); -builder.Logging.SetMinimumLevel(LogLevel.Debug); +// builder.Logging.SetMinimumLevel(LogLevel.Debug); var client = new HttpClient { diff --git a/Properties/launchSettings.json b/Properties/launchSettings.json index db1c6be..d5df975 100644 --- a/Properties/launchSettings.json +++ b/Properties/launchSettings.json @@ -2,8 +2,8 @@ "iisSettings": { "windowsAuthentication": false, "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:18284", + "iisExpress": { + "applicationUrl": "http://localhost:18284", "sslPort": 0 } }, @@ -12,8 +12,8 @@ "commandName": "Project", "dotnetRunMessages": true, "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5081", + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5081", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } diff --git a/Services/HttpUsersService.cs b/Services/HttpUsersService.cs index f7621f7..824fefd 100644 --- a/Services/HttpUsersService.cs +++ b/Services/HttpUsersService.cs @@ -1,3 +1,5 @@ +using System.Diagnostics; +using System.Net; using System.Net.Http.Json; using AdminPanel.Models; @@ -12,30 +14,54 @@ public class HttpUsersService : IUsersService this.Client = client; } - private record ListUsersResponse(List Users, uint TotalCount); + private record ServerErrorMessageDto(string? Field, string? Error, string Message); - public async Task<(uint, List)> ListUsers(uint from, uint len, string? searchString = null) + private async Task EnsureResponseIsOk(HttpResponseMessage response) { - var httpResponse = await Client.GetAsync($"/api/admin/list-users?start={from}&n={len}&search={searchString ?? ""}"); - httpResponse.EnsureSuccessStatusCode(); + + if (response.StatusCode == HttpStatusCode.OK) + { + return; + } - var response = await httpResponse.Content.ReadFromJsonAsync() - ?? throw new Exception("Received non-parseable json from server"); + var content = await response.Content.ReadFromJsonAsync(); + var messages = content! + .GroupBy(e => e.Field ?? e.Error!) + .ToDictionary( + g => g.Key, + g => g.ToList().ConvertAll(e => e.Message) + ); + + throw new ServiceException("Server refused request", messages); + } + + private record ListUsersResponseDto(List Users, uint TotalCount); + + public async Task<(uint, List)> ListUsers(uint from, uint len, string? searchString = null) + { + var httpResponse = + await Client.GetAsync($"/api/admin/list-users?start={from}&n={len}&search={searchString ?? ""}"); + await EnsureResponseIsOk(httpResponse); + + var response = await httpResponse.Content.ReadFromJsonAsync() + ?? throw new Exception("Received non-parseable json from server"); return (response.TotalCount, response.Users); } - private record AddUserRequest(string Username, string Email, string Password, bool IsAdmin); + private record AddUserRequestDto(string Username, string Email, string Password, bool IsAdmin); + + private record AddUserResponseDto(uint Id); - private record AddUserResponse(uint Id); - public async Task AddUser(string username, string email, string password, bool isAdmin) { - var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/add", new AddUserRequest(username, email, password, isAdmin)); - httpResponse.EnsureSuccessStatusCode(); + var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/add", + new AddUserRequestDto(username, email, password, isAdmin)); + + await EnsureResponseIsOk(httpResponse); - var response = await httpResponse.Content.ReadFromJsonAsync() - ?? throw new Exception("Received non-parseable json from server"); + var response = await httpResponse.Content.ReadFromJsonAsync() + ?? throw new Exception("Received non-parseable json from server"); return new User { @@ -46,19 +72,22 @@ public class HttpUsersService : IUsersService }; } - private record RemoveUsersRequest(uint[] Identifiers); + private record RemoveUsersRequestDto(uint[] Identifiers); public async Task RemoveUsers(IEnumerable userIds) { - var httpResponse = await Client.PostAsJsonAsync("/api/admin/user/remove-all", new RemoveUsersRequest(userIds.ToArray())); - httpResponse.EnsureSuccessStatusCode(); + var httpResponse = + await Client.PostAsJsonAsync("/api/admin/user/remove-all", new RemoveUsersRequestDto(userIds.ToArray())); + + await EnsureResponseIsOk(httpResponse); } - private record UpdateUserRequest(string Email, string Username, bool IsAdmin); + private record UpdateUserRequestDto(string Email, string Username, bool IsAdmin); public async Task UpdateUser(User user) { - var httpResponse = await Client.PostAsJsonAsync($"/api/admin/user/{user.Id}/update", new UpdateUserRequest(user.Email, user.Name, user.IsAdmin)); - httpResponse.EnsureSuccessStatusCode(); + var httpResponse = await Client.PostAsJsonAsync($"/api/admin/user/{user.Id}/update", + new UpdateUserRequestDto(user.Email, user.Name, user.IsAdmin)); + await EnsureResponseIsOk(httpResponse); } } \ No newline at end of file diff --git a/Services/ServiceException.cs b/Services/ServiceException.cs new file mode 100644 index 0000000..8dbcb00 --- /dev/null +++ b/Services/ServiceException.cs @@ -0,0 +1,18 @@ +namespace AdminPanel.Services; + +class ServiceException : Exception +{ + public Dictionary> ArgumentMessages { get; init; } + + + + public ServiceException(string? message, Dictionary> arguments) : base(message) + { + ArgumentMessages = arguments; + } + + public ServiceException(string? message, Dictionary> arguments, Exception? innerException) : base(message, innerException) + { + ArgumentMessages = arguments; + } +} \ No newline at end of file