import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { AppModule } from './app.module'; import { ConfigService } from '@nestjs/config'; import { SwaggerModule } from '@nestjs/swagger/dist/swagger-module'; 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'; /** GET/HEAD photos : CORS + CORP pour Flutter web (cross-origin `Image.network`). Pas de cookie sur ces URLs. */ function setStaticImageCorsHeaders(res: express.Response): void { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin'); } const staticImageServeOptions = { index: false, fallthrough: true, setHeaders: (res: express.Response) => setStaticImageCorsHeaders(res), }; /** Préflight si le navigateur interroge OPTIONS sur les médias. */ function uploadsCorsPreflight( req: express.Request, res: express.Response, next: express.NextFunction, ): void { if (req.method === 'OPTIONS') { setStaticImageCorsHeaders(res); res.setHeader('Access-Control-Allow-Methods', 'GET, HEAD, OPTIONS'); res.setHeader('Access-Control-Max-Age', '86400'); res.status(204).end(); return; } next(); } /** Répertoire disque contenant le dossier `photos` (ex. /app/uploads si photos dans /app/uploads/photos). */ function resolveUploadsFilesystemRoot(): string { const raw = process.env.UPLOAD_PHOTOS_DIR?.trim(); const photosDir = raw ? path.isAbsolute(raw) ? raw : path.resolve(process.cwd(), raw) : path.join(process.cwd(), 'uploads', 'photos'); return path.dirname(photosDir); } async function bootstrap() { const app = await NestFactory.create(AppModule, { logger: ['error', 'warn', 'log', 'debug', 'verbose'], }); // Inscription (photos base64 dans le JSON) : sans limite > défaut Express (~100 ko) → 413/500 ou corps tronqué. app.useBodyParser('json', { limit: '15mb' }); app.useBodyParser('urlencoded', { extended: true, limit: '15mb' }); // CORS global **avant** les fichiers statiques pour que les réponses API et idéalement la chaîne Express restent cohérentes. app.enableCors({ origin: true, // Reflète l’Origin (dev / prod) — routes API + cookies JWT methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'], allowedHeaders: ['Content-Type', 'Authorization', 'Accept'], credentials: true, }); const expressApp = app.getHttpAdapter().getInstance(); const uploadsRoot = resolveUploadsFilesystemRoot(); // Photos : chemins stockés en base type /uploads/photos/... // En-têtes explicites sur les réponses fichier (les statics peuvent répondre sans repasser par la même couche que les contrôleurs). expressApp.use('/uploads', uploadsCorsPreflight); expressApp.use('/api/v1/uploads', uploadsCorsPreflight); app.useStaticAssets(uploadsRoot, { prefix: '/uploads/', ...staticImageServeOptions, }); expressApp.use('/api/v1/uploads', express.static(uploadsRoot, staticImageServeOptions)); // Log de chaque appel API si LOG_API_REQUESTS=true (mode debug) app.useGlobalInterceptors(new LogRequestInterceptor()); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }) ); const configService = app.get(ConfigService); const port = configService.get('app.port', 3000); app.setGlobalPrefix('api/v1'); const config = new DocumentBuilder() .setTitle("P'titsPas API") .setDescription("API pour l'application P'titsPas") .setVersion('1.0.0') .addBearerAuth( { type: 'http', scheme: 'Bearer', bearerFormat: 'JWT', name: 'Authorization', description: 'Enter JWT token', in: 'header', }, 'access-token', ) //.addServer('/api/v1') .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/v1/swagger', app, document); await app.listen(port); console.log(`✅ P'titsPas API is running on: ${await app.getUrl()}`); } bootstrap().catch((err) => { console.error('❌ Error starting the application:', err); process.exit(1); });