- packages/database: schema Prisma (users/sessions/roles/permissions/
user_roles/role_permissions/audit_logs/password_reset_tokens), migration
inicial e seed (permissoes+perfis+bootstrap super_admin com senha
aleatoria em FIRST_LOGIN.txt). Decisao de ORM (Prisma) documentada em
docs/ARCHITECTURE.md
- packages/shared: catalogo de permissoes (fonte unica usada por seed e API)
- apps/api: NestJS 11 + Fastify
- autenticacao: Argon2id, access JWT + refresh token opaco com rotacao,
cookies HttpOnly/SameSite=Lax, change/forgot/reset password
- rate limiting progressivo de login via Redis (bloqueio crescente por IP)
- RBAC reforcado no backend (PermissionsGuard), protecao contra
auto-elevacao de privilegio
- auditoria (audit_logs) nas acoes sensiveis, com redacao de segredos
- health checks reais (postgres+redis), swagger desabilitavel, logs
estruturados JSON com request_id de correlacao, filtro global de
excecoes sem vazar erro cru
- infrastructure/docker/api.Dockerfile: build multi-stage do monorepo pnpm
- docker-compose.yml: servico api na rede interna, sem porta publicada
Testado via containers reais: login, /me, refresh, change-password,
rate limit (7 tentativas -> 429), RBAC (nega/permite), bloqueio de
auto-elevacao (403), audit log populado, health checks, lint e testes
unitarios passando.
169 lines
8.1 KiB
Markdown
169 lines
8.1 KiB
Markdown
# B2BCall — Arquitetura
|
|
|
|
## 1. Contexto do servidor (levantado em 2026-08-27)
|
|
|
|
| Item | Valor |
|
|
|---|---|
|
|
| SO | Debian 13 (trixie) |
|
|
| Kernel | 6.12.94+deb13-amd64 |
|
|
| CPU | 2 vCPU |
|
|
| RAM | 1.9 GiB (+ 1.6 GiB swap) |
|
|
| Disco | 29 GiB (26 GiB livres) |
|
|
| IP | 10.10.32.142/24 (privado, sem IP público) |
|
|
| Hostname | Lab-FSCore-API |
|
|
|
|
**Alerta de capacidade:** 1.9 GiB de RAM é pouco para rodar simultaneamente Postgres +
|
|
Redis + Asterisk + API (NestJS) + worker + asterisk-events + scheduler + frontend
|
|
(Next.js) + Nginx. Decisões desta arquitetura foram tomadas para caber neste ambiente
|
|
de laboratório:
|
|
|
|
- Build do frontend fora do container em produção real (ou build multi-stage com
|
|
`node --max-old-space-size` limitado) — builds Next.js podem estourar RAM em VMs de
|
|
2 GiB.
|
|
- `docker-compose.yml` define `mem_limit`/`deploy.resources.limits` conservadores por
|
|
serviço.
|
|
- Antes de operação real de discagem em produção, **recomenda-se aumentar a VM para
|
|
pelo menos 4 vCPU / 8 GiB RAM**. Este ambiente serve para desenvolvimento e testes
|
|
funcionais (inclusive `DIALER_SIMULATION=true`), não para carga real de discador
|
|
preditivo com volume.
|
|
|
|
## 2. Estrutura do monorepo
|
|
|
|
```text
|
|
/opt/b2bcall
|
|
├── apps
|
|
│ ├── frontend # Next.js (App Router) + Tailwind + shadcn/ui
|
|
│ ├── api # NestJS (Fastify adapter) - REST + WebSocket gateway
|
|
│ ├── dialer-worker # PredictiveDialerEngine, CPS limiter, retry engine
|
|
│ ├── asterisk-events # Conector AMI dedicado -> normaliza eventos -> Postgres/Redis/WS
|
|
│ └── scheduler # Callbacks, horários de campanha, reconciliação, cron jobs
|
|
├── packages
|
|
│ ├── database # Prisma/Drizzle schema + migrations + client
|
|
│ ├── shared # utils comuns (logger, result types, error classes)
|
|
│ ├── types # tipos TS compartilhados (DTOs, enums de estado)
|
|
│ ├── ui # componentes de UI compartilhados (design system)
|
|
│ └── telephony # TelephonyProvider (interface) + AsteriskTelephonyProvider (AMI/ARI)
|
|
├── infrastructure
|
|
│ ├── asterisk # pjsip.conf, extensions, odbc, realtime, queues
|
|
│ ├── nginx # reverse proxy, websocket upgrade, TLS
|
|
│ ├── postgres # init scripts, schemas (application / asterisk)
|
|
│ └── docker # Dockerfiles por serviço
|
|
├── scripts # install.sh, update.sh, backup.sh, restore.sh, healthcheck.sh
|
|
├── docs
|
|
├── docker-compose.yml
|
|
├── .env.example
|
|
├── .gitignore
|
|
└── README.md
|
|
```
|
|
|
|
## 3. Decisões técnicas
|
|
|
|
### 3.1 Rede Docker / Asterisk
|
|
|
|
- Asterisk roda em `network_mode: host` — RTP usa uma faixa grande de portas UDP
|
|
(10000-20000 por padrão); mapear porta a porta em bridge network é inviável em
|
|
produção. **Impacto documentado:** o container do Asterisk enxerga a rede do host
|
|
diretamente (perde isolamento de rede do Docker); todas as portas do Asterisk
|
|
(SIP 5060/5061, RTP, AMI 5038, ARI 8088) ficam sujeitas apenas ao firewall do host
|
|
(nftables), não ao Docker networking. Por isso as regras de nftables (seção 8) são
|
|
a única linha de defesa e devem ser tratadas como obrigatórias, não opcionais.
|
|
- Todos os demais serviços (api, frontend, worker, asterisk-events, scheduler,
|
|
postgres, redis, nginx) ficam em uma rede Docker privada (`b2bcall-net`, bridge),
|
|
sem publicação de portas para o host exceto Nginx (80/443).
|
|
- Postgres e Redis não publicam portas no host; acessíveis somente dentro de
|
|
`b2bcall-net`.
|
|
|
|
### 3.2 Topologia SIP
|
|
|
|
```text
|
|
INTERNET → [OpenSIPS] → rede SIP privada → [Asterisk] → [B2BCall]
|
|
Asterisk → rede privada → Troncos SIP (operadoras)
|
|
```
|
|
|
|
O Asterisk nunca terá IP público. OpenSIPS e troncos são acessados via IP privado.
|
|
AMI/ARI nunca expostos além do host local (bind em 127.0.0.1 ou rede interna Docker
|
|
quando possível).
|
|
|
|
### 3.2.1 Asterisk (host network) ↔ Postgres/Redis (rede Docker interna)
|
|
|
|
Como o Asterisk usa `network_mode: host`, ele não enxerga a rede Docker
|
|
interna (`b2bcall-net`) por nome — DNS de containers só funciona para quem
|
|
está anexado à mesma rede definida pelo usuário. Solução adotada: o Postgres
|
|
publica a porta 5432 **somente em `127.0.0.1` do host**
|
|
(`127.0.0.1:5432:5432`, nunca `0.0.0.0`), e o Asterisk se conecta via
|
|
`ASTERISK_DB_HOST=127.0.0.1` (variável própria, distinta de
|
|
`POSTGRES_HOST=postgres` usada pelos demais serviços na rede interna). Isso
|
|
mantém o Postgres inacessível pela LAN/Internet, alcançável apenas por
|
|
processos no próprio host — incluindo um container em host network. Redis
|
|
não precisa disso: só a aplicação (bridge network) fala com Redis, nunca o
|
|
Asterisk diretamente.
|
|
|
|
### 3.3 Asterisk Realtime
|
|
|
|
- PJSIP objects (`ps_endpoints`, `ps_auths`, `ps_aors`, `ps_contacts`,
|
|
`ps_endpoint_id_ips`, `ps_registrations`) via `res_odbc` + `res_config_odbc`,
|
|
schema dedicado `asterisk` no mesmo Postgres.
|
|
- Domínio de aplicação (campanhas, leads, agentes, filas, etc.) vive no schema
|
|
`public`/`application`. Nunca misturar as duas responsabilidades na mesma tabela.
|
|
|
|
### 3.4 Camada de telefonia
|
|
|
|
- `packages/telephony`: interface `TelephonyProvider` (Originate, Hangup,
|
|
QueuePause, QueueAdd/Remove, ExtensionState, DeviceState, QueueStatus,
|
|
PJSIPShowEndpoints/Contacts, reload) + implementação `AsteriskTelephonyProvider`
|
|
(AMI para controle/eventos administrativos, ARI apenas quando controle fino de
|
|
canais/bridges for necessário). Nenhum comando AMI é chamado diretamente de
|
|
controllers da API.
|
|
|
|
### 3.5 Eventos em tempo real
|
|
|
|
- `apps/asterisk-events` mantém conexão persistente ao AMI, normaliza eventos,
|
|
persiste os relevantes e publica no `apps/api` (WebSocket gateway) via Redis
|
|
pub/sub. Frontend nunca faz polling do Asterisk.
|
|
|
|
### 3.6 Motor do discador preditivo
|
|
|
|
- `apps/dialer-worker`: `PredictiveDialerEngine` — decide continuamente quantas
|
|
chamadas originar, usando EWMA de answer rate, TMA, abandono, CPS disponível
|
|
(token bucket coordenado via Redis) e capacidade de agentes. Detalhado em
|
|
`docs/PREDICTIVE_DIALER.md` (a ser criado na fase correspondente).
|
|
- Reserva de leads via `SELECT ... FOR UPDATE SKIP LOCKED` no Postgres —
|
|
transição `READY → RESERVED → DIALING` atômica, com timeout de reservation.
|
|
- Lock distribuído por campanha via Redis (`dialer:campaign:{id}:lock`, TTL +
|
|
renewal + ownership token) para impedir dois workers controlando a mesma
|
|
campanha.
|
|
|
|
### 3.6.1 ORM / migrations (decisão técnica)
|
|
|
|
Escolhido **Prisma** (`packages/database`) em vez de Drizzle/TypeORM: migrations
|
|
versionadas e testáveis nativamente (`prisma migrate`), schema declarativo
|
|
único como fonte de verdade, e client tipado que reduz erro humano no domínio
|
|
RBAC/auditoria (muitas tabelas de relacionamento). Trade-off aceito: o engine
|
|
binário do Prisma adiciona overhead de build/memória, mitigado por rodar
|
|
`prisma generate` uma vez por build de imagem (não em runtime) e por ser um
|
|
ambiente com swap disponível. Consultas de alta performance do motor do
|
|
discador (ex.: `SELECT ... FOR UPDATE SKIP LOCKED`) usam `$queryRaw` do
|
|
Prisma em vez de tentar modelar lock otimista via ORM.
|
|
|
|
### 3.7 Banco de dados
|
|
|
|
- PostgreSQL 17.
|
|
- Migrations obrigatórias (nenhuma alteração manual de schema).
|
|
- Particionamento temporal de `calls`, `call_events`, `queue_events`, `audit_logs`
|
|
avaliado quando o volume justificar — não implementado prematuramente, mas
|
|
índices e chaves são desenhados para permitir particionamento futuro sem
|
|
migração destrutiva.
|
|
|
|
### 3.8 Segurança desde a arquitetura
|
|
|
|
- Segredos apenas em `.env` (nunca commitado). Credenciais de trunk SIP
|
|
criptografadas em repouso (AES-256-GCM, master key fora do banco).
|
|
- RBAC completo (users/roles/permissions/user_roles/role_permissions),
|
|
reforçado no backend independentemente do frontend.
|
|
- nftables com política default-deny para serviços administrativos/telefônicos;
|
|
SSH nunca bloqueado durante instalação.
|
|
|
|
## 4. Status
|
|
|
|
Ver `TODO.md` para o checklist vivo de implementação.
|