Adiciona o frontend Next.js em frontend/ (setup, design system, autenticação/tema, layout/navegação, área master, área tenant completa: clientes, produtos, vendas, recebimentos, whatsapp, e-mail, alertas, equipe e perfil). Estende o backend de forma aditiva e autorizada: cookie httpOnly para refresh token (Etapa 13) e CRUD de equipe + edição de perfil do tenant (Etapa 18, módulo src/modules/equipe/ novo e PUT /tenant/perfil). Detalhes completos em Etapas/V2/*. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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
Deploy em produção
-
Segredos: copie
.env.examplepara.env(ou injete as mesmas variáveis via secrets do orquestrador — nunca commite o.env) e troque todos os valores marcados como "changeme"/exemplo:POSTGRES_PASSWORD, as senhas deDATABASE_URL_MASTER/DATABASE_URL_TENANT(rolesapp_master/app_tenant— troque comALTER ROLE ... WITH PASSWORD '...'no Postgres e atualize as duas connection strings de acordo),JWT_SECRETeENCRYPTION_KEY(mínimo 32 caracteres cada). TrocarENCRYPTION_KEYdepois que já existem segredos cifrados no banco (API keys, senha SMTP) invalida todos eles — só troque nessa hora se também for recriá-los. ConfigureCORS_ORIGINScom a(s) origem(ns) real(is) do frontend (nunca deixe vazio em produção se o frontend for acessado via browser). -
Build:
docker compose build(semdocker-compose.override.yml, que só se aplica automaticamente quando presente/em dev) builda a imagemproduction— multi-stage, sem devDependencies (prod-depsusanpm ci --omit=dev) — para os serviçosappeworker. -
Subir os serviços:
docker compose up -d— sobepostgres/redis(com healthcheck), e só iniciaapp/workerdepois que as duas dependências estiverem saudáveis (depends_on: condition: service_healthy).appexpõe HTTP (PORT, default 3000) e tem healthcheck próprio batendo em/health;workernão expõe porta (sem HTTP) e roda os crons/filas do motor de alertas — ver seção própria abaixo. Os dois escalam de forma independente (docker compose up -d --scale worker=3, por exemplo) sem duplicar o disparo dos crons. -
Migrations, incluindo RLS:
docker compose exec app npx prisma migrate deployaplica todas as migrations pendentes em ordem — as geradas automaticamente pelo Prisma e as manuais (RLS/policies/views, rolesapp_master/app_tenant,security_invokerdas views de dashboard) são só pastas emprisma/migrations/, então não existe um passo separado "aplicar RLS": um únicomigrate deployjá cobre tudo, nessa mesma ordem, em qualquer ambiente novo. -
Verificação:
curl https://<host>/health(200,{"status":"ok","database":"up"}),https://<host>/docs(Swagger, todos os módulos documentados com o guard exigido por cada rota) e confira nos logs doworkerque as duas filas (alertas-mensalidade/alertas-cobranca) inicializaram. -
Uploads de logo:
docker-compose.ymljá monta um volume nomeado (uploads_data:/app/uploads) pro serviçoapp, então logos enviados viaPOST /tenant/perfil/logosobrevivem a recriações do container. Trocar por object storage (S3 etc) numa etapa futura é só implementarStorageService(src/common/storage/storage.service.ts) de novo e trocar o provider registrado emTenantProfileModule— nenhum outro código muda.
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
├── security.util.ts # Helmet/CORS/arquivos estáticos — chamado por main.ts e por
│ # testes e2e que verificam esses cabeçalhos
├── common/storage/ # StorageService (interface) + LocalStorageService (Etapa 8)
├── 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
├── tenant-profile/ # GET/PUT/POST/DELETE /tenant/perfil (logo/cor), GET /public/tenants/:id/tema
└── 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
Documentação interativa de todos os endpoints (Swagger/OpenAPI, gerado via
@nestjs/swagger + o plugin do nest-cli.json — não precisa de
@ApiProperty() manual em cada DTO): /docs (/docs-json pro spec bruto).
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.