line-gestao-api/Controllers/UsersController.cs

386 lines
12 KiB
C#

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/users")]
[Authorize]
public class UsersController : ControllerBase
{
private static readonly HashSet<string> AllowedRoles = new(StringComparer.OrdinalIgnoreCase)
{
"admin",
"gestor"
};
private readonly AppDbContext _db;
private readonly UserManager<ApplicationUser> _userManager;
private readonly RoleManager<IdentityRole<Guid>> _roleManager;
private readonly ITenantProvider _tenantProvider;
public UsersController(
AppDbContext db,
UserManager<ApplicationUser> userManager,
RoleManager<IdentityRole<Guid>> roleManager,
ITenantProvider tenantProvider)
{
_db = db;
_userManager = userManager;
_roleManager = roleManager;
_tenantProvider = tenantProvider;
}
[HttpPost]
[Authorize(Roles = "admin")]
public async Task<ActionResult<UserListItemDto>> Create([FromBody] UserCreateRequest req)
{
var errors = ValidateCreate(req);
if (errors.Count > 0)
{
return BadRequest(new ValidationErrorResponse { Errors = errors });
}
if (_tenantProvider.TenantId == null)
{
return Unauthorized();
}
var tenantId = _tenantProvider.TenantId.Value;
var email = req.Email.Trim().ToLowerInvariant();
var normalizedEmail = _userManager.NormalizeEmail(email);
var exists = await _userManager.Users
.AnyAsync(u => u.TenantId == tenantId && u.NormalizedEmail == normalizedEmail);
if (exists)
{
return BadRequest(new ValidationErrorResponse
{
Errors = new List<ValidationErrorDto>
{
new() { Field = "email", Message = "E-mail já cadastrado." }
}
});
}
var role = req.Permissao.Trim().ToLowerInvariant();
if (!AllowedRoles.Contains(role) || !await _roleManager.RoleExistsAsync(role))
{
return BadRequest(new ValidationErrorResponse
{
Errors = new List<ValidationErrorDto>
{
new() { Field = "permissao", Message = "Permissão inválida." }
}
});
}
var user = new ApplicationUser
{
Name = req.Nome.Trim(),
Email = email,
UserName = email,
TenantId = tenantId,
IsActive = true,
EmailConfirmed = true
};
var createResult = await _userManager.CreateAsync(user, req.Senha);
if (!createResult.Succeeded)
{
return BadRequest(new ValidationErrorResponse
{
Errors = createResult.Errors.Select(e => new ValidationErrorDto
{
Field = "senha",
Message = e.Description
}).ToList()
});
}
await _userManager.AddToRoleAsync(user, role);
var response = new UserListItemDto
{
Id = user.Id,
Nome = user.Name,
Email = user.Email ?? string.Empty,
Permissao = role,
TenantId = user.TenantId,
Ativo = user.IsActive
};
return CreatedAtAction(nameof(GetById), new { id = user.Id }, response);
}
[HttpGet]
[Authorize(Roles = "admin")]
public async Task<ActionResult<PagedResult<UserListItemDto>>> GetAll(
[FromQuery] string? search,
[FromQuery] string? permissao,
[FromQuery] int page = 1,
[FromQuery] int pageSize = 20)
{
page = page < 1 ? 1 : page;
pageSize = pageSize < 1 ? 20 : pageSize;
var usersQuery = _userManager.Users.AsNoTracking();
if (!string.IsNullOrWhiteSpace(search))
{
var term = search.Trim();
usersQuery = usersQuery.Where(u =>
EF.Functions.ILike(u.Name, $"%{term}%") ||
EF.Functions.ILike(u.Email ?? string.Empty, $"%{term}%"));
}
IQueryable<Guid> userIdsByRole = Enumerable.Empty<Guid>().AsQueryable();
if (!string.IsNullOrWhiteSpace(permissao))
{
var roleName = permissao.Trim().ToLowerInvariant();
userIdsByRole = from ur in _db.UserRoles
join r in _db.Roles on ur.RoleId equals r.Id
where r.Name == roleName
select ur.UserId;
usersQuery = usersQuery.Where(u => userIdsByRole.Contains(u.Id));
}
var total = await usersQuery.CountAsync();
var users = await usersQuery
.OrderBy(u => u.Name)
.Skip((page - 1) * pageSize)
.Take(pageSize)
.ToListAsync();
var roleLookup = await (from ur in _db.UserRoles
join r in _db.Roles on ur.RoleId equals r.Id
where users.Select(u => u.Id).Contains(ur.UserId)
select new { ur.UserId, Role = r.Name ?? "" })
.ToListAsync();
var roleMap = roleLookup
.GroupBy(x => x.UserId)
.ToDictionary(g => g.Key, g => g.First().Role);
var items = users.Select(u => new UserListItemDto
{
Id = u.Id,
Nome = u.Name,
Email = u.Email ?? string.Empty,
Permissao = roleMap.GetValueOrDefault(u.Id, string.Empty),
TenantId = u.TenantId,
Ativo = u.IsActive
}).ToList();
return Ok(new PagedResult<UserListItemDto>
{
Page = page,
PageSize = pageSize,
Total = total,
Items = items
});
}
[HttpGet("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<ActionResult<UserListItemDto>> GetById(Guid id)
{
var user = await _userManager.Users.AsNoTracking().FirstOrDefaultAsync(u => u.Id == id);
if (user == null)
{
return NotFound();
}
var roles = await _userManager.GetRolesAsync(user);
var role = roles.FirstOrDefault() ?? string.Empty;
return Ok(new UserListItemDto
{
Id = user.Id,
Nome = user.Name,
Email = user.Email ?? string.Empty,
Permissao = role,
TenantId = user.TenantId,
Ativo = user.IsActive
});
}
[HttpPatch("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Update(Guid id, [FromBody] UserUpdateRequest req)
{
var errors = await ValidateUpdateAsync(id, req);
if (errors.Count > 0)
{
return BadRequest(new ValidationErrorResponse { Errors = errors });
}
var user = await _userManager.Users.FirstOrDefaultAsync(u => u.Id == id);
if (user == null)
{
return NotFound();
}
if (!string.IsNullOrWhiteSpace(req.Nome))
{
user.Name = req.Nome.Trim();
}
if (!string.IsNullOrWhiteSpace(req.Email))
{
var email = req.Email.Trim().ToLowerInvariant();
if (!string.Equals(user.Email, email, StringComparison.OrdinalIgnoreCase))
{
await _userManager.SetEmailAsync(user, email);
await _userManager.SetUserNameAsync(user, email);
}
}
if (req.Ativo.HasValue)
{
user.IsActive = req.Ativo.Value;
}
if (!string.IsNullOrWhiteSpace(req.Permissao))
{
var roleName = req.Permissao.Trim().ToLowerInvariant();
if (!AllowedRoles.Contains(roleName) || !await _roleManager.RoleExistsAsync(roleName))
{
return BadRequest(new ValidationErrorResponse
{
Errors = new List<ValidationErrorDto>
{
new() { Field = "permissao", Message = "Permissão inválida." }
}
});
}
var existingRoles = await _userManager.GetRolesAsync(user);
if (existingRoles.Count > 0)
{
await _userManager.RemoveFromRolesAsync(user, existingRoles);
}
await _userManager.AddToRoleAsync(user, roleName);
}
if (!string.IsNullOrWhiteSpace(req.Senha))
{
var token = await _userManager.GeneratePasswordResetTokenAsync(user);
var resetResult = await _userManager.ResetPasswordAsync(user, token, req.Senha);
if (!resetResult.Succeeded)
{
return BadRequest(new ValidationErrorResponse
{
Errors = resetResult.Errors.Select(e => new ValidationErrorDto
{
Field = "senha",
Message = e.Description
}).ToList()
});
}
}
await _userManager.UpdateAsync(user);
return NoContent();
}
private static List<ValidationErrorDto> ValidateCreate(UserCreateRequest req)
{
var errors = new List<ValidationErrorDto>();
if (string.IsNullOrWhiteSpace(req.Nome))
{
errors.Add(new ValidationErrorDto { Field = "nome", Message = "Nome é obrigatório." });
}
if (string.IsNullOrWhiteSpace(req.Email))
{
errors.Add(new ValidationErrorDto { Field = "email", Message = "Email é obrigatório." });
}
if (string.IsNullOrWhiteSpace(req.Senha) || req.Senha.Length < 6)
{
errors.Add(new ValidationErrorDto { Field = "senha", Message = "Senha deve ter pelo menos 6 caracteres." });
}
if (req.Senha != req.ConfirmarSenha)
{
errors.Add(new ValidationErrorDto { Field = "confirmarSenha", Message = "Confirmação de senha inválida." });
}
if (string.IsNullOrWhiteSpace(req.Permissao))
{
errors.Add(new ValidationErrorDto { Field = "permissao", Message = "Permissão é obrigatória." });
}
else if (!AllowedRoles.Contains(req.Permissao.Trim()))
{
errors.Add(new ValidationErrorDto { Field = "permissao", Message = "Permissão inválida." });
}
return errors;
}
private async Task<List<ValidationErrorDto>> ValidateUpdateAsync(Guid userId, UserUpdateRequest req)
{
var errors = new List<ValidationErrorDto>();
if (!string.IsNullOrWhiteSpace(req.Nome) && req.Nome.Trim().Length < 2)
{
errors.Add(new ValidationErrorDto { Field = "nome", Message = "Nome inválido." });
}
if (!string.IsNullOrWhiteSpace(req.Email))
{
var email = req.Email.Trim().ToLowerInvariant();
var normalized = _userManager.NormalizeEmail(email);
var tenantId = _tenantProvider.TenantId;
if (tenantId == null)
{
errors.Add(new ValidationErrorDto { Field = "email", Message = "Tenant inválido." });
}
else
{
var exists = await _userManager.Users.AnyAsync(u =>
u.Id != userId &&
u.TenantId == tenantId &&
u.NormalizedEmail == normalized);
if (exists)
{
errors.Add(new ValidationErrorDto { Field = "email", Message = "E-mail já cadastrado." });
}
}
}
if (!string.IsNullOrWhiteSpace(req.Senha))
{
if (req.Senha.Length < 6)
{
errors.Add(new ValidationErrorDto { Field = "senha", Message = "Senha deve ter pelo menos 6 caracteres." });
}
if (req.Senha != req.ConfirmarSenha)
{
errors.Add(new ValidationErrorDto { Field = "confirmarSenha", Message = "Confirmação de senha inválida." });
}
}
if (!string.IsNullOrWhiteSpace(req.Permissao) && !AllowedRoles.Contains(req.Permissao.Trim()))
{
errors.Add(new ValidationErrorDto { Field = "permissao", Message = "Permissão inválida." });
}
return errors;
}
}