import { Body, Controller, Get, Patch, Post, Query, Req, UnauthorizedException, BadRequestException, UseGuards } from '@nestjs/common'; import { LoginDto } from './dto/login.dto'; import { AuthService } from './auth.service'; import { Public } from 'src/common/decorators/public.decorator'; import { RegisterDto } from './dto/register.dto'; import { RegisterParentCompletDto } from './dto/register-parent-complet.dto'; import { RegisterAMCompletDto } from './dto/register-am-complet.dto'; import { ChangePasswordRequiredDto } from './dto/change-password.dto'; import { ApiBearerAuth, ApiOperation, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger'; import { AuthGuard } from 'src/common/guards/auth.guard'; import type { Request } from 'express'; import { UserService } from '../user/user.service'; import { ProfileResponseDto } from './dto/profile_response.dto'; import { RefreshTokenDto } from './dto/refresh_token.dto'; import { ResoumettreRepriseDto } from './dto/resoumettre-reprise.dto'; import { RepriseIdentifyBodyDto } from './dto/reprise-identify.dto'; import { User } from 'src/common/decorators/user.decorator'; import { Users } from 'src/entities/users.entity'; import { RepriseDossierDto } from './dto/reprise-dossier.dto'; @ApiTags('Authentification') @Controller('auth') export class AuthController { constructor( private readonly authService: AuthService, private readonly userService: UserService, ) { } @Public() @ApiOperation({ summary: 'Connexion' }) @Post('login') async login(@Body() dto: LoginDto) { return this.authService.login(dto); } @Public() @Post('register') @ApiOperation({ summary: 'Inscription (OBSOLÈTE - utiliser /register/parent)' }) @ApiResponse({ status: 409, description: 'Email déjà utilisé' }) async register(@Body() dto: RegisterDto) { return this.authService.register(dto); } @Public() @Post('register/parent') @ApiOperation({ summary: 'Inscription Parent COMPLÈTE - Workflow 6 étapes', description: 'Crée Parent 1 + Parent 2 (opt) + Enfants + Présentation + CGU en une transaction' }) @ApiResponse({ status: 201, description: 'Inscription réussie - Dossier en attente de validation' }) @ApiResponse({ status: 400, description: 'Données invalides ou CGU non acceptées' }) @ApiResponse({ status: 409, description: 'Email déjà utilisé' }) async inscrireParentComplet(@Body() dto: RegisterParentCompletDto) { return this.authService.inscrireParentComplet(dto); } @Public() @Post('register/am') @ApiOperation({ summary: 'Inscription Assistante Maternelle COMPLÈTE', description: 'Crée User AM + entrée assistantes_maternelles (identité + infos pro + photo + CGU) en une transaction. Si photo_base64 est fourni sans photo_filename, le serveur utilise le nom par défaut photo_am.jpg (extension du fichier stocké = type du data-URL).', }) @ApiResponse({ status: 201, description: 'Inscription réussie - Dossier en attente de validation' }) @ApiResponse({ status: 400, description: 'Données invalides ou CGU non acceptées' }) @ApiResponse({ status: 409, description: 'Email déjà utilisé' }) async inscrireAMComplet(@Body() dto: RegisterAMCompletDto) { return this.authService.inscrireAMComplet(dto); } @Public() @Get('reprise-dossier') @ApiOperation({ summary: 'Dossier pour reprise (token seul)' }) @ApiQuery({ name: 'token', required: true, description: 'Token reprise (lien email)' }) @ApiResponse({ status: 200, description: 'Données dossier pour préremplir', type: RepriseDossierDto }) @ApiResponse({ status: 404, description: 'Token invalide ou expiré' }) async getRepriseDossier(@Query('token') token: string): Promise { return this.authService.getRepriseDossier(token); } @Public() @Patch('reprise-resoumettre') @ApiOperation({ summary: 'Resoumettre le dossier (mise à jour + statut en_attente, invalide le token)' }) @ApiResponse({ status: 200, description: 'Dossier resoumis' }) @ApiResponse({ status: 404, description: 'Token invalide ou expiré' }) async resoumettreReprise(@Body() dto: ResoumettreRepriseDto) { const { token, ...fields } = dto; return this.authService.resoumettreReprise(token, fields); } @Public() @Post('reprise-identify') @ApiOperation({ summary: 'Modale reprise : numéro + email → type + token' }) @ApiResponse({ status: 201, description: 'type (parent/AM) + token pour GET reprise-dossier / PUT reprise-resoumettre' }) @ApiResponse({ status: 404, description: 'Aucun dossier en reprise pour ce numéro et email' }) async repriseIdentify(@Body() dto: RepriseIdentifyBodyDto) { return this.authService.identifyReprise(dto.numero_dossier, dto.email); } @Public() @Post('refresh') @ApiBearerAuth('refresh_token') @ApiResponse({ status: 200, description: 'Nouveaux tokens générés avec succès.' }) @ApiResponse({ status: 401, description: 'Token de rafraîchissement invalide ou expiré.' }) @ApiOperation({ summary: 'Rafraichir les tokens' }) async refresh(@Body() dto: RefreshTokenDto) { return this.authService.refreshTokens(dto.refresh_token); } @Get('me') @UseGuards(AuthGuard) @ApiBearerAuth('access-token') @ApiOperation({ summary: "Récupérer le profil complet de l'utilisateur connecté" }) @ApiResponse({ status: 200, type: ProfileResponseDto }) async getProfile(@Req() req: Request): Promise { if (!req.user || !req.user.sub) { throw new UnauthorizedException('Utilisateur non authentifié'); } const user = await this.userService.findOne(req.user.sub); return { id: user.id, email: user.email, role: user.role, prenom: user.prenom ?? '', nom: user.nom ?? '', statut: user.statut, changement_mdp_obligatoire: user.changement_mdp_obligatoire, }; } @UseGuards(AuthGuard) @ApiBearerAuth('access-token') @Post('logout') logout(@User() currentUser: Users) { return this.authService.logout(currentUser.id); } @Post('change-password-required') @UseGuards(AuthGuard) @ApiBearerAuth('access-token') @ApiOperation({ summary: 'Changement de mot de passe obligatoire', description: 'Permet de changer le mot de passe lors de la première connexion (flag changement_mdp_obligatoire)' }) @ApiResponse({ status: 200, description: 'Mot de passe changé avec succès' }) @ApiResponse({ status: 400, description: 'Mot de passe actuel incorrect ou confirmation non correspondante' }) @ApiResponse({ status: 403, description: 'Changement de mot de passe non requis pour cet utilisateur' }) async changePasswordRequired( @User() currentUser: Users, @Body() dto: ChangePasswordRequiredDto, ) { // Vérifier que les mots de passe correspondent if (dto.nouveau_mot_de_passe !== dto.confirmation_mot_de_passe) { throw new BadRequestException('Les mots de passe ne correspondent pas'); } return this.authService.changePasswordRequired( currentUser.id, dto.mot_de_passe_actuel, dto.nouveau_mot_de_passe, ); } }