line-gestao-api/Controllers/ProfileController.cs

265 lines
8.0 KiB
C#

using System.ComponentModel.DataAnnotations;
using System.Security.Claims;
using line_gestao_api.Dtos;
using line_gestao_api.Models;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using Microsoft.EntityFrameworkCore;
namespace line_gestao_api.Controllers;
[ApiController]
[Route("api/profile")]
[Authorize]
public class ProfileController : ControllerBase
{
private static readonly EmailAddressAttribute EmailValidator = new();
private readonly UserManager<ApplicationUser> _userManager;
public ProfileController(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
[HttpGet("me")]
public async Task<ActionResult<ProfileMeDto>> GetMe()
{
var user = await GetAuthenticatedUserAsync();
if (user == null)
{
return Unauthorized(new { message = "Usuário não autenticado." });
}
return Ok(ToProfileDto(user));
}
[HttpPatch]
public async Task<ActionResult<ProfileMeDto>> UpdateProfile([FromBody] UpdateProfileRequest req)
{
var user = await GetAuthenticatedUserAsync();
if (user == null)
{
return Unauthorized(new { message = "Usuário não autenticado." });
}
var errors = await ValidateProfileUpdateAsync(user.Id, req);
if (errors.Count > 0)
{
return BadRequest(new ValidationErrorResponse { Errors = errors });
}
var nome = req.Nome.Trim();
var email = req.Email.Trim().ToLowerInvariant();
user.Name = nome;
if (!string.Equals(user.Email, email, StringComparison.OrdinalIgnoreCase))
{
var setEmailResult = await _userManager.SetEmailAsync(user, email);
if (!setEmailResult.Succeeded)
{
return BadRequest(ToValidationErrorResponse("email", setEmailResult.Errors));
}
var setUserNameResult = await _userManager.SetUserNameAsync(user, email);
if (!setUserNameResult.Succeeded)
{
return BadRequest(ToValidationErrorResponse("email", setUserNameResult.Errors));
}
}
var updateResult = await _userManager.UpdateAsync(user);
if (!updateResult.Succeeded)
{
return BadRequest(ToValidationErrorResponse("perfil", updateResult.Errors));
}
return Ok(ToProfileDto(user));
}
[HttpPost("change-password")]
public async Task<IActionResult> ChangePassword([FromBody] ChangeMyPasswordRequest req)
{
var errors = ValidatePasswordChange(req);
if (errors.Count > 0)
{
return BadRequest(new ValidationErrorResponse { Errors = errors });
}
var user = await GetAuthenticatedUserAsync();
if (user == null)
{
return Unauthorized(new { message = "Usuário não autenticado." });
}
var result = await _userManager.ChangePasswordAsync(user, req.CredencialAtual, req.NovaCredencial);
if (!result.Succeeded)
{
return BadRequest(MapPasswordChangeErrors(result.Errors));
}
return NoContent();
}
private async Task<ApplicationUser?> GetAuthenticatedUserAsync()
{
var userIdRaw = User.FindFirstValue(ClaimTypes.NameIdentifier)
?? User.FindFirstValue("sub");
if (!Guid.TryParse(userIdRaw, out var userId))
{
return null;
}
return await _userManager.Users
.FirstOrDefaultAsync(u => u.Id == userId && u.IsActive);
}
private async Task<List<ValidationErrorDto>> ValidateProfileUpdateAsync(Guid userId, UpdateProfileRequest req)
{
var errors = new List<ValidationErrorDto>();
if (string.IsNullOrWhiteSpace(req.Nome) || req.Nome.Trim().Length < 2)
{
errors.Add(new ValidationErrorDto
{
Field = "nome",
Message = "Nome é obrigatório e deve ter pelo menos 2 caracteres."
});
}
if (string.IsNullOrWhiteSpace(req.Email))
{
errors.Add(new ValidationErrorDto
{
Field = "email",
Message = "Email é obrigatório."
});
}
else
{
var email = req.Email.Trim().ToLowerInvariant();
if (!EmailValidator.IsValid(email))
{
errors.Add(new ValidationErrorDto
{
Field = "email",
Message = "Email inválido."
});
}
else
{
var normalized = _userManager.NormalizeEmail(email);
var exists = await _userManager.Users
.AnyAsync(u => u.Id != userId && u.NormalizedEmail == normalized);
if (exists)
{
errors.Add(new ValidationErrorDto
{
Field = "email",
Message = "E-mail já cadastrado."
});
}
}
}
return errors;
}
private static List<ValidationErrorDto> ValidatePasswordChange(ChangeMyPasswordRequest req)
{
var errors = new List<ValidationErrorDto>();
if (string.IsNullOrWhiteSpace(req.CredencialAtual))
{
errors.Add(new ValidationErrorDto
{
Field = "credencialAtual",
Message = "Credencial atual é obrigatória."
});
}
if (string.IsNullOrWhiteSpace(req.NovaCredencial))
{
errors.Add(new ValidationErrorDto
{
Field = "novaCredencial",
Message = "Nova credencial é obrigatória."
});
}
else if (req.NovaCredencial.Length < 8)
{
errors.Add(new ValidationErrorDto
{
Field = "novaCredencial",
Message = "Nova credencial deve ter pelo menos 8 caracteres."
});
}
if (string.IsNullOrWhiteSpace(req.ConfirmarNovaCredencial))
{
errors.Add(new ValidationErrorDto
{
Field = "confirmarNovaCredencial",
Message = "Confirmação da nova credencial é obrigatória."
});
}
else if (!string.Equals(req.NovaCredencial, req.ConfirmarNovaCredencial, StringComparison.Ordinal))
{
errors.Add(new ValidationErrorDto
{
Field = "confirmarNovaCredencial",
Message = "Confirmação da nova credencial inválida."
});
}
return errors;
}
private static ValidationErrorResponse ToValidationErrorResponse(string field, IEnumerable<IdentityError> identityErrors)
{
return new ValidationErrorResponse
{
Errors = identityErrors.Select(e => new ValidationErrorDto
{
Field = field,
Message = e.Description
}).ToList()
};
}
private static ValidationErrorResponse MapPasswordChangeErrors(IEnumerable<IdentityError> identityErrors)
{
var errors = identityErrors.Select(e =>
{
var field = string.Equals(e.Code, "PasswordMismatch", StringComparison.OrdinalIgnoreCase)
? "credencialAtual"
: "novaCredencial";
var message = string.Equals(e.Code, "PasswordMismatch", StringComparison.OrdinalIgnoreCase)
? "Credencial atual inválida."
: e.Description;
return new ValidationErrorDto
{
Field = field,
Message = message
};
}).ToList();
return new ValidationErrorResponse { Errors = errors };
}
private static ProfileMeDto ToProfileDto(ApplicationUser user)
{
return new ProfileMeDto
{
Id = user.Id,
Nome = user.Name,
Email = user.Email ?? string.Empty
};
}
}