Files
esg/src/main.ts

114 lines
3.5 KiB
TypeScript

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<void> {
const logger = new OpenTelemetryNestLogger();
Logger.overrideLogger(logger);
const app = await NestFactory.create(AppModule, { logger });
const configService = app.get(ConfigService);
const port = configService.get<number>('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<void> => {
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);
});