import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { AppModule } from './app.module'; import { join } from 'path'; import { ValidationPipe, BadRequestException } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { CustomHttpExceptionFilter } from './common/filters/http-exception.filter'; import { PrismaExceptionFilter } from './common/filters/prisma-exception.filter'; import { DecimalInterceptor } from './common/interceptors/decimal.interceptor'; import helmet from 'helmet'; async function bootstrap() { // Fallback defaults for development environment if (process.env.NODE_ENV !== 'production') { process.env.JWT_ACCESS_SECRET = process.env.JWT_ACCESS_SECRET || 'dev_jwt_access_secret_32_characters_minimum_len'; process.env.JWT_REFRESH_SECRET = process.env.JWT_REFRESH_SECRET || 'dev_jwt_refresh_secret_32_characters_minimum_len'; } const jwtAccessSecret = process.env.JWT_ACCESS_SECRET; const jwtRefreshSecret = process.env.JWT_REFRESH_SECRET; if (!jwtAccessSecret || jwtAccessSecret.trim().length < 32) { console.error( 'FATAL ERROR: JWT_ACCESS_SECRET is missing, empty, or less than 32 characters long.', ); process.exit(1); } if (!jwtRefreshSecret || jwtRefreshSecret.trim().length < 32) { console.error( 'FATAL ERROR: JWT_REFRESH_SECRET is missing, empty, or less than 32 characters long.', ); process.exit(1); } const app = await NestFactory.create(AppModule); // Serve static uploads folder app.useStaticAssets(join(process.cwd(), 'uploads'), { prefix: '/uploads/', }); app.use( helmet({ contentSecurityPolicy: false, // Avoid blocking Swagger UI scripts and assets }), ); app.setGlobalPrefix('api'); app.enableCors({ origin: true, credentials: true, }); app.useGlobalPipes( new ValidationPipe({ transform: true, whitelist: true, forbidNonWhitelisted: true, exceptionFactory: (errors) => { const formattedDetails = errors.map((err) => { const constraints = err.constraints ? Object.values(err.constraints) : []; return { field: err.property, message: constraints.length > 0 ? constraints[0] : 'مقدار وارد شده نامعتبر است.', }; }); return new BadRequestException({ message: 'اطلاعات ورودی فرم معتبر نمی‌باشد. لطفاً موارد مشخص شده را اصلاح نمایید.', error: 'INVALID_INPUT_DATA', details: formattedDetails, }); }, }), ); app.useGlobalFilters( new CustomHttpExceptionFilter(), new PrismaExceptionFilter(), ); app.useGlobalInterceptors(new DecimalInterceptor()); const isProduction = process.env.NODE_ENV === 'production'; const enableSwagger = process.env.ENABLE_SWAGGER === 'true'; if (!isProduction || enableSwagger) { const config = new DocumentBuilder() .setTitle('Canina Iran API') .setDescription( 'API Documentation for Canina Iran Pet Health & Supplement Platform', ) .setVersion('1.0.0') .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, document); console.log('Swagger UI is ACTIVE on /api/docs'); } else { console.log('Swagger UI is DISABLED for security (production environment)'); } await app.listen(process.env.PORT ?? 4001); } bootstrap().catch((err: unknown) => { console.error('Bootstrap error:', err); process.exit(1); });