import './telemetry'; import { Logger, ValidationPipe } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { NestFactory } from '@nestjs/core'; import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger'; import { NextFunction, Request, Response } from 'express'; import { AppModule } from './app.module'; import { API_KEY_HEADER } from './common/constants/app.constants'; import { OpenTelemetryNestLogger } from './otel-nest-logger'; import { buildValidationErrorResponse } from './common/helpers/validation-error.helper'; import { AppException } from './common/exceptions/app-exception'; import { recordHttpRequestEnd, recordHttpRequestStart, shutdownTelemetry, } from './telemetry'; async function bootstrap(): Promise { const logger = new OpenTelemetryNestLogger(); Logger.overrideLogger(logger); const app = await NestFactory.create(AppModule, { logger }); const configService = app.get(ConfigService); const port = configService.get('app.port', 8085); app.use((req: Request, res: Response, next: NextFunction) => { const start = Date.now(); const attributes = { 'http.request.method': req.method, 'http.route': req.path, }; recordHttpRequestStart(attributes); res.on('finish', () => { recordHttpRequestEnd(Date.now() - start, { ...attributes, 'http.response.status_code': res.statusCode, }); }); next(); }); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, transformOptions: { enableImplicitConversion: true }, exceptionFactory: (errors) => { const { normalizedError } = buildValidationErrorResponse(errors); return new AppException('VALIDATION_ERROR', { message: normalizedError.message, messageFa: normalizedError.messageFa, details: normalizedError.details, }); }, }), ); const baseUrlProd = process.env.BASE_URL_PROD; const clientUrl = process.env.CLIENT_URL; const documentBuilder = new DocumentBuilder() .setTitle('External Services Gateway') .setDescription( 'Unified gateway for external inquiry providers with JWT authentication, RBAC, ' + 'per-user inquiry access, audit logging, and normalized inquiry responses.', ) .setVersion('1.0') .addServer("http://localhost:8085") .addServer("http://192.168.20.22:8085"); if (clientUrl) { documentBuilder.addServer(clientUrl); } if (baseUrlProd) { documentBuilder.addServer(baseUrlProd); } const swaggerConfig = documentBuilder .addBearerAuth( { type: 'http', scheme: 'bearer', bearerFormat: 'JWT', in: 'header' }, 'bearer', ) .addApiKey({ type: 'apiKey', name: API_KEY_HEADER, in: 'header' }, 'api-key') .build(); const document = SwaggerModule.createDocument(app, swaggerConfig); SwaggerModule.setup('api/docs', app, document); await app.listen(port); Logger.log(`Inquiry Gateway running on http://localhost:${port}`); Logger.log(`Swagger docs: http://localhost:${port}/api/docs`); const shutdown = async (signal: NodeJS.Signals): Promise => { Logger.log(`Received ${signal}, shutting down`); await app.close(); await shutdownTelemetry(); process.exit(0); }; process.once('SIGINT', shutdown); process.once('SIGTERM', shutdown); } bootstrap().catch(async (error: unknown) => { Logger.error('Application failed to start', error instanceof Error ? error.stack : String(error)); await shutdownTelemetry(); process.exit(1); });