using AuthHex.Infra.UoW; using AuthHex.Interfaces; using AuthHex.Models; using Core_Archi.Helpers; using Microsoft.AspNetCore.Http; namespace AuthHex.Services.RoleManager; /// /// Role use-cases for AuthHex. AuthHex only owns the lookup table /// (identity source for the JWT RoleId/RoleCode claims). Roles are authored in ERP-core /// and cloned here via createRole using the SAME RoleId so the two stores stay in /// sync. See docs/11-BACKEND-PHASE1.md (AuthHex proxy section). /// public class RoleManagerService { private readonly IRoleRepository _repository; private readonly IUnitOfWork _uow; public RoleManagerService(IRoleRepository repository, IUnitOfWork uow) { _repository = repository; _uow = uow; } /// /// Clone a role created in the main system. Accepts the caller-supplied RoleId so the /// AuthHex row matches ERP-core's. Idempotent-ish: a duplicate RoleId or Code is rejected. /// public async Task CreateRole(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); if (data == null || !data.Any()) throw new ArgumentException("Invalid payload data"); if (!data.ContainsKey("roleId") || !Guid.TryParse(data["roleId"].GetString(), out var roleId)) throw new ArgumentException("Invalid roleId"); var code = data.ContainsKey("code") ? data["code"].GetString() : null; if (string.IsNullOrWhiteSpace(code)) throw new ArgumentException("Role code is required"); code = code.Trim(); var name = data.ContainsKey("name") ? data["name"].GetString() : null; bool? isSystemRole = null; if (data.ContainsKey("isSystemRole")) { var el = data["isSystemRole"]; isSystemRole = el.ValueKind == System.Text.Json.JsonValueKind.True || el.ValueKind == System.Text.Json.JsonValueKind.False ? el.GetBoolean() : (bool?)null; } if (await _repository.GetByIdAsync(roleId) != null) throw new InvalidOperationException($"Role '{roleId}' already exists."); if (await _repository.GetByCodeAsync(code) != null) throw new InvalidOperationException($"Role code '{code}' already exists."); var role = new Role { RoleId = roleId, Code = code, Name = name, IsSystemRole = isSystemRole, CreatedAt = DateTime.UtcNow }; await _repository.AddRoleAsync(role); await _uow.CommitAsync(); return ToDto(role); } catch { await _uow.RollbackAsync(); throw; } } public async Task ListRoles(object payload, HttpContext httpContext) { var roles = await _repository.ListAsync(); return roles.Select(ToDto).ToList(); } public async Task GetRole(object payload, HttpContext httpContext) { var data = DeserializePayload.Deserialize(payload); if (data == null || !data.ContainsKey("roleId") || !Guid.TryParse(data["roleId"].GetString(), out var roleId)) throw new ArgumentException("Invalid roleId"); var role = await _repository.GetByIdAsync(roleId); if (role == null) throw new KeyNotFoundException($"Role '{roleId}' not found."); return ToDto(role); } /// /// Delete a role. The User.RoleId FK is Restrict, so if any user is still assigned /// the DB rejects the delete and we surface a conflict — keeping ERP-core (which calls /// this before deleting its own copy) from ever orphaning an in-use role. /// public async Task DeleteRole(object payload, HttpContext httpContext) { await _uow.BeginAsync(); try { var data = DeserializePayload.Deserialize(payload); if (data == null || !data.ContainsKey("roleId") || !Guid.TryParse(data["roleId"].GetString(), out var roleId)) throw new ArgumentException("Invalid roleId"); var role = await _repository.GetByIdAsync(roleId) ?? throw new KeyNotFoundException($"Role '{roleId}' not found."); var inUse = await _repository.IsAssignedToAnyUserAsync(roleId); if (inUse) throw new InvalidOperationException($"Role '{role.Code}' is assigned to one or more users and cannot be deleted."); await _repository.DeleteRoleAsync(role); await _uow.CommitAsync(); return new { roleId = role.RoleId, deleted = true }; } catch { await _uow.RollbackAsync(); throw; } } private static object ToDto(Role role) => new { roleId = role.RoleId, code = role.Code, name = role.Name, isSystemRole = role.IsSystemRole, createdAt = role.CreatedAt }; }