Adiciona um processo worker separado do processo web (src/worker.ts, WorkerModule) com dois crons diários e processors do BullMQ para alertas de mensalidade (master→tenant) e cobrança (tenant→customer): marca faturas/parcelas atrasadas, resolve template com fallback tenant→master por canal, renderiza placeholders, dispara WhatsApp/e-mail com retry e backoff exponencial, e grava alert_logs com idempotência por dia. Módulo alerts (web) traz o CRUD de alert_templates (master e tenant) e a consulta de alert_logs com filtros. alert_templates/alert_logs não têm RLS no schema, então o isolamento entre tenants é feito manualmente (PrismaMasterService + filtro por tenant_id), coberto por teste e2e dedicado. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
9.3 KiB
SeConectai Vendas
CRM/controle de vendas multi-tenant. Um painel master (dono da plataforma) administra tenants (clientes), que por sua vez cadastram seus próprios clientes ("customers"), produtos/serviços e vendas.
Stack: Node.js 20 + TypeScript, NestJS, PostgreSQL 16 + Prisma, Redis,
Docker/Docker Compose. Multi-tenancy via banco único, isolado por
tenant_id + Row Level Security (RLS) do Postgres.
Subindo o ambiente do zero
-
Copie o arquivo de variáveis de ambiente e ajuste se necessário:
cp .env.example .env -
Suba os containers (Postgres, Redis e a API com hot-reload em modo dev):
docker compose upIsso builda a imagem
app(stagedev), sobepostgreserediscom healthcheck, e só inicia a API depois que as duas dependências estiverem saudáveis.docker-compose.override.ymlé aplicado automaticamente e monta o código-fonte local no container, então alterações emsrc/reiniciam a API sozinhas (npm run dev/nest start --watch). O mesmodocker compose upjá sobe o serviçoworker(motor de alertas — ver seção própria abaixo) lado a lado com a API. -
Rode as migrations do Prisma (tabelas, enums, índices) e a migration manual de RLS/views/checks:
docker compose exec app npx prisma migrate deployEm desenvolvimento, para criar uma nova migration a partir de mudanças no
schema.prisma:docker compose exec app npx prisma migrate dev -
Confirme que a API está de pé:
curl http://localhost:3000/healthDeve retornar
200com{"status":"ok","database":"up",...}.
Rodando sem Docker (opcional)
Requer Node.js 20+, um Postgres 16 e um Redis acessíveis localmente.
npm install
npm run prisma:generate
npm run prisma:migrate
npm run dev
Variáveis de ambiente
Veja .env.example — todas as chaves usadas pela aplicação e
pelo docker-compose.yml (credenciais do Postgres, DATABASE_URL e
variantes, Redis, JWT_SECRET e demais parâmetros de autenticação) estão
documentadas lá.
Autenticação
src/modules/auth/ implementa login/refresh/logout/reset de senha para os
dois tipos de usuário:
POST /auth/master/loginePOST /auth/tenant/login— e-mail+senha → access token (JWT, 15 min por padrão) + refresh token opaco (rotacionado a cada uso, guardado só como hash sha256 emauth_refresh_tokens).POST /auth/refresh— rotaciona o refresh token; reuso de um token já rotacionado é tratado como indício de roubo e revoga toda a família de tokens do usuário.POST /auth/logout— revoga o refresh token informado.POST /auth/password/forgot/POST /auth/password/reset— gera token de reset (hash, expiração curta, uso único); por ora o token é só logado no console (envio de e-mail real é da Etapa 4).- Senhas com
argon2id(parâmetros OWASP 2024). Bloqueio de força bruta por e-mail apósLOGIN_MAX_ATTEMPTSfalhas emLOGIN_LOCKOUT_WINDOW_MINUTES(tabelalogin_attempts), mais um rate limit por IP nas rotas de login (@nestjs/throttler). JwtAuthGuard,MasterGuard,TenantGuard,RolesGuard(+@Roles(...)) e o decorator@CurrentUser()— verGET /auth/me,GET /auth/master/adminseGET /auth/tenant/usersemauth.controller.tspara exemplos de uso de cada guard.
Migrations e RLS
O schema em prisma/schema.prisma é a tradução 1:1 de
Etapas/seconectai-vendas-schema.sql (fonte de verdade). Construtos que o
Prisma DSL não expressa nativamente — views de dashboard, CHECK
constraints e as CREATE POLICY de Row Level Security — vivem numa
migration manual em prisma/migrations/<timestamp>_rls_views_constraints/,
aplicada junto com as migrations geradas automaticamente pelo Prisma
(prisma migrate deploy roda todas em ordem).
Uma segunda migration manual
(prisma/migrations/20260813120000_auth_app_roles/) cria duas roles de
banco usadas em runtime:
app_master(LOGIN+BYPASSRLS, conectada viaDATABASE_URL_MASTER) — usada pelo painel master, que precisa enxergar dados de todos os tenants.app_tenant(LOGINcomum, semBYPASSRLS, conectada viaDATABASE_URL_TENANT) — usada por toda query autenticada comotenant_user.
Isso é necessário porque a role de DATABASE_URL (seconectai, dona das
tabelas) é a superuser bootstrap do Postgres criada pelo POSTGRES_USER
do docker-compose — e superusers sempre ignoram Row Level Security,
mesmo com as policies criadas. Sem uma role não-superuser dedicada, o
isolamento por tenant seria só decorativo.
Por request, o TenantRlsInterceptor (registrado globalmente) identifica
requests autenticadas como tenant_user e, via TenantContextService,
abre uma transação na conexão app_tenant executando:
SELECT set_config('app.current_tenant_id', '<uuid-do-tenant-logado>', true); -- equivalente a SET LOCAL
sempre usando o tenant_id do JWT da sessão — nunca um valor vindo de
input do usuário (body/query). Serviços que precisam rodar dentro desse
contexto usam TenantContextService.getTx() (ver
AuthController.listTenantUsers como exemplo). Requests master usam
PrismaMasterService (conexão app_master, sem passar pelas policies).
Motor de alertas (worker)
O disparo automatizado de alertas (mensalidade do tenant, cobrança de parcelas do customer) roda num processo separado do processo web:
src/main.ts(processoapp) — só HTTP. Não importaScheduleModulenem processa nenhuma fila.src/worker.ts(processoworker) — sem HTTP. Sobe viaNestFactory.createApplicationContext(WorkerModule), que inicializa os dois crons diários (AlertSchedulerService,@nestjs/schedule) e os dois processors do BullMQ (MensalidadeProcessor/CobrancaProcessor, filasalertas-mensalidade/alertas-cobrancano Redis já usado pelodocker-compose).
Essa separação existe para poder escalar os dois processos de forma
independente sem duplicar o disparo do cron: se o app rodar em várias
réplicas (comum atrás de um load balancer), um @Cron() registrado nele
dispararia o mesmo job uma vez por réplica. Como os crons vivem só no
worker, escalar worker (docker compose up --scale worker=3)
paraleliza o processamento das filas, não a geração dos jobs.
docker compose up já sobe os dois processos (serviço worker no
docker-compose.yml/docker-compose.override.yml, mesma imagem do app,
command diferente: node dist/worker em produção, npm run worker:dev
em desenvolvimento). Para rodar só o worker manualmente:
docker compose up worker
Horário dos crons, antecedência do alerta e política de retry são
configuráveis via ALERT_CRON_MENSALIDADE/ALERT_CRON_COBRANCA,
ALERT_DIAS_ANTECEDENCIA_MENSALIDADE/ALERT_DIAS_ANTECEDENCIA_COBRANCA e
ALERT_JOB_MAX_ATTEMPTS/ALERT_JOB_BACKOFF_MS (ver .env.example).
Scripts
| Script | Descrição |
|---|---|
npm run dev |
Inicia a API em modo watch |
npm run build |
Compila TypeScript para dist/ |
npm run start:prod |
Roda a build de produção (dist/main.js) |
npm run worker:dev |
Inicia o worker de alertas em modo watch |
npm run worker:prod |
Roda a build de produção do worker (dist/worker.js) |
npm run lint |
ESLint (com --fix) |
npm run test |
Testes unitários (Jest) |
npm run test:e2e |
Testes end-to-end (Jest + Supertest) |
npm run prisma:generate |
Gera o Prisma Client |
npm run prisma:migrate |
Cria/aplica migration em desenvolvimento |
npm run prisma:migrate:deploy |
Aplica migrations pendentes (produção/CI) |
npm run prisma:studio |
Abre o Prisma Studio |
Estrutura do projeto
src/
├── main.ts # processo web (HTTP)
├── worker.ts # processo worker (crons + filas BullMQ, sem HTTP)
├── app.module.ts # módulos do processo web
├── config/ # ConfigModule, validação de env, PrismaService/PrismaMasterService/
│ # PrismaTenantService, TenantContextService (mecanismo de RLS por request)
├── worker/ # WorkerModule, AlertSchedulerService (crons), processors do BullMQ
└── modules/
├── health/ # GET /health
├── auth/ # login/refresh/logout/reset, guards, decorators, JwtStrategy
└── alerts/ # CRUD de alert_templates, GET .../alertas/logs, resolver de fallback
# e placeholders (usado pelo módulo web e pelo worker)
prisma/
├── schema.prisma
└── migrations/
Etapas/ # Prompts e controle de progresso do build faseado
Progresso do build
Este projeto é construído em etapas. O estado oficial de cada etapa (o que
já foi concluído, o que falta, decisões tomadas) fica em
Etapas/09-controle-de-progresso.md.