using Microsoft.AspNetCore.Identity; using Microsoft.EntityFrameworkCore; using Nuuru.Server.Auth; using Nuuru.Server.DTOs.Role; using Nuuru.Server.Models; using System.Security.Claims; namespace Nuuru.Server.Services { public interface IRoleService { Task CreateRoleAsync(string name, string? color, IEnumerable permissions); Task UpdateRoleAsync(Guid roleId, string name, string? color, IEnumerable permissions); Task DeleteRoleAsync(Guid roleId); Task> GetAllRolesAsync(); Task GetRoleAsync(Guid roleId); Task GetRoleByNameAsync(string name); Task AssignRoleToUserAsync(Guid userId, Guid roleId); Task RemoveRoleFromUserAsync(Guid userId, Guid roleId); Task> GetUserRolesAsync(Guid userId); } public class RoleService : IRoleService { private readonly RoleManager _roleManager; private readonly UserManager _userManager; private readonly ILogger _logger; public RoleService( RoleManager roleManager, UserManager userManager, ILogger logger) { _roleManager = roleManager; _userManager = userManager; _logger = logger; } public async Task CreateRoleAsync(string name, string? color, IEnumerable permissions) { if (string.IsNullOrWhiteSpace(name)) { _logger.LogWarning("Attempted to create role with empty name"); return null; } // Check if role already exists var existingRole = await _roleManager.FindByNameAsync(name); if (existingRole != null) { _logger.LogWarning("Role with name {RoleName} already exists", name); return null; } var role = new ApplicationRole { Name = name, Color = color }; var result = await _roleManager.CreateAsync(role); if (!result.Succeeded) { _logger.LogError("Failed to create role {RoleName}: {Errors}", name, string.Join(", ", result.Errors.Select(e => e.Description))); return null; } // Add permissions to role foreach (var permission in permissions) { var claim = new Claim(Permissions.ClaimType, permission); var claimResult = await _roleManager.AddClaimAsync(role, claim); if (!claimResult.Succeeded) { _logger.LogWarning("Failed to add permission {Permission} to role {RoleName}", permission, name); } } _logger.LogInformation("Created role {RoleName} with {PermissionCount} permissions", name, permissions.Count()); return await GetRoleAsync(role.Id); } public async Task UpdateRoleAsync(Guid roleId, string name, string? color, IEnumerable permissions) { var role = await _roleManager.FindByIdAsync(roleId.ToString()); if (role == null) { _logger.LogWarning("Attempted to update non-existent role: {RoleId}", roleId); return null; } // Update name and color if changed var needsUpdate = false; if (role.Name != name) { // Check if new name already exists var existingRole = await _roleManager.FindByNameAsync(name); if (existingRole != null && existingRole.Id != roleId) { _logger.LogWarning("Cannot rename role to {RoleName} - name already exists", name); return null; } role.Name = name; needsUpdate = true; } if (role.Color != color) { role.Color = color; needsUpdate = true; } if (needsUpdate) { var updateResult = await _roleManager.UpdateAsync(role); if (!updateResult.Succeeded) { _logger.LogError("Failed to update role: {Errors}", string.Join(", ", updateResult.Errors.Select(e => e.Description))); return null; } } // Update permissions - remove all existing and add new ones var existingClaims = await _roleManager.GetClaimsAsync(role); var permissionClaims = existingClaims .Where(c => c.Type == Permissions.ClaimType) .ToList(); foreach (var claim in permissionClaims) { await _roleManager.RemoveClaimAsync(role, claim); } foreach (var permission in permissions) { var claim = new Claim(Permissions.ClaimType, permission); await _roleManager.AddClaimAsync(role, claim); } _logger.LogInformation("Updated role {RoleName} with {PermissionCount} permissions", name, permissions.Count()); return await GetRoleAsync(roleId); } public async Task DeleteRoleAsync(Guid roleId) { var role = await _roleManager.FindByIdAsync(roleId.ToString()); if (role == null) { _logger.LogWarning("Attempted to delete non-existent role: {RoleId}", roleId); return false; } // Check if role has any users var usersInRole = await _userManager.GetUsersInRoleAsync(role.Name!); if (usersInRole.Any()) { _logger.LogWarning("Cannot delete role {RoleName} - {UserCount} users are assigned to it", role.Name, usersInRole.Count); return false; } var result = await _roleManager.DeleteAsync(role); if (result.Succeeded) { _logger.LogInformation("Deleted role {RoleName}", role.Name); return true; } _logger.LogError("Failed to delete role {RoleName}: {Errors}", role.Name, string.Join(", ", result.Errors.Select(e => e.Description))); return false; } public async Task> GetAllRolesAsync() { var roles = await _roleManager.Roles.ToListAsync(); var roleDtos = new List(); foreach (var role in roles) { var claims = await _roleManager.GetClaimsAsync(role); var permissions = claims .Where(c => c.Type == Permissions.ClaimType) .Select(c => c.Value) .ToList(); var usersInRole = await _userManager.GetUsersInRoleAsync(role.Name!); roleDtos.Add(new RoleDto { Id = role.Id, Name = role.Name!, Color = role.Color, Permissions = permissions, UserCount = usersInRole.Count }); } return roleDtos; } public async Task GetRoleAsync(Guid roleId) { var role = await _roleManager.FindByIdAsync(roleId.ToString()); if (role == null) { return null; } var claims = await _roleManager.GetClaimsAsync(role); var permissions = claims .Where(c => c.Type == Permissions.ClaimType) .Select(c => c.Value) .ToList(); var usersInRole = await _userManager.GetUsersInRoleAsync(role.Name!); return new RoleDto { Id = role.Id, Name = role.Name!, Color = role.Color, Permissions = permissions, UserCount = usersInRole.Count }; } public async Task GetRoleByNameAsync(string name) { var role = await _roleManager.FindByNameAsync(name); if (role == null) { return null; } return await GetRoleAsync(role.Id); } public async Task AssignRoleToUserAsync(Guid userId, Guid roleId) { var user = await _userManager.FindByIdAsync(userId.ToString()); if (user == null) { _logger.LogWarning("Attempted to assign role to non-existent user: {UserId}", userId); return false; } var role = await _roleManager.FindByIdAsync(roleId.ToString()); if (role == null) { _logger.LogWarning("Attempted to assign non-existent role: {RoleId}", roleId); return false; } // Check if user already has the role if (await _userManager.IsInRoleAsync(user, role.Name!)) { _logger.LogWarning("User {UserId} already has role {RoleName}", userId, role.Name); return false; } var result = await _userManager.AddToRoleAsync(user, role.Name!); if (result.Succeeded) { _logger.LogInformation("Assigned role {RoleName} to user {UserId}", role.Name, userId); return true; } _logger.LogError("Failed to assign role {RoleName} to user {UserId}: {Errors}", role.Name, userId, string.Join(", ", result.Errors.Select(e => e.Description))); return false; } public async Task RemoveRoleFromUserAsync(Guid userId, Guid roleId) { var user = await _userManager.FindByIdAsync(userId.ToString()); if (user == null) { _logger.LogWarning("Attempted to remove role from non-existent user: {UserId}", userId); return false; } var role = await _roleManager.FindByIdAsync(roleId.ToString()); if (role == null) { _logger.LogWarning("Attempted to remove non-existent role: {RoleId}", roleId); return false; } // Check if user has the role if (!await _userManager.IsInRoleAsync(user, role.Name!)) { _logger.LogWarning("User {UserId} does not have role {RoleName}", userId, role.Name); return false; } var result = await _userManager.RemoveFromRoleAsync(user, role.Name!); if (result.Succeeded) { _logger.LogInformation("Removed role {RoleName} from user {UserId}", role.Name, userId); return true; } _logger.LogError("Failed to remove role {RoleName} from user {UserId}: {Errors}", role.Name, userId, string.Join(", ", result.Errors.Select(e => e.Description))); return false; } public async Task> GetUserRolesAsync(Guid userId) { var user = await _userManager.FindByIdAsync(userId.ToString()); if (user == null) { return Enumerable.Empty(); } var roleNames = await _userManager.GetRolesAsync(user); var roleDtos = new List(); foreach (var roleName in roleNames) { var roleDto = await GetRoleByNameAsync(roleName); if (roleDto != null) { roleDtos.Add(roleDto); } } return roleDtos; } } }