Adiciona upload/remoção de logo e cor primária por tenant (módulo tenant-profile, armazenamento local atrás de uma interface StorageService trocável por object storage depois) e um endpoint público de tema para a tela de login carregar antes da autenticação. Revisão de segurança: Helmet e CORS explícito (extraídos para security.util.ts, reutilizado pelos testes e2e que verificam os cabeçalhos), Swagger em /docs cobrindo todos os módulos via o plugin oficial, paginação em alert_templates. Corrige um achado real: o reset de senha logava o token em texto puro desde a Etapa 2 — agora envia por e-mail de verdade. Build de produção do Docker validado (imagem sem devDependencies, app e worker rodando contra Postgres/Redis reais) e README consolidado com o processo de deploy. Fecha o backlog com um teste e2e do fluxo completo: master cria tenant → login → customer/produto → venda com parcelas → parcela paga → dashboard financeiro reflete o pagamento. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
275 lines
13 KiB
Markdown
275 lines
13 KiB
Markdown
# 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
|
|
|
|
1. Copie o arquivo de variáveis de ambiente e ajuste se necessário:
|
|
|
|
```bash
|
|
cp .env.example .env
|
|
```
|
|
|
|
2. Suba os containers (Postgres, Redis e a API com hot-reload em modo dev):
|
|
|
|
```bash
|
|
docker compose up
|
|
```
|
|
|
|
Isso builda a imagem `app` (stage `dev`), sobe `postgres` e `redis` com
|
|
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 em `src/` reiniciam a
|
|
API sozinhas (`npm run dev` / `nest start --watch`). O mesmo `docker
|
|
compose up` já sobe o serviço `worker` (motor de alertas — ver seção
|
|
própria abaixo) lado a lado com a API.
|
|
|
|
3. Rode as migrations do Prisma (tabelas, enums, índices) e a migration
|
|
manual de RLS/views/checks:
|
|
|
|
```bash
|
|
docker compose exec app npx prisma migrate deploy
|
|
```
|
|
|
|
Em desenvolvimento, para criar uma nova migration a partir de mudanças no
|
|
`schema.prisma`:
|
|
|
|
```bash
|
|
docker compose exec app npx prisma migrate dev
|
|
```
|
|
|
|
4. Confirme que a API está de pé:
|
|
|
|
```bash
|
|
curl http://localhost:3000/health
|
|
```
|
|
|
|
Deve retornar `200` com `{"status":"ok","database":"up",...}`.
|
|
|
|
## Rodando sem Docker (opcional)
|
|
|
|
Requer Node.js 20+, um Postgres 16 e um Redis acessíveis localmente.
|
|
|
|
```bash
|
|
npm install
|
|
npm run prisma:generate
|
|
npm run prisma:migrate
|
|
npm run dev
|
|
```
|
|
|
|
## Deploy em produção
|
|
|
|
1. **Segredos**: copie `.env.example` para `.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 de `DATABASE_URL_MASTER`/
|
|
`DATABASE_URL_TENANT` (roles `app_master`/`app_tenant` — troque com
|
|
`ALTER ROLE ... WITH PASSWORD '...'` no Postgres e atualize as duas
|
|
connection strings de acordo), `JWT_SECRET` e `ENCRYPTION_KEY` (mínimo 32
|
|
caracteres cada). Trocar `ENCRYPTION_KEY` depois que já existem segredos
|
|
cifrados no banco (API keys, senha SMTP) invalida todos eles — só troque
|
|
nessa hora se também for recriá-los. Configure `CORS_ORIGINS` com a(s)
|
|
origem(ns) real(is) do frontend (nunca deixe vazio em produção se o
|
|
frontend for acessado via browser).
|
|
|
|
2. **Build**: `docker compose build` (sem `docker-compose.override.yml`,
|
|
que só se aplica automaticamente quando presente/em dev) builda a
|
|
imagem `production` — multi-stage, sem devDependencies (`prod-deps` usa
|
|
`npm ci --omit=dev`) — para os serviços `app` e `worker`.
|
|
|
|
3. **Subir os serviços**: `docker compose up -d` — sobe `postgres`/`redis`
|
|
(com healthcheck), e só inicia `app`/`worker` depois que as duas
|
|
dependências estiverem saudáveis (`depends_on: condition:
|
|
service_healthy`). `app` expõe HTTP (`PORT`, default 3000) e tem
|
|
healthcheck próprio batendo em `/health`; `worker` nã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.
|
|
|
|
4. **Migrations, incluindo RLS**: `docker compose exec app npx prisma
|
|
migrate deploy` aplica **todas** as migrations pendentes em ordem — as
|
|
geradas automaticamente pelo Prisma e as manuais (RLS/policies/views,
|
|
roles `app_master`/`app_tenant`, `security_invoker` das views de
|
|
dashboard) são só pastas em `prisma/migrations/`, então não existe um
|
|
passo separado "aplicar RLS": um único `migrate deploy` já cobre tudo,
|
|
nessa mesma ordem, em qualquer ambiente novo.
|
|
|
|
5. **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 do `worker` que as duas filas (`alertas-mensalidade`/
|
|
`alertas-cobranca`) inicializaram.
|
|
|
|
6. **Uploads de logo**: `docker-compose.yml` já monta um volume nomeado
|
|
(`uploads_data:/app/uploads`) pro serviço `app`, então logos enviados via
|
|
`POST /tenant/perfil/logo` sobrevivem a recriações do container. Trocar
|
|
por object storage (S3 etc) numa etapa futura é só implementar
|
|
`StorageService` (`src/common/storage/storage.service.ts`) de novo e
|
|
trocar o provider registrado em `TenantProfileModule` — nenhum outro
|
|
código muda.
|
|
|
|
## Variáveis de ambiente
|
|
|
|
Veja [.env.example](.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/login` e `POST /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 em `auth_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ós `LOGIN_MAX_ATTEMPTS` falhas em `LOGIN_LOCKOUT_WINDOW_MINUTES`
|
|
(tabela `login_attempts`), mais um rate limit por IP nas rotas de login
|
|
(`@nestjs/throttler`).
|
|
- `JwtAuthGuard`, `MasterGuard`, `TenantGuard`, `RolesGuard` (+
|
|
`@Roles(...)`) e o decorator `@CurrentUser()` — ver
|
|
`GET /auth/me`, `GET /auth/master/admins` e `GET /auth/tenant/users` em
|
|
`auth.controller.ts` para 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 via
|
|
`DATABASE_URL_MASTER`) — usada pelo painel master, que precisa enxergar
|
|
dados de todos os tenants.
|
|
- **`app_tenant`** (`LOGIN` comum, sem `BYPASSRLS`, conectada via
|
|
`DATABASE_URL_TENANT`) — usada por toda query autenticada como
|
|
`tenant_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:
|
|
|
|
```sql
|
|
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` (processo `app`) — só HTTP. Não importa `ScheduleModule`
|
|
nem processa nenhuma fila.
|
|
- `src/worker.ts` (processo `worker`) — sem HTTP. Sobe via
|
|
`NestFactory.createApplicationContext(WorkerModule)`, que inicializa os
|
|
dois crons diários (`AlertSchedulerService`, `@nestjs/schedule`) e os
|
|
dois processors do BullMQ (`MensalidadeProcessor`/`CobrancaProcessor`,
|
|
filas `alertas-mensalidade`/`alertas-cobranca` no Redis já usado pelo
|
|
`docker-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:
|
|
|
|
```bash
|
|
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`](Etapas/09-controle-de-progresso.md).
|