import { NestFactory } from '@nestjs/core'; import { ValidationPipe, Logger as NestLogger } from '@nestjs/common'; import { ConfigService } from '@nestjs/config'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; import { AppModule } from './app.module'; import helmet from 'helmet'; import { Logger, LoggerErrorInterceptor } from 'nestjs-pino'; import * as express from 'express'; import * as path from 'path'; // Prisma BigInt alanları JSON'a serialize edilemiyor — global polyfill // MediaAsset.sizeBytes gibi alanlar BigInt tipinde (BigInt.prototype as any).toJSON = function () { return Number(this); }; async function bootstrap() { const logger = new NestLogger('Bootstrap'); logger.log('🔄 ContentGen AI başlatılıyor...'); const app = await NestFactory.create(AppModule, { bufferLogs: true, rawBody: true, // Stripe webhook imza doğrulaması için gerekli }); // Use Pino Logger app.useLogger(app.get(Logger)); app.useGlobalInterceptors(new LoggerErrorInterceptor()); // Security Headers app.use( helmet({ contentSecurityPolicy: false, crossOriginEmbedderPolicy: false, crossOriginResourcePolicy: { policy: 'cross-origin' }, }), ); // Graceful Shutdown (Prisma & Docker) app.enableShutdownHooks(); // Get config service const configService = app.get(ConfigService); const port = configService.get('PORT', 3000); const nodeEnv = configService.get('NODE_ENV', 'development'); // ── Static File Serving — Medya dosyalarına HTTP erişim ── const mediaPath = configService.get( 'STORAGE_LOCAL_PATH', './data/media', ); const absoluteMediaPath = path.resolve(mediaPath); // Medya dosyaları için CORS header'ları (Frontend farklı port'ta çalışıyor) app.use('/media', (req: any, res: any, next: any) => { res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin'); next(); }); app.use( '/media', express.static(absoluteMediaPath, { maxAge: '1d', etag: true, lastModified: true, index: false, dotfiles: 'deny', }), ); logger.log(`📂 Medya dizini: ${absoluteMediaPath} → /media/*`); // Enable CORS app.enableCors({ origin: true, credentials: true, }); // Global prefix app.setGlobalPrefix('api'); // Validation pipe (Strict) app.useGlobalPipes( new ValidationPipe({ whitelist: true, transform: true, forbidNonWhitelisted: true, transformOptions: { enableImplicitConversion: true, }, }), ); // Swagger setup const swaggerConfig = new DocumentBuilder() .setTitle('ContentGen AI — Video Generation SaaS API') .setDescription( 'AI destekli video üretim platformu. Senaryo oluşturma, medya üretimi, render pipeline ve billing yönetimi.', ) .setVersion('1.0.0') .addBearerAuth() .addTag('Auth', 'Kimlik doğrulama') .addTag('Users', 'Kullanıcı yönetimi') .addTag('Projects', 'Proje ve senaryo yönetimi') .addTag('Dashboard', 'İstatistikler ve grafikler') .addTag('Billing', 'Abonelik ve kredi yönetimi') .addTag('Templates', 'Şablon pazaryeri') .addTag('Notifications', 'Bildirim yönetimi') .addTag('Admin', 'Yönetici paneli') .addTag('Health', 'Sistem sağlık kontrolü') .build(); logger.log('Swagger başlatılıyor...'); const document = SwaggerModule.createDocument(app, swaggerConfig); SwaggerModule.setup('api/docs', app, document, { swaggerOptions: { persistAuthorization: true, }, }); logger.log('Swagger hazır'); logger.log(`Port ${port} üzerinde dinleniyor...`); await app.listen(port, '0.0.0.0'); logger.log('═══════════════════════════════════════════════════════════'); logger.log(`🚀 ContentGen AI API: http://localhost:${port}/api`); logger.log(`📚 Swagger Docs: http://localhost:${port}/api/docs`); logger.log(`💚 Health Check: http://localhost:${port}/api/health`); logger.log(`📂 Medya Dosyaları: http://localhost:${port}/media/`); logger.log(`🌍 Ortam: ${nodeEnv.toUpperCase()}`); logger.log('═══════════════════════════════════════════════════════════'); if (nodeEnv === 'development') { logger.warn('⚠️ Geliştirme modunda çalışıyor'); } } void bootstrap();