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
78 lines
2.9 KiB
TypeScript
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();
|