xpeditis2.0/apps/backend/src/main.ts
David d65cb721b5
Some checks are pending
CD Production (Hetzner k3s) / Promote Images (preprod → prod) (push) Waiting to run
CD Production (Hetzner k3s) / Deploy to k3s (xpeditis-prod) (push) Blocked by required conditions
CD Production (Hetzner k3s) / Smoke Tests (push) Blocked by required conditions
CD Production (Hetzner k3s) / Deployment Summary (push) Blocked by required conditions
CD Production (Hetzner k3s) / Notify Success (push) Blocked by required conditions
CD Production (Hetzner k3s) / Notify Failure (push) Blocked by required conditions
chore: sync full codebase from cicd branch
Aligns main with the complete application codebase (cicd branch).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 12:56:44 +02:00

131 lines
5.1 KiB
TypeScript

import { NestFactory } from '@nestjs/core';
import { ValidationPipe, VersioningType } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { ConfigService } from '@nestjs/config';
import helmet from 'helmet';
import compression from 'compression';
import { AppModule } from './app.module';
import { Logger } from 'nestjs-pino';
import { helmetConfig, corsConfig } from './infrastructure/security/security.config';
import type { Request, Response, NextFunction } from 'express';
async function bootstrap() {
const app = await NestFactory.create(AppModule, {
bufferLogs: true,
// Enable rawBody for Stripe webhooks signature verification
rawBody: true,
});
// Get config service
const configService = app.get(ConfigService);
const port = configService.get<number>('PORT', 4000);
const apiPrefix = configService.get<string>('API_PREFIX', 'api/v1');
const isProduction = configService.get<string>('NODE_ENV') === 'production';
// Use Pino logger
app.useLogger(app.get(Logger));
// Security - Helmet with OWASP recommended headers
app.use(helmet(helmetConfig));
// Compression for API responses
app.use(compression());
// CORS with strict configuration
app.enableCors(corsConfig);
// Global prefix
app.setGlobalPrefix(apiPrefix);
// API versioning
app.enableVersioning({
type: VersioningType.URI,
});
// Global validation pipe
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
transformOptions: {
enableImplicitConversion: true,
},
})
);
// ─── Swagger documentation ────────────────────────────────────────────────
const swaggerUser = configService.get<string>('SWAGGER_USERNAME');
const swaggerPass = configService.get<string>('SWAGGER_PASSWORD');
const swaggerEnabled = !isProduction || (Boolean(swaggerUser) && Boolean(swaggerPass));
if (swaggerEnabled) {
// HTTP Basic Auth guard for Swagger routes when credentials are configured
if (swaggerUser && swaggerPass) {
const swaggerPaths = ['/api/docs', '/api/docs-json', '/api/docs-yaml'];
app.use(swaggerPaths, (req: Request, res: Response, next: NextFunction) => {
const authHeader = req.headers['authorization'];
if (!authHeader || !authHeader.startsWith('Basic ')) {
res.setHeader('WWW-Authenticate', 'Basic realm="Xpeditis API Docs"');
res.status(401).send('Authentication required');
return;
}
const decoded = Buffer.from(authHeader.slice(6), 'base64').toString('utf-8');
const colonIndex = decoded.indexOf(':');
const user = decoded.slice(0, colonIndex);
const pass = decoded.slice(colonIndex + 1);
if (user !== swaggerUser || pass !== swaggerPass) {
res.setHeader('WWW-Authenticate', 'Basic realm="Xpeditis API Docs"');
res.status(401).send('Invalid credentials');
return;
}
next();
});
}
const config = new DocumentBuilder()
.setTitle('Xpeditis API')
.setDescription(
'Maritime Freight Booking Platform - API for searching rates and managing bookings'
)
.setVersion('1.0')
.addBearerAuth()
.addApiKey({ type: 'apiKey', name: 'x-api-key', in: 'header' }, 'x-api-key')
.addTag('rates', 'Rate search and comparison')
.addTag('bookings', 'Booking management')
.addTag('auth', 'Authentication and authorization')
.addTag('users', 'User management')
.addTag('organizations', 'Organization management')
.build();
const document = SwaggerModule.createDocument(app, config);
SwaggerModule.setup('api/docs', app, document, {
customSiteTitle: 'Xpeditis API Documentation',
customfavIcon: 'https://xpeditis.com/favicon.ico',
customCss: '.swagger-ui .topbar { display: none }',
});
}
// ─────────────────────────────────────────────────────────────────────────
await app.listen(port);
const swaggerStatus = swaggerEnabled
? swaggerUser
? `http://localhost:${port}/api/docs (protected)`
: `http://localhost:${port}/api/docs (open — add SWAGGER_USERNAME/PASSWORD to secure)`
: 'disabled in production';
console.log(`
╔═══════════════════════════════════════════════╗
║ ║
║ 🚢 Xpeditis API Server Running ║
║ ║
║ API: http://localhost:${port}/${apiPrefix}
║ Docs: ${swaggerStatus}
║ ║
╚═══════════════════════════════════════════════╝
`);
}
bootstrap();