From cf25e4e1e67342283f06619df50e671914ddce55 Mon Sep 17 00:00:00 2001 From: Masu Baumgartner <68913099+Masu-Baumgartner@users.noreply.github.com> Date: Sun, 6 Oct 2024 01:19:23 +0200 Subject: [PATCH 1/2] Implemented admin users crud api --- .../Admin/Users/UsersController.cs | 73 +++++++++++++++++++ .../Moonlight.ApiServer.csproj | 2 +- Moonlight.ApiServer/Program.cs | 1 + Moonlight.Client/Moonlight.Client.csproj | 1 + .../Requests/Admin/Users/CreateUserRequest.cs | 19 +++++ .../Requests/Admin/Users/UpdateUserRequest.cs | 16 ++++ 6 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs create mode 100644 Moonlight.Shared/Http/Requests/Admin/Users/CreateUserRequest.cs create mode 100644 Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserRequest.cs diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs new file mode 100644 index 00000000..d0e5f949 --- /dev/null +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs @@ -0,0 +1,73 @@ +using Microsoft.AspNetCore.Mvc; +using MoonCore.Exceptions; +using MoonCore.Extended.Abstractions; +using MoonCore.Extended.Helpers; +using MoonCore.Models; +using Moonlight.ApiServer.Database.Entities; +using Moonlight.Shared.Http.Requests.Admin.Users; + +namespace Moonlight.ApiServer.Http.Controllers.Admin.Users; + +[ApiController] +[Route("api/admin/users")] +public class UsersController : Controller +{ + private readonly CrudHelper CrudHelper; + private readonly DatabaseRepository UserRepository; + + public UsersController(CrudHelper crudHelper, DatabaseRepository userRepository) + { + CrudHelper = crudHelper; + UserRepository = userRepository; + } + + [HttpGet] + public async Task> Get([FromQuery] int page, [FromQuery] int pageSize = 50) + => await CrudHelper.Get(page, pageSize); + + [HttpGet("{id}")] + public async Task GetSingle(int id) + => await CrudHelper.GetSingle(id); + + [HttpPost] + public async Task Create([FromBody] CreateUserRequest request) + { + // Reformat values + request.Username = request.Username.ToLower().Trim(); + request.Email = request.Email.ToLower().Trim(); + + // Check for users with the same values + if (UserRepository.Get().Any(x => x.Username == request.Username)) + throw new HttpApiException("A user with that username already exists", 400); + + if (UserRepository.Get().Any(x => x.Email == request.Email)) + throw new HttpApiException("A user with that email address already exists", 400); + + request.Password = HashHelper.Hash(request.Password); + + return await CrudHelper.Create(request); + } + + [HttpPatch("{id}")] + public async Task Update([FromRoute] int id, [FromBody] UpdateUserRequest request) + { + var user = await CrudHelper.GetSingle(id); + + // Reformat values + request.Username = request.Username.ToLower().Trim(); + request.Email = request.Email.ToLower().Trim(); + + // Check for users with the same values + if (UserRepository.Get().Any(x => x.Username == request.Username && x.Id != user.Id)) + throw new HttpApiException("A user with that username already exists", 400); + + if (UserRepository.Get().Any(x => x.Email == request.Email && x.Id != user.Id)) + throw new HttpApiException("A user with that email address already exists", 400); + + return await CrudHelper.Update(user, request); + } + + [HttpDelete("{id}")] + public async Task Delete([FromRoute] int id) + => await CrudHelper.Delete(id); +} \ No newline at end of file diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index cd92652b..6df60878 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -13,7 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Moonlight.ApiServer/Program.cs b/Moonlight.ApiServer/Program.cs index 3ca36c66..706b486b 100644 --- a/Moonlight.ApiServer/Program.cs +++ b/Moonlight.ApiServer/Program.cs @@ -86,6 +86,7 @@ var databaseHelper = new DatabaseHelper( builder.Services.AddSingleton(databaseHelper); builder.Services.AddScoped(typeof(DatabaseRepository<>)); +builder.Services.AddScoped(typeof(CrudHelper<>)); builder.Services.AddDbContext(); databaseHelper.AddDbContext(); diff --git a/Moonlight.Client/Moonlight.Client.csproj b/Moonlight.Client/Moonlight.Client.csproj index bc029be0..dd97f0d9 100644 --- a/Moonlight.Client/Moonlight.Client.csproj +++ b/Moonlight.Client/Moonlight.Client.csproj @@ -13,6 +13,7 @@ + diff --git a/Moonlight.Shared/Http/Requests/Admin/Users/CreateUserRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Users/CreateUserRequest.cs new file mode 100644 index 00000000..fed2dafd --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Admin/Users/CreateUserRequest.cs @@ -0,0 +1,19 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.Shared.Http.Requests.Admin.Users; + +public class CreateUserRequest +{ + [Required(ErrorMessage = "You need to provide an email address")] + [EmailAddress(ErrorMessage = "You need to provide a valid email address")] + public string Email { get; set; } + + [Required(ErrorMessage = "You need to provide a username")] + [RegularExpression("^[a-z][a-z0-9]*$", ErrorMessage = "Usernames can only contain lowercase characters and numbers and should not start with a number")] + public string Username { get; set; } + + [Required(ErrorMessage = "You need to provide a password")] + [MinLength(8, ErrorMessage = "Your password needs to be at least 8 characters long")] + [MaxLength(256, ErrorMessage = "Your password should not exceed the length of 256 characters")] + public string Password { get; set; } +} \ No newline at end of file diff --git a/Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserRequest.cs b/Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserRequest.cs new file mode 100644 index 00000000..9dd2d4b1 --- /dev/null +++ b/Moonlight.Shared/Http/Requests/Admin/Users/UpdateUserRequest.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace Moonlight.Shared.Http.Requests.Admin.Users; + +public class UpdateUserRequest +{ + [Required(ErrorMessage = "You need to provide an email address")] + [EmailAddress(ErrorMessage = "You need to provide a valid email address")] + public string Email { get; set; } + + [Required(ErrorMessage = "You need to provide a username")] + [RegularExpression("^[a-z][a-z0-9]*$", ErrorMessage = "Usernames can only contain lowercase characters and numbers and should not start with a number")] + public string Username { get; set; } + + public string? Password { get; set; } +} \ No newline at end of file From f48e5d4b19690f90f31f3bc2976736cd760be903 Mon Sep 17 00:00:00 2001 From: Masu Baumgartner <68913099+Masu-Baumgartner@users.noreply.github.com> Date: Sun, 6 Oct 2024 20:44:18 +0200 Subject: [PATCH 2/2] Implemented admin crud ui for users page. Fixed some smaller issues --- .../Admin/Users/UsersController.cs | 24 ++++--- .../Moonlight.ApiServer.csproj | 2 +- Moonlight.ApiServer/Program.cs | 2 +- Moonlight.Client/UI/Partials/AppHeader.razor | 2 +- Moonlight.Client/UI/Partials/AppSidebar.razor | 2 +- .../UI/Views/Admin/Users/Index.razor | 65 +++++++++++++++++++ .../Admin/Users/UserDetailResponse.cs | 8 +++ 7 files changed, 93 insertions(+), 12 deletions(-) create mode 100644 Moonlight.Client/UI/Views/Admin/Users/Index.razor create mode 100644 Moonlight.Shared/Http/Responses/Admin/Users/UserDetailResponse.cs diff --git a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs index d0e5f949..2e72b3af 100644 --- a/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs +++ b/Moonlight.ApiServer/Http/Controllers/Admin/Users/UsersController.cs @@ -5,6 +5,7 @@ using MoonCore.Extended.Helpers; using MoonCore.Models; using Moonlight.ApiServer.Database.Entities; using Moonlight.Shared.Http.Requests.Admin.Users; +using Moonlight.Shared.Http.Responses.Admin.Users; namespace Moonlight.ApiServer.Http.Controllers.Admin.Users; @@ -12,25 +13,25 @@ namespace Moonlight.ApiServer.Http.Controllers.Admin.Users; [Route("api/admin/users")] public class UsersController : Controller { - private readonly CrudHelper CrudHelper; + private readonly CrudHelper CrudHelper; private readonly DatabaseRepository UserRepository; - public UsersController(CrudHelper crudHelper, DatabaseRepository userRepository) + public UsersController(CrudHelper crudHelper, DatabaseRepository userRepository) { CrudHelper = crudHelper; UserRepository = userRepository; } [HttpGet] - public async Task> Get([FromQuery] int page, [FromQuery] int pageSize = 50) + public async Task> Get([FromQuery] int page, [FromQuery] int pageSize = 50) => await CrudHelper.Get(page, pageSize); [HttpGet("{id}")] - public async Task GetSingle(int id) + public async Task GetSingle(int id) => await CrudHelper.GetSingle(id); [HttpPost] - public async Task Create([FromBody] CreateUserRequest request) + public async Task Create([FromBody] CreateUserRequest request) { // Reformat values request.Username = request.Username.ToLower().Trim(); @@ -49,9 +50,9 @@ public class UsersController : Controller } [HttpPatch("{id}")] - public async Task Update([FromRoute] int id, [FromBody] UpdateUserRequest request) + public async Task Update([FromRoute] int id, [FromBody] UpdateUserRequest request) { - var user = await CrudHelper.GetSingle(id); + var user = await CrudHelper.GetSingleModel(id); // Reformat values request.Username = request.Username.ToLower().Trim(); @@ -63,7 +64,14 @@ public class UsersController : Controller if (UserRepository.Get().Any(x => x.Email == request.Email && x.Id != user.Id)) throw new HttpApiException("A user with that email address already exists", 400); - + + // Perform hashing the password if required + if (!string.IsNullOrEmpty(request.Password)) + { + request.Password = HashHelper.Hash(request.Password); + user.TokenValidTimestamp = DateTime.UtcNow; // This change will get applied by the crud helper + } + return await CrudHelper.Update(user, request); } diff --git a/Moonlight.ApiServer/Moonlight.ApiServer.csproj b/Moonlight.ApiServer/Moonlight.ApiServer.csproj index 6df60878..58691412 100644 --- a/Moonlight.ApiServer/Moonlight.ApiServer.csproj +++ b/Moonlight.ApiServer/Moonlight.ApiServer.csproj @@ -13,7 +13,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/Moonlight.ApiServer/Program.cs b/Moonlight.ApiServer/Program.cs index 706b486b..0263b092 100644 --- a/Moonlight.ApiServer/Program.cs +++ b/Moonlight.ApiServer/Program.cs @@ -86,7 +86,7 @@ var databaseHelper = new DatabaseHelper( builder.Services.AddSingleton(databaseHelper); builder.Services.AddScoped(typeof(DatabaseRepository<>)); -builder.Services.AddScoped(typeof(CrudHelper<>)); +builder.Services.AddScoped(typeof(CrudHelper<,>)); builder.Services.AddDbContext(); databaseHelper.AddDbContext(); diff --git a/Moonlight.Client/UI/Partials/AppHeader.razor b/Moonlight.Client/UI/Partials/AppHeader.razor index 0da86ce0..e8c275c3 100644 --- a/Moonlight.Client/UI/Partials/AppHeader.razor +++ b/Moonlight.Client/UI/Partials/AppHeader.razor @@ -62,7 +62,7 @@ From: "transform opacity-100 scale-100" To: "transform opacity-0 scale-95" --> -