fix: photos admin — URL via /api/v1/uploads + static Express
- Nest: servir le volume uploads aussi sous /api/v1/uploads (Traefik /api) - Flutter: _fullPhotoUrl = base API + chemin /uploads/... (plus d’origine site seul) - EnfantDossier: tolérer photoUrl camelCase Made-with: Cursor
This commit is contained in:
parent
e7f0037ac3
commit
09289882d0
@ -7,6 +7,7 @@ import { DocumentBuilder } from '@nestjs/swagger';
|
|||||||
import { ValidationPipe } from '@nestjs/common';
|
import { ValidationPipe } from '@nestjs/common';
|
||||||
import { LogRequestInterceptor } from './common/interceptors/log-request.interceptor';
|
import { LogRequestInterceptor } from './common/interceptors/log-request.interceptor';
|
||||||
import * as path from 'path';
|
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). */
|
/** Répertoire disque contenant le dossier `photos` (ex. /app/uploads si photos dans /app/uploads/photos). */
|
||||||
function resolveUploadsFilesystemRoot(): string {
|
function resolveUploadsFilesystemRoot(): string {
|
||||||
@ -28,12 +29,22 @@ async function bootstrap() {
|
|||||||
app.useBodyParser('json', { limit: '15mb' });
|
app.useBodyParser('json', { limit: '15mb' });
|
||||||
app.useBodyParser('urlencoded', { extended: true, limit: '15mb' });
|
app.useBodyParser('urlencoded', { extended: true, limit: '15mb' });
|
||||||
|
|
||||||
// Servir les fichiers sous /uploads/... (photos enfants / AM). Traefik doit router /uploads vers l’API.
|
// Photos : chemins stockés en base type /uploads/photos/...
|
||||||
app.useStaticAssets(resolveUploadsFilesystemRoot(), {
|
// - /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/',
|
prefix: '/uploads/',
|
||||||
index: false,
|
index: false,
|
||||||
fallthrough: true,
|
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)
|
// Log de chaque appel API si LOG_API_REQUESTS=true (mode debug)
|
||||||
app.useGlobalInterceptors(new LogRequestInterceptor());
|
app.useGlobalInterceptors(new LogRequestInterceptor());
|
||||||
|
|||||||
@ -203,7 +203,7 @@ class EnfantDossier {
|
|||||||
gender: (json['gender'] ?? json['genre'])?.toString(),
|
gender: (json['gender'] ?? json['genre'])?.toString(),
|
||||||
status: json['status']?.toString(),
|
status: json['status']?.toString(),
|
||||||
dueDate: json['due_date']?.toString(),
|
dueDate: json['due_date']?.toString(),
|
||||||
photoUrl: json['photo_url']?.toString(),
|
photoUrl: (json['photo_url'] ?? json['photoUrl'])?.toString(),
|
||||||
consentPhoto: json['consent_photo'] == true,
|
consentPhoto: json['consent_photo'] == true,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -115,9 +115,8 @@ class _ValidationAmWizardState extends State<ValidationAmWizard> {
|
|||||||
if (url == null || url.trim().isEmpty) return '';
|
if (url == null || url.trim().isEmpty) return '';
|
||||||
final u = url.trim();
|
final u = url.trim();
|
||||||
if (u.startsWith('http://') || u.startsWith('https://')) return u;
|
if (u.startsWith('http://') || u.startsWith('https://')) return u;
|
||||||
final base = ApiConfig.baseUrl;
|
final base = ApiConfig.baseUrl.replaceAll(RegExp(r'/+$'), '');
|
||||||
final origin = base.replaceAll(RegExp(r'/api/v1.*'), '');
|
return u.startsWith('/') ? '$base$u' : '$base/$u';
|
||||||
return u.startsWith('/') ? '$origin$u' : '$origin/$u';
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildPhotoSection(AppUser u) {
|
Widget _buildPhotoSection(AppUser u) {
|
||||||
|
|||||||
@ -138,9 +138,9 @@ class _ValidationFamilyWizardState extends State<ValidationFamilyWizard> {
|
|||||||
if (url == null || url.trim().isEmpty) return '';
|
if (url == null || url.trim().isEmpty) return '';
|
||||||
final u = url.trim();
|
final u = url.trim();
|
||||||
if (u.startsWith('http://') || u.startsWith('https://')) return u;
|
if (u.startsWith('http://') || u.startsWith('https://')) return u;
|
||||||
final base = ApiConfig.baseUrl;
|
// Même origine que l’API : /uploads est servi par Nest sous /api/v1/uploads/ (Traefik n’expose souvent que /api).
|
||||||
final origin = base.replaceAll(RegExp(r'/api/v1.*'), '');
|
final base = ApiConfig.baseUrl.replaceAll(RegExp(r'/+$'), '');
|
||||||
return u.startsWith('/') ? '$origin$u' : '$origin/$u';
|
return u.startsWith('/') ? '$base$u' : '$base/$u';
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user