using line_gestao_api.Data; using line_gestao_api.Dtos; using line_gestao_api.Models; using line_gestao_api.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; namespace line_gestao_api.Controllers; [ApiController] [Route("api/system/tenants/{tenantId:guid}/users")] [Authorize(Policy = "SystemAdmin")] public class SystemTenantUsersController : ControllerBase { private readonly AppDbContext _db; private readonly UserManager _userManager; private readonly RoleManager> _roleManager; private readonly ISystemAuditService _systemAuditService; public SystemTenantUsersController( AppDbContext db, UserManager userManager, RoleManager> roleManager, ISystemAuditService systemAuditService) { _db = db; _userManager = userManager; _roleManager = roleManager; _systemAuditService = systemAuditService; } [HttpPost] public async Task> CreateUserForTenant( [FromRoute] Guid tenantId, [FromBody] CreateSystemTenantUserRequest request) { if (tenantId == Guid.Empty) { return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "TenantId inválido.", "invalid_tenant_id"); } var tenant = await _db.Tenants.AsNoTracking().FirstOrDefaultAsync(t => t.Id == tenantId); if (tenant == null) { return await RejectAsync(tenantId, StatusCodes.Status404NotFound, "Tenant não encontrado.", "tenant_not_found"); } if (string.IsNullOrWhiteSpace(request.Email)) { return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "Email é obrigatório.", "missing_email"); } if (string.IsNullOrWhiteSpace(request.Password)) { return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "Password é obrigatória.", "missing_password"); } if (request.Roles == null || request.Roles.Count == 0) { return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "Informe ao menos uma role.", "missing_roles"); } var normalizedRoles = request.Roles .Where(r => !string.IsNullOrWhiteSpace(r)) .Select(r => r.Trim().ToLowerInvariant()) .Distinct(StringComparer.OrdinalIgnoreCase) .ToList(); if (normalizedRoles.Count == 0) { return await RejectAsync(tenantId, StatusCodes.Status400BadRequest, "Roles inválidas.", "invalid_roles"); } var unsupportedRoles = normalizedRoles .Where(role => !AppRoles.All.Contains(role, StringComparer.OrdinalIgnoreCase)) .ToList(); if (unsupportedRoles.Count > 0) { return await RejectAsync( tenantId, StatusCodes.Status400BadRequest, $"Roles não suportadas: {string.Join(", ", unsupportedRoles)}. Use apenas: {string.Join(", ", AppRoles.All)}.", "unsupported_roles"); } if (!tenant.IsSystem && normalizedRoles.Contains(SystemTenantConstants.SystemRole)) { return await RejectAsync( tenantId, StatusCodes.Status400BadRequest, "A role sysadmin só pode ser usada no SystemTenant.", "invalid_sysadmin_outside_system_tenant"); } if (tenant.IsSystem && normalizedRoles.Any(r => r != SystemTenantConstants.SystemRole)) { return await RejectAsync( tenantId, StatusCodes.Status400BadRequest, "No SystemTenant é permitido apenas a role sysadmin.", "invalid_non_system_role_for_system_tenant"); } foreach (var role in normalizedRoles) { if (!await _roleManager.RoleExistsAsync(role)) { return await RejectAsync( tenantId, StatusCodes.Status400BadRequest, $"Role inexistente: {role}", "role_not_found"); } } var email = request.Email.Trim().ToLowerInvariant(); var normalizedEmail = _userManager.NormalizeEmail(email); var alreadyExists = await _userManager.Users .IgnoreQueryFilters() .AnyAsync(u => u.TenantId == tenantId && u.NormalizedEmail == normalizedEmail); if (alreadyExists) { return await RejectAsync(tenantId, StatusCodes.Status409Conflict, "Já existe usuário com este email neste tenant.", "email_exists"); } var name = string.IsNullOrWhiteSpace(request.Name) ? email : request.Name.Trim(); var user = new ApplicationUser { Name = name, Email = email, UserName = email, TenantId = tenantId, EmailConfirmed = true, IsActive = true, LockoutEnabled = true }; IdentityResult createResult; try { createResult = await _userManager.CreateAsync(user, request.Password); } catch (DbUpdateException) { return await RejectAsync( tenantId, StatusCodes.Status409Conflict, "Não foi possível criar usuário. Email/username já em uso.", "db_conflict"); } if (!createResult.Succeeded) { await _systemAuditService.LogAsync( action: SystemAuditActions.CreateTenantUserRejected, targetTenantId: tenantId, metadata: new { reason = "identity_create_failed", email, errors = createResult.Errors.Select(e => e.Description).ToList() }); return BadRequest(createResult.Errors.Select(e => e.Description).ToList()); } var addRolesResult = await _userManager.AddToRolesAsync(user, normalizedRoles); if (!addRolesResult.Succeeded) { await _userManager.DeleteAsync(user); await _systemAuditService.LogAsync( action: SystemAuditActions.CreateTenantUserRejected, targetTenantId: tenantId, metadata: new { reason = "identity_add_roles_failed", email, roles = normalizedRoles, errors = addRolesResult.Errors.Select(e => e.Description).ToList() }); return BadRequest(addRolesResult.Errors.Select(e => e.Description).ToList()); } await _systemAuditService.LogAsync( action: SystemAuditActions.CreateTenantUser, targetTenantId: tenantId, metadata: new { createdUserId = user.Id, email, roles = normalizedRoles }); var response = new SystemTenantUserCreatedDto { UserId = user.Id, TenantId = tenantId, Email = email, Roles = normalizedRoles }; return StatusCode(StatusCodes.Status201Created, response); } private async Task> RejectAsync( Guid targetTenantId, int statusCode, string message, string reason) { await _systemAuditService.LogAsync( action: SystemAuditActions.CreateTenantUserRejected, targetTenantId: targetTenantId, metadata: new { reason, message }); return StatusCode(statusCode, message); } }