import { NestFactory } from '@nestjs/core'; import { NestExpressApplication } from '@nestjs/platform-express'; import { ValidationPipe } from '@nestjs/common'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import helmet from 'helmet'; import * as cookieParserModule from 'cookie-parser'; import { json } from 'express'; import { join } from 'path'; import { AppModule } from './app.module'; import { HttpExceptionFilter } from './common/filters/http-exception.filter'; import { TransformInterceptor } from './common/interceptors/transform.interceptor'; async function bootstrap() { const app = await NestFactory.create(AppModule, { bodyParser: false }); // Replace Express's JSON parser with one that stringifies BigInt. // Express's default `json()` throws "Do not know how to serialize a BigInt". app.use( json({ limit: '2mb', reviver: (_key: string, value: unknown) => { // If a numeric string would overflow the JS number range we keep // it as a string; the JWT strategy / services expect bigints. if (typeof value === 'string' && /^-?\d{16,}$/.test(value)) { // Leave as string — services parse with BigInt(). return value; } return value; }, }), ); // Security headers (X-Content-Type-Options, X-Frame-Options, CSP, HSTS, ...) // Static assets (/uploads, /assets) are embedded cross-origin by other // sites, so CORP must allow cross-origin reads. app.use(helmet({ crossOriginResourcePolicy: { policy: 'cross-origin' } })); // Parse auth cookies (HttpOnly access/refresh tokens). Resolve both the // namespace and its `default` interop shape so it works regardless of // the compiled module interop mode. const cookieParser = ( cookieParserModule as unknown as { default?: typeof cookieParserModule; } ).default ?? cookieParserModule; app.use(cookieParser()); // CORS: only origins listed in CORS_ORIGINS (comma-separated) are // allowed. Credentials are enabled because the session lives in // HttpOnly cookies. "*" disables the allowlist and reflects any origin // (reflected origins are required when credentials are enabled). const corsOrigins = (process.env.CORS_ORIGINS ?? '') .split(',') .map((o) => o.trim()) .filter(Boolean); const origin = corsOrigins.includes('*') ? true : corsOrigins; app.enableCors(corsOrigins.length > 0 ? { origin, credentials: true } : undefined); // Serve uploaded files. nosniff prevents browsers from sniffing a // non-image content type out of an uploaded file. app.useStaticAssets(join(process.cwd(), 'uploads'), { prefix: '/uploads/', setHeaders: (res) => { res.setHeader('X-Content-Type-Options', 'nosniff'); }, }); app.useStaticAssets(join(process.cwd(), 'public'), { prefix: '/assets/', }); // Global pipes app.useGlobalPipes( new ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: true, }), ); // Global filters and interceptors app.useGlobalFilters(new HttpExceptionFilter()); app.useGlobalInterceptors(new TransformInterceptor()); // Swagger is only exposed outside production to avoid leaking the // full admin API surface. if (process.env.NODE_ENV !== 'production') { const config = new DocumentBuilder() .setTitle('InkReach Product Center API') .setDescription('Backend API for InkReach Product Center') .setVersion('1.0') .addBearerAuth() .build(); const document = SwaggerModule.createDocument(app, config); SwaggerModule.setup('api/docs', app, document); } const port = process.env.PORT ?? 3001; await app.listen(port, '0.0.0.0'); console.log(`🚀 Application is running on: http://0.0.0.0:${port}`); } // Make JSON.stringify aware of BigInt so outgoing responses containing // primary keys (`BigInt` columns) don't blow up. BigInts are serialized // as their decimal string — clients should parse them with BigInt(). (BigInt.prototype as unknown as { toJSON: () => string }).toJSON = function () { return this.toString(); }; bootstrap();