diff --git a/mws.api/Controllers/RolesController.cs b/mws.api/Controllers/RolesController.cs index a89e36e..1ca1ea5 100644 --- a/mws.api/Controllers/RolesController.cs +++ b/mws.api/Controllers/RolesController.cs @@ -1,3 +1,4 @@ +using MediatR; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Mws.Application.Permissions; @@ -8,41 +9,41 @@ namespace Mws.Api.Controllers; [ApiController] [Route("api/roles")] [Authorize] -public class RolesController(IRoleService roleService, IPermissionService permissions) : ControllerBase +public class RolesController(ISender sender, IPermissionService permissions) : ControllerBase { [HttpGet] public async Task>> GetAll(CancellationToken ct) { await permissions.EnsureAsync(User.GetUserId(), "roles", PermissionAction.View, ct); - return Ok(await roleService.GetRolesAsync(ct)); + return Ok(await sender.Send(new GetRolesQuery(), ct)); } [HttpGet("{id:guid}")] public async Task> Get(Guid id, CancellationToken ct) { await permissions.EnsureAsync(User.GetUserId(), "roles", PermissionAction.View, ct); - return Ok(await roleService.GetRoleAsync(id, ct)); + return Ok(await sender.Send(new GetRoleQuery(id), ct)); } [HttpPost] public async Task> Create([FromBody] SaveRoleRequest request, CancellationToken ct) { await permissions.EnsureAsync(User.GetUserId(), "roles", PermissionAction.Create, ct); - return Ok(await roleService.CreateRoleAsync(request, ct)); + return Ok(await sender.Send(new CreateRoleCommand(request), ct)); } [HttpPut("{id:guid}")] public async Task> Update(Guid id, [FromBody] SaveRoleRequest request, CancellationToken ct) { await permissions.EnsureAsync(User.GetUserId(), "roles", PermissionAction.Edit, ct); - return Ok(await roleService.UpdateRoleAsync(id, request, ct)); + return Ok(await sender.Send(new UpdateRoleCommand(id, request), ct)); } [HttpDelete("{id:guid}")] public async Task Delete(Guid id, CancellationToken ct) { await permissions.EnsureAsync(User.GetUserId(), "roles", PermissionAction.Delete, ct); - await roleService.DeleteRoleAsync(id, ct); + await sender.Send(new DeleteRoleCommand(id), ct); return NoContent(); } } diff --git a/mws.application/Roles/Commands/CreateRole.cs b/mws.application/Roles/Commands/CreateRole.cs new file mode 100644 index 0000000..4afaaa7 --- /dev/null +++ b/mws.application/Roles/Commands/CreateRole.cs @@ -0,0 +1,40 @@ +using AutoMapper; +using MediatR; +using Mws.Application.Common; +using Mws.Domain.Roles; + +namespace Mws.Application.Roles; + +public record CreateRoleCommand(SaveRoleRequest Request) : IRequest; + +public class CreateRoleHandler(IUnitOfWork uow, IMapper mapper) : IRequestHandler +{ + public async Task Handle(CreateRoleCommand command, CancellationToken ct) + { + var name = command.Request.Name.Trim(); + if (string.IsNullOrWhiteSpace(name)) + { + throw new BadRequestException("Role name is required"); + } + + if (await uow.Roles.ExistsByNameAsync(name, null, ct)) + { + throw new BadRequestException("Role name already exists"); + } + + var now = DateTime.UtcNow; + var role = new Role + { + Id = Guid.NewGuid(), + Name = name, + IsSystem = false, + CreatedAt = now, + UpdatedAt = now, + Permissions = RolePermissionBuilder.Build(command.Request.Permissions), + }; + + uow.Roles.Add(role); + await uow.SaveChangesAsync(ct); + return mapper.Map(role); + } +} diff --git a/mws.application/Roles/Commands/DeleteRole.cs b/mws.application/Roles/Commands/DeleteRole.cs new file mode 100644 index 0000000..e0990d2 --- /dev/null +++ b/mws.application/Roles/Commands/DeleteRole.cs @@ -0,0 +1,28 @@ +using MediatR; +using Mws.Application.Common; + +namespace Mws.Application.Roles; + +public record DeleteRoleCommand(Guid Id) : IRequest; + +public class DeleteRoleHandler(IUnitOfWork uow) : IRequestHandler +{ + public async Task Handle(DeleteRoleCommand command, CancellationToken ct) + { + var role = await uow.Roles.GetByIdAsync(command.Id, ct) + ?? throw new NotFoundException("Role not found"); + + if (role.IsSystem) + { + throw new BadRequestException("Cannot delete a system role"); + } + + if (await uow.Users.ExistsByRoleIdAsync(command.Id, ct)) + { + throw new BadRequestException("Cannot delete a role that is assigned to accounts"); + } + + uow.Roles.Remove(role); + await uow.SaveChangesAsync(ct); + } +} diff --git a/mws.application/Roles/Commands/UpdateRole.cs b/mws.application/Roles/Commands/UpdateRole.cs new file mode 100644 index 0000000..9c888f9 --- /dev/null +++ b/mws.application/Roles/Commands/UpdateRole.cs @@ -0,0 +1,40 @@ +using AutoMapper; +using MediatR; +using Mws.Application.Common; + +namespace Mws.Application.Roles; + +public record UpdateRoleCommand(Guid Id, SaveRoleRequest Request) : IRequest; + +public class UpdateRoleHandler(IUnitOfWork uow, IMapper mapper) : IRequestHandler +{ + public async Task Handle(UpdateRoleCommand command, CancellationToken ct) + { + var role = await uow.Roles.GetByIdWithPermissionsAsync(command.Id, ct) + ?? throw new NotFoundException("Role not found"); + + var name = command.Request.Name.Trim(); + if (string.IsNullOrWhiteSpace(name)) + { + throw new BadRequestException("Role name is required"); + } + + if (await uow.Roles.ExistsByNameAsync(name, command.Id, ct)) + { + throw new BadRequestException("Role name already exists"); + } + + role.Name = name; + role.UpdatedAt = DateTime.UtcNow; + + uow.Roles.RemovePermissions(role.Permissions.ToList()); + role.Permissions = RolePermissionBuilder.Build(command.Request.Permissions); + foreach (var p in role.Permissions) + { + p.RoleId = role.Id; + } + + await uow.SaveChangesAsync(ct); + return mapper.Map(role); + } +} diff --git a/mws.application/Roles/IRoleService.cs b/mws.application/Roles/IRoleService.cs deleted file mode 100644 index 8e21df9..0000000 --- a/mws.application/Roles/IRoleService.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace Mws.Application.Roles; - -public interface IRoleService -{ - Task> GetRolesAsync(CancellationToken ct = default); - Task GetRoleAsync(Guid id, CancellationToken ct = default); - Task CreateRoleAsync(SaveRoleRequest request, CancellationToken ct = default); - Task UpdateRoleAsync(Guid id, SaveRoleRequest request, CancellationToken ct = default); - Task DeleteRoleAsync(Guid id, CancellationToken ct = default); -} diff --git a/mws.application/Roles/Queries/GetRole.cs b/mws.application/Roles/Queries/GetRole.cs new file mode 100644 index 0000000..0f10169 --- /dev/null +++ b/mws.application/Roles/Queries/GetRole.cs @@ -0,0 +1,17 @@ +using AutoMapper; +using MediatR; +using Mws.Application.Common; + +namespace Mws.Application.Roles; + +public record GetRoleQuery(Guid Id) : IRequest; + +public class GetRoleHandler(IUnitOfWork uow, IMapper mapper) : IRequestHandler +{ + public async Task Handle(GetRoleQuery query, CancellationToken ct) + { + var role = await uow.Roles.GetByIdWithPermissionsAsync(query.Id, ct) + ?? throw new NotFoundException("Role not found"); + return mapper.Map(role); + } +} diff --git a/mws.application/Roles/Queries/GetRoles.cs b/mws.application/Roles/Queries/GetRoles.cs new file mode 100644 index 0000000..b4dc3b9 --- /dev/null +++ b/mws.application/Roles/Queries/GetRoles.cs @@ -0,0 +1,16 @@ +using AutoMapper; +using MediatR; +using Mws.Application.Common; + +namespace Mws.Application.Roles; + +public record GetRolesQuery : IRequest>; + +public class GetRolesHandler(IUnitOfWork uow, IMapper mapper) : IRequestHandler> +{ + public async Task> Handle(GetRolesQuery query, CancellationToken ct) + { + var roles = await uow.Roles.GetAllWithPermissionsAsync(ct); + return mapper.Map>(roles); + } +} diff --git a/mws.application/Roles/RolePermissionBuilder.cs b/mws.application/Roles/RolePermissionBuilder.cs new file mode 100644 index 0000000..ab9d3d3 --- /dev/null +++ b/mws.application/Roles/RolePermissionBuilder.cs @@ -0,0 +1,25 @@ +using Mws.Application.Permissions; +using Mws.Domain.Roles; + +namespace Mws.Application.Roles; + +internal static class RolePermissionBuilder +{ + public static List Build(List entries) + { + var byScreen = entries.Where(e => ScreenCatalog.Keys.Contains(e.Screen)).ToDictionary(e => e.Screen); + + return ScreenCatalog.Keys.Select(key => + { + byScreen.TryGetValue(key, out var entry); + return new RolePermission + { + Screen = key, + CanView = entry?.CanView ?? false, + CanCreate = entry?.CanCreate ?? false, + CanEdit = entry?.CanEdit ?? false, + CanDelete = entry?.CanDelete ?? false, + }; + }).ToList(); + } +} diff --git a/mws.application/Roles/RoleService.cs b/mws.application/Roles/RoleService.cs deleted file mode 100644 index 855af44..0000000 --- a/mws.application/Roles/RoleService.cs +++ /dev/null @@ -1,118 +0,0 @@ -using AutoMapper; -using Mws.Application.Common; -using Mws.Application.Permissions; -using Mws.Domain.Roles; - -namespace Mws.Application.Roles; - -public class RoleService(IUnitOfWork uow, IMapper mapper) : IRoleService -{ - public async Task> GetRolesAsync(CancellationToken ct = default) - { - var roles = await uow.Roles.GetAllWithPermissionsAsync(ct); - return mapper.Map>(roles); - } - - public async Task GetRoleAsync(Guid id, CancellationToken ct = default) - { - var role = await uow.Roles.GetByIdWithPermissionsAsync(id, ct) - ?? throw new NotFoundException("Role not found"); - return mapper.Map(role); - } - - public async Task CreateRoleAsync(SaveRoleRequest request, CancellationToken ct = default) - { - var name = request.Name.Trim(); - if (string.IsNullOrWhiteSpace(name)) - { - throw new BadRequestException("Role name is required"); - } - - if (await uow.Roles.ExistsByNameAsync(name, null, ct)) - { - throw new BadRequestException("Role name already exists"); - } - - var now = DateTime.UtcNow; - var role = new Role - { - Id = Guid.NewGuid(), - Name = name, - IsSystem = false, - CreatedAt = now, - UpdatedAt = now, - Permissions = BuildPermissions(request.Permissions), - }; - - uow.Roles.Add(role); - await uow.SaveChangesAsync(ct); - return mapper.Map(role); - } - - public async Task UpdateRoleAsync(Guid id, SaveRoleRequest request, CancellationToken ct = default) - { - var role = await uow.Roles.GetByIdWithPermissionsAsync(id, ct) - ?? throw new NotFoundException("Role not found"); - - var name = request.Name.Trim(); - if (string.IsNullOrWhiteSpace(name)) - { - throw new BadRequestException("Role name is required"); - } - - if (await uow.Roles.ExistsByNameAsync(name, id, ct)) - { - throw new BadRequestException("Role name already exists"); - } - - role.Name = name; - role.UpdatedAt = DateTime.UtcNow; - - uow.Roles.RemovePermissions(role.Permissions.ToList()); - role.Permissions = BuildPermissions(request.Permissions); - foreach (var p in role.Permissions) - { - p.RoleId = role.Id; - } - - await uow.SaveChangesAsync(ct); - return mapper.Map(role); - } - - public async Task DeleteRoleAsync(Guid id, CancellationToken ct = default) - { - var role = await uow.Roles.GetByIdAsync(id, ct) - ?? throw new NotFoundException("Role not found"); - - if (role.IsSystem) - { - throw new BadRequestException("Cannot delete a system role"); - } - - if (await uow.Users.ExistsByRoleIdAsync(id, ct)) - { - throw new BadRequestException("Cannot delete a role that is assigned to accounts"); - } - - uow.Roles.Remove(role); - await uow.SaveChangesAsync(ct); - } - - private static List BuildPermissions(List entries) - { - var byScreen = entries.Where(e => ScreenCatalog.Keys.Contains(e.Screen)).ToDictionary(e => e.Screen); - - return ScreenCatalog.Keys.Select(key => - { - byScreen.TryGetValue(key, out var entry); - return new RolePermission - { - Screen = key, - CanView = entry?.CanView ?? false, - CanCreate = entry?.CanCreate ?? false, - CanEdit = entry?.CanEdit ?? false, - CanDelete = entry?.CanDelete ?? false, - }; - }).ToList(); - } -} diff --git a/mws.infrastructure/DependencyInjection.cs b/mws.infrastructure/DependencyInjection.cs index 033aaa6..bfa56e9 100644 --- a/mws.infrastructure/DependencyInjection.cs +++ b/mws.infrastructure/DependencyInjection.cs @@ -6,7 +6,6 @@ using Mws.Application.Common; using Mws.Application.Documents; using Mws.Application.Permissions; using Mws.Application.Projects; -using Mws.Application.Roles; using Mws.Application.Tasks; using Mws.Infrastructure.Authentication; using Mws.Infrastructure.Persistence; @@ -40,7 +39,6 @@ public static class DependencyInjection services.AddScoped(); services.AddScoped(); - services.AddScoped(); services.AddScoped(); services.AddScoped(); services.AddScoped();