From 3219b3a14d2ba3b9822764b1f036f5bf8c40aa38 Mon Sep 17 00:00:00 2001 From: Robert van Diest Date: Wed, 25 Mar 2026 19:14:02 +0100 Subject: [PATCH] Allow deletion of admins --- src/backend/src/Randall.Api/Admin/AdminController.cs | 7 ++++--- .../Admin/DeleteUser/DeleteUserCommand.cs | 2 +- .../Admin/DeleteUser/DeleteUserHandler.cs | 9 ++++++++- src/backend/src/Randall.Domain/Users/IUserRepository.cs | 1 + .../Persistence/Users/UserRepository.cs | 3 +++ 5 files changed, 17 insertions(+), 5 deletions(-) diff --git a/src/backend/src/Randall.Api/Admin/AdminController.cs b/src/backend/src/Randall.Api/Admin/AdminController.cs index e9b13f0..566a208 100644 --- a/src/backend/src/Randall.Api/Admin/AdminController.cs +++ b/src/backend/src/Randall.Api/Admin/AdminController.cs @@ -19,8 +19,9 @@ public class AdminController( DeleteUserHandler deleteUserHandler, MakeAdminHandler makeAdminHandler) : ControllerBase { - private bool IsAdmin => - User.FindFirstValue("isAdmin") == "true"; + private bool IsAdmin => User.FindFirstValue("isAdmin") == "true"; + private Guid RequesterId => Guid.Parse(User.FindFirstValue(System.Security.Claims.ClaimTypes.NameIdentifier) + ?? User.FindFirstValue("sub")!); [HttpGet("users")] public async Task GetAllUsers(CancellationToken ct) @@ -62,7 +63,7 @@ public class AdminController( public async Task DeleteUser(Guid id, CancellationToken ct) { if (!IsAdmin) return Forbid(); - var result = await deleteUserHandler.HandleAsync(new DeleteUserCommand(id), ct); + var result = await deleteUserHandler.HandleAsync(new DeleteUserCommand(RequesterId, id), ct); if (!result.IsSuccess) return BadRequest(new ProblemDetails { Detail = result.Error }); return NoContent(); diff --git a/src/backend/src/Randall.Application/Admin/DeleteUser/DeleteUserCommand.cs b/src/backend/src/Randall.Application/Admin/DeleteUser/DeleteUserCommand.cs index d60c38d..dd84b93 100644 --- a/src/backend/src/Randall.Application/Admin/DeleteUser/DeleteUserCommand.cs +++ b/src/backend/src/Randall.Application/Admin/DeleteUser/DeleteUserCommand.cs @@ -1,3 +1,3 @@ namespace Randall.Application.Admin.DeleteUser; -public record DeleteUserCommand(Guid UserId); +public record DeleteUserCommand(Guid RequesterId, Guid UserId); diff --git a/src/backend/src/Randall.Application/Admin/DeleteUser/DeleteUserHandler.cs b/src/backend/src/Randall.Application/Admin/DeleteUser/DeleteUserHandler.cs index ccc5859..1e24dd9 100644 --- a/src/backend/src/Randall.Application/Admin/DeleteUser/DeleteUserHandler.cs +++ b/src/backend/src/Randall.Application/Admin/DeleteUser/DeleteUserHandler.cs @@ -11,8 +11,15 @@ public class DeleteUserHandler(IUserRepository userRepository) if (user is null) return Result.Failure("User not found."); + if (user.Id == command.RequesterId) + return Result.Failure("You cannot delete your own account."); + if (user.IsAdmin) - return Result.Failure("Admin users cannot be deleted."); + { + var adminCount = await userRepository.CountAdminsAsync(ct); + if (adminCount <= 1) + return Result.Failure("Cannot delete the last admin account."); + } userRepository.Delete(user); await userRepository.SaveChangesAsync(ct); diff --git a/src/backend/src/Randall.Domain/Users/IUserRepository.cs b/src/backend/src/Randall.Domain/Users/IUserRepository.cs index 7551f0e..d126c14 100644 --- a/src/backend/src/Randall.Domain/Users/IUserRepository.cs +++ b/src/backend/src/Randall.Domain/Users/IUserRepository.cs @@ -7,6 +7,7 @@ public interface IUserRepository Task ExistsByEmailAsync(string email, CancellationToken ct = default); Task> GetPendingAsync(CancellationToken ct = default); Task> GetAllAsync(CancellationToken ct = default); + Task CountAdminsAsync(CancellationToken ct = default); Task> GetAllNonAdminAsync(CancellationToken ct = default); Task AddAsync(User user, CancellationToken ct = default); void Delete(User user); diff --git a/src/backend/src/Randall.Infrastructure/Persistence/Users/UserRepository.cs b/src/backend/src/Randall.Infrastructure/Persistence/Users/UserRepository.cs index de47d8a..62c89ae 100644 --- a/src/backend/src/Randall.Infrastructure/Persistence/Users/UserRepository.cs +++ b/src/backend/src/Randall.Infrastructure/Persistence/Users/UserRepository.cs @@ -20,6 +20,9 @@ public class UserRepository(AppDbContext context) : IUserRepository public Task> GetAllAsync(CancellationToken ct = default) => context.Users.OrderBy(u => u.Name).ToListAsync(ct); + public Task CountAdminsAsync(CancellationToken ct = default) => + context.Users.CountAsync(u => u.IsAdmin); + public Task> GetAllNonAdminAsync(CancellationToken ct = default) => context.Users.Where(u => !u.IsAdmin).OrderBy(u => u.Name).ToListAsync(ct);