diff --git a/backend/src/main.ts b/backend/src/main.ts index 9c192d2..27a2863 100644 --- a/backend/src/main.ts +++ b/backend/src/main.ts @@ -7,6 +7,7 @@ import { DocumentBuilder } from '@nestjs/swagger'; import { ValidationPipe } from '@nestjs/common'; import { LogRequestInterceptor } from './common/interceptors/log-request.interceptor'; import * as path from 'path'; +import * as express from 'express'; /** Répertoire disque contenant le dossier `photos` (ex. /app/uploads si photos dans /app/uploads/photos). */ function resolveUploadsFilesystemRoot(): string { @@ -28,12 +29,22 @@ async function bootstrap() { app.useBodyParser('json', { limit: '15mb' }); app.useBodyParser('urlencoded', { extended: true, limit: '15mb' }); - // Servir les fichiers sous /uploads/... (photos enfants / AM). Traefik doit router /uploads vers l’API. - app.useStaticAssets(resolveUploadsFilesystemRoot(), { + // Photos : chemins stockés en base type /uploads/photos/... + // - /uploads/ : si le proxy route ce préfixe vers Nest. + // - /api/v1/uploads/ : même trafic que l’API (Traefik PathPrefix /api → pas besoin de règle /uploads). + const uploadsRoot = resolveUploadsFilesystemRoot(); + app.useStaticAssets(uploadsRoot, { prefix: '/uploads/', index: false, fallthrough: true, }); + app + .getHttpAdapter() + .getInstance() + .use( + '/api/v1/uploads', + express.static(uploadsRoot, { index: false, fallthrough: true }), + ); // Log de chaque appel API si LOG_API_REQUESTS=true (mode debug) app.useGlobalInterceptors(new LogRequestInterceptor()); diff --git a/frontend/lib/models/dossier_unifie.dart b/frontend/lib/models/dossier_unifie.dart index 9dd4ccd..ae62b26 100644 --- a/frontend/lib/models/dossier_unifie.dart +++ b/frontend/lib/models/dossier_unifie.dart @@ -203,7 +203,7 @@ class EnfantDossier { gender: (json['gender'] ?? json['genre'])?.toString(), status: json['status']?.toString(), dueDate: json['due_date']?.toString(), - photoUrl: json['photo_url']?.toString(), + photoUrl: (json['photo_url'] ?? json['photoUrl'])?.toString(), consentPhoto: json['consent_photo'] == true, ); } diff --git a/frontend/lib/widgets/admin/validation_am_wizard.dart b/frontend/lib/widgets/admin/validation_am_wizard.dart index e15bae5..2c1ae05 100644 --- a/frontend/lib/widgets/admin/validation_am_wizard.dart +++ b/frontend/lib/widgets/admin/validation_am_wizard.dart @@ -115,9 +115,8 @@ class _ValidationAmWizardState extends State { if (url == null || url.trim().isEmpty) return ''; final u = url.trim(); if (u.startsWith('http://') || u.startsWith('https://')) return u; - final base = ApiConfig.baseUrl; - final origin = base.replaceAll(RegExp(r'/api/v1.*'), ''); - return u.startsWith('/') ? '$origin$u' : '$origin/$u'; + final base = ApiConfig.baseUrl.replaceAll(RegExp(r'/+$'), ''); + return u.startsWith('/') ? '$base$u' : '$base/$u'; } Widget _buildPhotoSection(AppUser u) { diff --git a/frontend/lib/widgets/admin/validation_family_wizard.dart b/frontend/lib/widgets/admin/validation_family_wizard.dart index de89bd9..2bbe511 100644 --- a/frontend/lib/widgets/admin/validation_family_wizard.dart +++ b/frontend/lib/widgets/admin/validation_family_wizard.dart @@ -138,9 +138,9 @@ class _ValidationFamilyWizardState extends State { if (url == null || url.trim().isEmpty) return ''; final u = url.trim(); if (u.startsWith('http://') || u.startsWith('https://')) return u; - final base = ApiConfig.baseUrl; - final origin = base.replaceAll(RegExp(r'/api/v1.*'), ''); - return u.startsWith('/') ? '$origin$u' : '$origin/$u'; + // Même origine que l’API : /uploads est servi par Nest sous /api/v1/uploads/ (Traefik n’expose souvent que /api). + final base = ApiConfig.baseUrl.replaceAll(RegExp(r'/+$'), ''); + return u.startsWith('/') ? '$base$u' : '$base/$u'; } @override