using AutoMapper; using Mws.Application.Auth; using Mws.Application.Common; using Mws.Application.Permissions; using Mws.Domain.Users; namespace Mws.Application.Accounts; public class AccountService(IUnitOfWork uow, IPasswordHasher passwordHasher, IPermissionService permissions, IMapper mapper) : IAccountService { private const string Screen = "accounts"; public async Task> GetAccountsAsync(Guid actorUserId, string? term, CancellationToken ct = default) { await permissions.EnsureAsync(actorUserId, Screen, PermissionAction.View, ct); var users = await uow.Users.SearchWithRoleAsync(term, null, ct); return mapper.Map>(users); } public async Task CreateAccountAsync(Guid actorUserId, CreateAccountRequest request, CancellationToken ct = default) { await permissions.EnsureAsync(actorUserId, Screen, PermissionAction.Create, ct); var username = request.Username.Trim(); if (string.IsNullOrWhiteSpace(username) || string.IsNullOrWhiteSpace(request.Password)) { throw new BadRequestException("Username and password are required"); } if (await uow.Users.ExistsByUsernameAsync(username, ct)) { throw new BadRequestException("Username already exists"); } var role = await uow.Roles.GetByIdAsync(request.RoleId, ct) ?? throw new BadRequestException("Role not found"); var now = DateTime.UtcNow; var user = new User { Id = Guid.NewGuid(), Username = username, PasswordHash = passwordHasher.Hash(request.Password), DisplayName = request.DisplayName.Trim(), RoleId = role.Id, IsActive = true, CreatedAt = now, UpdatedAt = now, }; uow.Users.Add(user); await uow.SaveChangesAsync(ct); user.Role = role; return mapper.Map(user); } public async Task UpdateAccountAsync(Guid actorUserId, Guid id, UpdateAccountRequest request, CancellationToken ct = default) { await permissions.EnsureAsync(actorUserId, Screen, PermissionAction.Edit, ct); var user = await uow.Users.GetByIdWithRoleAsync(id, ct) ?? throw new NotFoundException("Account not found"); var role = await uow.Roles.GetByIdAsync(request.RoleId, ct) ?? throw new BadRequestException("Role not found"); user.DisplayName = request.DisplayName.Trim(); user.RoleId = role.Id; user.Role = role; user.IsActive = request.IsActive; user.UpdatedAt = DateTime.UtcNow; await uow.SaveChangesAsync(ct); return mapper.Map(user); } public async Task DeleteAccountAsync(Guid actorUserId, Guid id, CancellationToken ct = default) { await permissions.EnsureAsync(actorUserId, Screen, PermissionAction.Delete, ct); var user = await uow.Users.GetByIdAsync(id, ct) ?? throw new NotFoundException("Account not found"); var soleOwnerProjectIds = await uow.Projects.GetOwnedProjectIdsAsync(id, ct); foreach (var projectId in soleOwnerProjectIds) { var ownerCount = await uow.Projects.CountOwnersAsync(projectId, ct); if (ownerCount <= 1) { throw new BadRequestException("Cannot delete an account that is the sole owner of a project"); } } uow.Users.Remove(user); await uow.SaveChangesAsync(ct); } public async Task ResetPasswordAsync(Guid actorUserId, Guid id, ResetPasswordRequest request, CancellationToken ct = default) { await permissions.EnsureAsync(actorUserId, Screen, PermissionAction.Edit, ct); if (string.IsNullOrWhiteSpace(request.NewPassword)) { throw new BadRequestException("New password is required"); } var user = await uow.Users.GetByIdAsync(id, ct) ?? throw new NotFoundException("Account not found"); user.PasswordHash = passwordHasher.Hash(request.NewPassword); user.UpdatedAt = DateTime.UtcNow; await uow.SaveChangesAsync(ct); } }