Responsividade Mobile

This commit is contained in:
Eduardo 2026-02-24 11:04:42 -03:00
parent e6cd510a29
commit 5101c3665a
3 changed files with 172 additions and 0 deletions

View File

@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
using System.Security.Claims;
namespace line_gestao_api.Controllers;
@ -293,6 +294,95 @@ public class UsersController : ControllerBase
return NoContent();
}
[HttpDelete("{id:guid}")]
[Authorize(Roles = "admin")]
public async Task<IActionResult> Delete(Guid id)
{
if (_tenantProvider.TenantId == null)
{
return Unauthorized();
}
var currentUserId = GetCurrentUserId();
if (currentUserId.HasValue && currentUserId.Value == id)
{
return BadRequest(new ValidationErrorResponse
{
Errors = new List<ValidationErrorDto>
{
new() { Field = "usuario", Message = "Você não pode excluir a própria conta." }
}
});
}
var tenantId = _tenantProvider.TenantId.Value;
var user = await _userManager.Users.FirstOrDefaultAsync(u => u.Id == id && u.TenantId == tenantId);
if (user == null)
{
return NotFound();
}
if (user.IsActive)
{
return BadRequest(new ValidationErrorResponse
{
Errors = new List<ValidationErrorDto>
{
new() { Field = "usuario", Message = "Inative a conta antes de excluir permanentemente." }
}
});
}
var targetRoles = await _userManager.GetRolesAsync(user);
var isAdmin = targetRoles.Any(r => string.Equals(r, "admin", StringComparison.OrdinalIgnoreCase));
if (isAdmin)
{
var adminRoleId = await _roleManager.Roles
.Where(r => r.Name == "admin")
.Select(r => (Guid?)r.Id)
.FirstOrDefaultAsync();
if (adminRoleId.HasValue)
{
var hasAnotherAdmin = await (
from ur in _db.UserRoles
join u in _userManager.Users on ur.UserId equals u.Id
where ur.RoleId == adminRoleId.Value
&& u.TenantId == tenantId
&& u.Id != id
select u.Id
).AnyAsync();
if (!hasAnotherAdmin)
{
return BadRequest(new ValidationErrorResponse
{
Errors = new List<ValidationErrorDto>
{
new() { Field = "usuario", Message = "Não é permitido excluir o último administrador." }
}
});
}
}
}
var deleteResult = await _userManager.DeleteAsync(user);
if (!deleteResult.Succeeded)
{
return BadRequest(new ValidationErrorResponse
{
Errors = deleteResult.Errors.Select(e => new ValidationErrorDto
{
Field = "usuario",
Message = e.Description
}).ToList()
});
}
return NoContent();
}
private static List<ValidationErrorDto> ValidateCreate(UserCreateRequest req)
{
var errors = new List<ValidationErrorDto>();
@ -382,4 +472,10 @@ public class UsersController : ControllerBase
return errors;
}
private Guid? GetCurrentUserId()
{
var raw = User.FindFirstValue(ClaimTypes.NameIdentifier) ?? User.FindFirstValue("sub");
return Guid.TryParse(raw, out var parsed) ? parsed : null;
}
}

View File

@ -105,6 +105,7 @@ builder.Services.AddIdentityCore<ApplicationUser>(options =>
options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(15);
})
.AddRoles<IdentityRole<Guid>>()
.AddErrorDescriber<PortugueseIdentityErrorDescriber>()
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();

View File

@ -0,0 +1,75 @@
using Microsoft.AspNetCore.Identity;
namespace line_gestao_api.Services;
public class PortugueseIdentityErrorDescriber : IdentityErrorDescriber
{
public override IdentityError DefaultError()
=> NewError(nameof(DefaultError), "Ocorreu uma falha desconhecida.");
public override IdentityError ConcurrencyFailure()
=> NewError(nameof(ConcurrencyFailure), "Falha de concorrência. O registro foi alterado por outro processo.");
public override IdentityError PasswordMismatch()
=> NewError(nameof(PasswordMismatch), "Senha incorreta.");
public override IdentityError InvalidToken()
=> NewError(nameof(InvalidToken), "Token inválido.");
public override IdentityError LoginAlreadyAssociated()
=> NewError(nameof(LoginAlreadyAssociated), "Este login já está associado a outra conta.");
public override IdentityError InvalidUserName(string? userName)
=> NewError(nameof(InvalidUserName), $"Nome de usuário '{userName}' é inválido. Use apenas letras e números.");
public override IdentityError InvalidEmail(string? email)
=> NewError(nameof(InvalidEmail), $"E-mail '{email}' é inválido.");
public override IdentityError DuplicateUserName(string userName)
=> NewError(nameof(DuplicateUserName), $"O nome de usuário '{userName}' já está em uso.");
public override IdentityError DuplicateEmail(string email)
=> NewError(nameof(DuplicateEmail), $"O e-mail '{email}' já está em uso.");
public override IdentityError InvalidRoleName(string? role)
=> NewError(nameof(InvalidRoleName), $"O nome da permissão '{role}' é inválido.");
public override IdentityError DuplicateRoleName(string role)
=> NewError(nameof(DuplicateRoleName), $"A permissão '{role}' já existe.");
public override IdentityError UserAlreadyHasPassword()
=> NewError(nameof(UserAlreadyHasPassword), "Este usuário já possui senha definida.");
public override IdentityError UserLockoutNotEnabled()
=> NewError(nameof(UserLockoutNotEnabled), "Bloqueio não está habilitado para este usuário.");
public override IdentityError UserAlreadyInRole(string role)
=> NewError(nameof(UserAlreadyInRole), $"O usuário já possui a permissão '{role}'.");
public override IdentityError UserNotInRole(string role)
=> NewError(nameof(UserNotInRole), $"O usuário não possui a permissão '{role}'.");
public override IdentityError PasswordTooShort(int length)
=> NewError(nameof(PasswordTooShort), $"A senha deve ter pelo menos {length} caracteres.");
public override IdentityError PasswordRequiresNonAlphanumeric()
=> NewError(nameof(PasswordRequiresNonAlphanumeric), "A senha deve conter pelo menos um caractere especial.");
public override IdentityError PasswordRequiresDigit()
=> NewError(nameof(PasswordRequiresDigit), "A senha deve conter pelo menos um número ('0'-'9').");
public override IdentityError PasswordRequiresLower()
=> NewError(nameof(PasswordRequiresLower), "A senha deve conter pelo menos uma letra minúscula ('a'-'z').");
public override IdentityError PasswordRequiresUpper()
=> NewError(nameof(PasswordRequiresUpper), "A senha deve conter pelo menos uma letra maiúscula ('A'-'Z').");
public override IdentityError PasswordRequiresUniqueChars(int uniqueChars)
=> NewError(nameof(PasswordRequiresUniqueChars), $"A senha deve usar pelo menos {uniqueChars} caracteres diferentes.");
public override IdentityError RecoveryCodeRedemptionFailed()
=> NewError(nameof(RecoveryCodeRedemptionFailed), "Código de recuperação inválido.");
private static IdentityError NewError(string code, string description)
=> new() { Code = code, Description = description };
}