Files
B2BCall-dialer/apps/api/src/main.ts
Matheus b27cfaab02 feat(billing): rating engine, fechamento de periodo, dashboard platform (fase 22)
Fecha a orquestracao de Billing (agente.md secao 120-139) sobre o schema/
RatingEngine puro ja existentes: escritores do ledger UsageEvent
(CALL_SECONDS no CDR, ACTIVE_DAY via sweep diario), closeBillingPeriod/
reopenBillingPeriod (fechamento imutavel com audit trail), e os
controllers de price books/rate decks/plan versions/subscriptions/
periods/statements. Corrige 2 bugs reais de RLS achados no teste ponta a
ponta (reopen sem tenant context, subscriptions sem withTenantContext) e
adiciona teste unitario do RatingEngine (17 casos).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EWHKmcVJtstQFErbZ1AanY
2026-08-29 00:28:35 -03:00

78 lines
2.9 KiB
TypeScript

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<NestFastifyApplication>(
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();