import { execFileSync } from 'node:child_process'; import cookieParser from 'cookie-parser'; import { NestFactory } from '@nestjs/core'; import type { NestExpressApplication } from '@nestjs/platform-express'; import { apiEnvSchema, parseEnv } from '@dorfteich/shared'; import { Logger } from 'nestjs-pino'; import { AppModule } from './app.module'; import { AppConfig } from './config/app-config.service'; /** * Apply pending migrations before the application accepts traffic, so * self-hosters update by simply pulling a new image (ADR 0002). Prisma * serializes concurrent deploys with a database advisory lock. */ function runMigrations(): void { const prismaCli = require.resolve('prisma/build/index.js'); execFileSync(process.execPath, [prismaCli, 'migrate', 'deploy'], { stdio: 'inherit' }); } async function bootstrap(): Promise { // Validate the environment before doing anything with it; this throws a // readable list of problems and prevents a half-started process. const env = parseEnv(apiEnvSchema, process.env); if (env.MIGRATE_ON_START) { runMigrations(); } // bufferLogs holds early log lines until the pino logger is attached, // so even bootstrap errors come out as structured JSON. const app = await NestFactory.create(AppModule, { bufferLogs: true }); app.useLogger(app.get(Logger)); // One reverse-proxy hop (Caddy) in front of us: req.ip must reflect the // real client for rate limiting and audit logs. app.set('trust proxy', 1); app.use(cookieParser()); // Base64-encoded Yjs page state (max 5 MiB, operations.md) inflates by // ~4/3; 8 MiB leaves headroom for the JSON envelope around it. app.useBodyParser('json', { limit: '8mb' }); app.setGlobalPrefix('api/v1'); app.enableShutdownHooks(); const config = app.get(AppConfig); await app.listen(config.env.PORT); } void bootstrap();