gohorsejobs/backoffice/src/main.ts

104 lines
3.4 KiB
TypeScript

import { NestFactory } from '@nestjs/core';
import {
FastifyAdapter,
NestFastifyApplication,
} from '@nestjs/platform-fastify';
import { ValidationPipe, Logger } from '@nestjs/common';
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
import { AppModule } from './app.module';
import compression from '@fastify/compress';
import helmet from '@fastify/helmet';
import cookie from '@fastify/cookie';
async function bootstrap() {
const dbUrl = process.env.DATABASE_URL || 'NOT_SET';
console.log(`[DEBUG] DATABASE_URL loaded: ${dbUrl.replace(/:[^:]*@/, ':***@')}`); // Mask password
// Create Fastify adapter with Pino logging
const adapter = new FastifyAdapter({
logger: {
level: process.env.NODE_ENV === 'production' ? 'info' : 'debug',
transport:
process.env.NODE_ENV !== 'production'
? { target: 'pino-pretty', options: { colorize: true } }
: undefined,
},
trustProxy: true, // Required for getting real IP behind reverse proxy
bodyLimit: 10 * 1024 * 1024, // 10MB
});
const app = await NestFactory.create<NestFastifyApplication>(
AppModule,
adapter,
{ rawBody: true },
);
// Register Fastify plugins
await app.register(compression, { encodings: ['gzip', 'deflate'] });
await app.register(helmet, {
contentSecurityPolicy: process.env.NODE_ENV === 'production',
});
await app.register(cookie); // Enable cookie parsing for JWT auth
// CORS configuration (Fastify-native)
app.enableCors({
origin: (origin, callback) => {
// Parse CORS_ORIGINS from env (comma-separated)
const envOrigins = process.env.CORS_ORIGINS?.split(',').map(o => o.trim()) || [];
const allowedOrigins = [
'http://localhost:3000',
'http://localhost:8963',
...envOrigins,
].filter(Boolean);
// Allow all *.gohorsejobs.com subdomains (http and https)
const gohorsePattern = /^https?:\/\/([a-z0-9-]+\.)*gohorsejobs\.com$/;
if (!origin || allowedOrigins.includes(origin) || gohorsePattern.test(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'), false);
}
},
credentials: true,
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
maxAge: 86400, // 24 hours preflight cache
});
// Global validation pipe
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
transform: true,
forbidNonWhitelisted: true,
transformOptions: { enableImplicitConversion: true },
}),
);
// Swagger documentation
const config = new DocumentBuilder()
.setTitle('GoHorse Backoffice API')
.setDescription('SaaS Administration and Subscription Management')
.setVersion('1.0')
.addTag('Stripe')
.addTag('Plans')
.addTag('Admin Dashboard')
.addBearerAuth()
.build();
SwaggerModule.setup('docs', app, SwaggerModule.createDocument(app, config));
// Start server
const port = process.env.BACKOFFICE_PORT || 3001;
const host = '0.0.0.0'; // Force 0.0.0.0 to allow container binding even if env injects public IP
await app.listen(port, host);
const logger = new Logger('Bootstrap');
logger.log(`🚀 Backoffice API running on: http://${host}:${port}`);
logger.log(`📚 Swagger docs: http://${host}:${port}/api/docs`);
logger.log(`❤️ Health check: http://${host}:${port}/health`);
}
void bootstrap();