import "reflect-metadata"; import { NestFactory } from "@nestjs/core"; import { FastifyAdapter, type NestFastifyApplication } from "@nestjs/platform-fastify"; import { IoAdapter } from "@nestjs/platform-socket.io"; import { ValidationPipe } from "@nestjs/common"; import helmet from "@fastify/helmet"; import cors from "@fastify/cors"; import rateLimit from "@fastify/rate-limit"; import { AppModule } from "./app.module"; import { DomainExceptionFilter } from "./common/filters/domain-exception.filter"; import { runRetentionSweep } from "./recordings/retention-sweep"; import { runActiveDaySweep } from "./billing/active-day-sweep"; const RETENTION_SWEEP_INTERVAL_MS = 60 * 60 * 1000; const ACTIVE_DAY_SWEEP_INTERVAL_MS = 60 * 60 * 1000; async function bootstrap() { const app = await NestFactory.create( AppModule, new FastifyAdapter({ trustProxy: true }), ); // HTTP security headers (agente.md secao 182). await app.register(helmet); // CORS restritivo: só a origem do frontend, configurável via env. Nunca // "*" — dados de tenant nunca devem ser acessíveis por qualquer origem. const corsOrigin = process.env.CORS_ORIGIN; await app.register(cors, { origin: corsOrigin ? corsOrigin.split(",") : false, credentials: true, }); // Rate limit global de defesa em profundidade; o login tem um limite mais // estrito aplicado no próprio controller (agente.md secao 149). await app.register(rateLimit, { max: 300, timeWindow: "1 minute", }); app.useGlobalPipes( new ValidationPipe({ whitelist: true, forbidNonWhitelisted: true, transform: true, }), ); app.useGlobalFilters(new DomainExceptionFilter()); // Monitoramento em tempo real (agente.md secao 54-55, 161) — socket.io // sobre o mesmo servidor HTTP do Fastify, path proprio pra nao colidir // com as rotas REST. app.useWebSocketAdapter(new IoAdapter(app)); const port = Number(process.env.API_PORT ?? 3000); await app.listen(port, "127.0.0.1"); console.log(`b2bcall-api ouvindo em http://127.0.0.1:${port}`); // Retenção de gravações (agente.md secao 94: "scheduler deverá aplicar // retenção"). apps/api já é um processo de longa duração — não precisa // de um serviço dedicado só pra isso; roda uma vez no boot e depois de // hora em hora. runRetentionSweep().catch((err) => console.error("falha na varredura de retencao (boot)", err)); setInterval(() => { runRetentionSweep().catch((err) => console.error("falha na varredura de retencao", err)); }, RETENTION_SWEEP_INTERVAL_MS); // Usage metering diario (agente.md secao 131: EXTENSION/AGENT/TRUNK // ACTIVE_DAY) — mesmo padrao da varredura de retencao acima. runActiveDaySweep().catch((err) => console.error("falha na varredura de uso diario (boot)", err)); setInterval(() => { runActiveDaySweep().catch((err) => console.error("falha na varredura de uso diario", err)); }, ACTIVE_DAY_SWEEP_INTERVAL_MS); } bootstrap();