using Microsoft.EntityFrameworkCore; public class UserDirectoryService : IUserDirectoryService { private readonly AppDbContext _db; public UserDirectoryService(AppDbContext db) => _db = db; public async Task> ListByRoleAsync(UserRole? role) { var query = _db.Users.AsNoTracking().Where(u => u.IsActive); if (role.HasValue) query = query.Where(u => u.Role == role.Value); return await query .OrderBy(u => u.FullName) .Select(u => new UserSummaryResponse( u.Id, u.Username, u.FullName, u.Role.ToDbString())) .ToListAsync(); } public async Task CreateAsync(CreateUserRequest request) { var exists = await _db.Users.AnyAsync(u => u.Username == request.Username); if (exists) throw new ConflictException( $"Username '{request.Username}' is already taken.", "USERNAME_TAKEN"); var user = new User { Id = Guid.NewGuid(), Username = request.Username, PasswordHash = BCrypt.Net.BCrypt.HashPassword(request.Password), FullName = request.FullName, Role = UserRoleExtensions.FromDbString(request.Role), IsActive = true, CreatedAt = DateTimeOffset.UtcNow }; _db.Users.Add(user); await _db.SaveChangesAsync(); return new UserSummaryResponse(user.Id, user.Username, user.FullName, user.Role.ToDbString()); } public async Task UpdateAsync(Guid userId, UpdateUserRequest request) { var user = await _db.Users.FindAsync(userId); if (user is null) throw new NotFoundException("User not found.", "USER_NOT_FOUND"); if (request.FullName is not null) user.FullName = request.FullName; if (request.Role is not null) user.Role = UserRoleExtensions.FromDbString(request.Role); if (request.IsActive.HasValue) user.IsActive = request.IsActive.Value; await _db.SaveChangesAsync(); return new UserSummaryResponse(user.Id, user.Username, user.FullName, user.Role.ToDbString()); } public async Task ResetPasswordAsync(Guid userId, string newPassword) { var user = await _db.Users.FindAsync(userId); if (user is null) throw new NotFoundException("User not found.", "USER_NOT_FOUND"); user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(newPassword); await _db.SaveChangesAsync(); } public async Task ChangePasswordAsync(Guid userId, string currentPassword, string newPassword) { var user = await _db.Users.FindAsync(userId); if (user is null) throw new NotFoundException("User not found.", "USER_NOT_FOUND"); if (!BCrypt.Net.BCrypt.Verify(currentPassword, user.PasswordHash)) throw new ValidationException( "Current password is incorrect.", "INVALID_PASSWORD"); user.PasswordHash = BCrypt.Net.BCrypt.HashPassword(newPassword); await _db.SaveChangesAsync(); } }