import { Body, CallHandler, Controller, Delete, ExecutionContext, Get, HttpCode, HttpStatus, Injectable, NestInterceptor, Param, ParseUUIDPipe, Patch, Post, Query, UploadedFile, UseGuards, UseInterceptors, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiOperation, ApiQuery, ApiTags, } from '@nestjs/swagger'; import { diskStorage } from 'multer'; import { extname } from 'path'; import { Observable } from 'rxjs'; import { EnfantsService } from './enfants.service'; import { CreateEnfantsDto } from './dto/create_enfants.dto'; import { UpdateEnfantsDto } from './dto/update_enfants.dto'; import { RoleType, Users } from 'src/entities/users.entity'; import { User } from 'src/common/decorators/user.decorator'; import { AuthGuard } from 'src/common/guards/auth.guard'; import { Roles } from 'src/common/decorators/roles.decorator'; import { RolesGuard } from 'src/common/guards/roles.guard'; import { SuppressionService } from '../suppressions/suppression.service'; const photoMulterOptions = { storage: diskStorage({ destination: './uploads/photos', filename: (req, file, cb) => { const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1e9); const ext = extname(file.originalname); cb(null, `enfant-${uniqueSuffix}${ext}`); }, }), fileFilter: (req, file, cb) => { if (!file.mimetype.match(/\/(jpg|jpeg|png|gif)$/)) { return cb(new Error('Seules les images sont autorisées'), false); } cb(null, true); }, limits: { fileSize: 5 * 1024 * 1024, }, }; /** * Multer uniquement si Content-Type multipart (parent ou staff + photo). * JSON sans photo (#132) passe sans interceptor fichier. */ @Injectable() class OptionalEnfantPhotoInterceptor implements NestInterceptor { private readonly multipart = new (FileInterceptor( 'photo', photoMulterOptions, ))(); intercept(context: ExecutionContext, next: CallHandler): Observable | Promise> { const req = context.switchToHttp().getRequest(); const ct = String(req.headers['content-type'] ?? ''); if (!ct.includes('multipart/form-data')) { return next.handle(); } return this.multipart.intercept(context, next); } } @ApiBearerAuth('access-token') @ApiTags('Enfants') @UseGuards(AuthGuard, RolesGuard) @Controller('enfants') export class EnfantsController { constructor( private readonly enfantsService: EnfantsService, private readonly suppressionService: SuppressionService, ) { } @Roles( RoleType.PARENT, RoleType.GESTIONNAIRE, RoleType.ADMINISTRATEUR, RoleType.SUPER_ADMIN, ) @Post() @HttpCode(HttpStatus.CREATED) @ApiOperation({ summary: 'Créer un enfant', description: 'PARENT : multipart éventuel, rattache au compte connecté. ' + 'Staff : parent_user_id obligatoire ; JSON sans photo OK ; avec photo → multipart (champ fichier `photo`, max 5 Mo). Ticket #132.', }) @ApiConsumes('application/json', 'multipart/form-data') @ApiBody({ description: 'Champs métier (+ parent_user_id côté staff). Fichier optionnel `photo` en multipart.', type: CreateEnfantsDto, }) @UseInterceptors(OptionalEnfantPhotoInterceptor) create( @Body() dto: CreateEnfantsDto, @UploadedFile() photo: Express.Multer.File, @User() currentUser: Users, ) { return this.enfantsService.create(dto, currentUser, photo); } @Roles(RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE, RoleType.SUPER_ADMIN) @Get() findAll() { return this.enfantsService.findAll(); } @Roles( RoleType.PARENT, RoleType.ADMINISTRATEUR, RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE ) @Get(':id') findOne( @Param('id', new ParseUUIDPipe()) id: string, @User() currentUser: Users ) { return this.enfantsService.findOne(id, currentUser); } @Roles( RoleType.PARENT, RoleType.ADMINISTRATEUR, RoleType.SUPER_ADMIN, RoleType.GESTIONNAIRE, ) @Patch(':id') @ApiOperation({ summary: 'Mettre à jour un enfant', description: 'JSON sans photo OK ; avec nouvelle photo → multipart (champ fichier `photo`, max 5 Mo).', }) @ApiConsumes('application/json', 'multipart/form-data') @UseInterceptors(OptionalEnfantPhotoInterceptor) update( @Param('id', new ParseUUIDPipe()) id: string, @Body() dto: UpdateEnfantsDto, @UploadedFile() photo: Express.Multer.File, @User() currentUser: Users, ) { return this.enfantsService.update(id, dto, currentUser, photo); } @Roles( RoleType.SUPER_ADMIN, RoleType.ADMINISTRATEUR, RoleType.GESTIONNAIRE, ) @Delete(':id') @ApiOperation({ summary: 'Supprimer un enfant (#159)', description: 'Query `deleteDossier=true` si dernier enfant et suppression du dossier famille souhaitée.', }) @ApiQuery({ name: 'deleteDossier', required: false, description: 'Si true et dernier enfant : cascade dossier famille', }) remove( @Param('id', new ParseUUIDPipe()) id: string, @Query('deleteDossier') deleteDossier: string | undefined, @User() currentUser: Users, ) { const flag = deleteDossier === 'true' || deleteDossier === '1' || deleteDossier === 'yes'; return this.suppressionService.deleteEnfant(id, flag, currentUser); } }