using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using SchotzIadKmTracker.Api.Models.DTOs;
using SchotzIadKmTracker.Api.Services;
using SchotzIadKmTracker.Api.Services.Exceptions;
namespace SchotzIadKmTracker.Api.Controllers;
///
/// Endpoints publics d'authentification de l'application mono-utilisateur :
/// inscription, connexion, demande et application de la réinitialisation du
/// mot de passe, ainsi que la vérification de l'existence du compte.
/// Toutes les routes sont anonymes () : elles
/// restent accessibles même une fois l'autorisation globale activée (Exigence 9.1).
/// Les erreurs sont renvoyées au format ProblemDetails, comme les autres controllers.
///
[ApiController]
[Route("api/auth")]
[AllowAnonymous]
public class AuthController : ControllerBase
{
private readonly IAuthService _authService;
public AuthController(IAuthService authService)
{
_authService = authService;
}
///
/// Inscrit le compte unique de l'application. Les erreurs de validation
/// (email invalide, mot de passe trop court) sont renvoyées automatiquement
/// en 400 par FluentValidation (Exigences 1.3, 1.4). Un compte déjà existant
/// est rejeté en 409 (Exigence 1.2).
///
[HttpPost("register")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
[ProducesResponseType(StatusCodes.Status409Conflict)]
public async Task Register(
[FromBody] RegisterDto dto,
CancellationToken cancellationToken)
{
try
{
await _authService.RegisterAsync(dto, cancellationToken);
return Ok();
}
catch (CompteDejaExistantException ex)
{
return Problem(detail: ex.Message, statusCode: StatusCodes.Status409Conflict);
}
}
///
/// Authentifie le compte et retourne le jeton JWT accompagné de sa date
/// d'expiration. En cas d'identifiants invalides, renvoie un 401 avec un
/// message générique afin de ne pas distinguer email inconnu et mot de passe
/// erroné (Exigences 2.1, 2.2).
///
[HttpPost("login")]
[ProducesResponseType(typeof(LoginResultDto), StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status401Unauthorized)]
public async Task> Login(
[FromBody] LoginDto dto,
CancellationToken cancellationToken)
{
try
{
var resultat = await _authService.LoginAsync(dto, cancellationToken);
return Ok(resultat);
}
catch (IdentifiantsInvalidesException)
{
return Problem(detail: "Identifiants invalides.", statusCode: StatusCodes.Status401Unauthorized);
}
}
///
/// Traite une demande de réinitialisation de mot de passe. Par principe de
/// non-divulgation, renvoie toujours 200 avec la même réponse, que l'email
/// corresponde ou non à un compte (Exigence 6.6). La logique de non-divulgation
/// est portée par le service ; les erreurs techniques réelles remontent.
///
[HttpPost("forgot-password")]
[ProducesResponseType(StatusCodes.Status200OK)]
public async Task ForgotPassword(
[FromBody] ForgotPasswordDto dto,
CancellationToken cancellationToken)
{
await _authService.ForgotPasswordAsync(dto, cancellationToken);
return Ok();
}
///
/// Réinitialise le mot de passe à partir d'un jeton de réinitialisation.
/// Un jeton expiré (Exigence 7.3) ou invalide/déjà utilisé (Exigence 7.4)
/// est rejeté en 400. Les erreurs de validation du nouveau mot de passe sont
/// renvoyées automatiquement en 400 par FluentValidation (Exigence 7.5).
///
[HttpPost("reset-password")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task ResetPassword(
[FromBody] ResetPasswordDto dto,
CancellationToken cancellationToken)
{
try
{
await _authService.ResetPasswordAsync(dto, cancellationToken);
return Ok();
}
catch (LienResetExpireException)
{
return Problem(detail: "Le lien de réinitialisation a expiré.", statusCode: StatusCodes.Status400BadRequest);
}
catch (LienResetInvalideException)
{
return Problem(detail: "Le lien de réinitialisation est invalide.", statusCode: StatusCodes.Status400BadRequest);
}
}
///
/// Indique si un compte existe déjà, afin d'orienter le frontend entre
/// inscription et connexion (Exigences 1.2, 9.1).
///
[HttpGet("compte-existe")]
[ProducesResponseType(typeof(CompteExisteDto), StatusCodes.Status200OK)]
public async Task> CompteExiste(CancellationToken cancellationToken)
{
return Ok(new CompteExisteDto(await _authService.CompteExisteAsync(cancellationToken)));
}
}