Appearance
Resiliência e Observabilidade
Padrões obrigatórios de infraestrutura para resiliência, observabilidade e escalabilidade da API.
1. Visão Geral
┌─────────────────────────────────────────────────────────────────────────┐
│ ARQUITETURA DE RESILIÊNCIA │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ┌─────────┐ ┌──────────────┐ ┌───────────┐ ┌──────────────┐ │
│ │ Request │───►│ Correlation │───►│ Controller│───►│ Service │ │
│ │ │ │ ID Middleware │ │ │ │ │ │
│ └─────────┘ └──────────────┘ └───────────┘ └──────┬───────┘ │
│ │ │
│ ┌────────────────────────────────────┤ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌────────────────────┐ │
│ │ Circuit Breaker │ │ BullMQ Queue │ │
│ │ + Retry + Cache │ │ + DLQ + Workers │ │
│ └────────┬─────────┘ └────────┬───────────┘ │
│ │ │ │
│ ▼ ▼ │
│ ┌──────────────────┐ ┌────────────────────┐ │
│ │ External APIs │ │ Async Processors │ │
│ │ (NFSe, CNPJA, │ │ (PDF, Email, │ │
│ │ OAuth, Twilio) │ │ NFSe, Cron) │ │
│ └──────────────────┘ └────────────────────┘ │
│ │
│ ┌──────────────────────────────────────────────────────────────────┐ │
│ │ OBSERVABILIDADE │ │
│ │ Pino (JSON) → CloudWatch Logs │ │
│ │ OpenTelemetry → Traces + Metrics │ │
│ │ Sentry → Errors + Profiling │ │
│ │ Correlation ID → Rastreamento end-to-end │ │
│ └──────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘2. Stack de Infraestrutura
| Componente | Tecnologia | Finalidade |
|---|---|---|
| Cache / Lock / Queue Broker | Redis 7+ | Cache distribuído, locks, backend do BullMQ |
| Filas | BullMQ | Processamento assíncrono com retry e DLQ |
| Logs | Pino + nestjs-pino | Logs estruturados em JSON |
| Tracing | OpenTelemetry SDK | Traces distribuídos e métricas |
| Errors | Sentry (existente) | Captura de erros e profiling |
| Circuit Breaker | cockatiel | Circuit breaker, retry, timeout |
| Cache HTTP | @nestjs/cache-manager + Redis | Cache em endpoints de leitura |
| Health Checks | @nestjs/terminus | Health checks profundos |
3. Correlation ID
Toda requisição deve ser rastreável end-to-end através de um identificador único.
3.1 Fluxo
┌──────────┐ ┌────────────────────┐ ┌──────────┐ ┌──────────┐
│ Client │───►│ CorrelationId │───►│ Service │───►│ External │
│ │ │ Middleware │ │ │ │ API Call │
│ │ │ │ │ │ │ │
│ │ │ Gera UUID se não │ │ Acessa │ │ Recebe │
│ │ │ vier no header │ │ via ALS │ │ header │
└──────────┘ └────────────────────┘ └──────────┘ └──────────┘
│ │
▼ ▼
┌───────────┐ ┌───────────┐
│ Response │ │ Logs │
│ Header │ │ (Pino) │
│ X-Corr-ID │ │ corrId │
└───────────┘ └───────────┘3.2 Regras
- Header:
X-Correlation-ID - Se o client enviar, usar o valor recebido
- Se não enviar, gerar UUID v4 automaticamente
- Armazenar via
AsyncLocalStorage(Node.js nativo) - Incluir em todos os logs automaticamente via Pino
- Propagar para chamadas HTTP externas (NFSe, CNPJA, OAuth)
- Propagar para jobs de fila (metadata do job BullMQ)
- Retornar no header da response
- Cron jobs geram correlation ID próprio por execução
3.3 Estrutura
src/
├── common/
│ ├── middleware/
│ │ └── correlation-id.middleware.ts
│ └── context/
│ └── correlation.context.ts # AsyncLocalStorage wrapper4. Logs Centralizados
Logs estruturados em JSON com contexto automático.
4.1 Configuração
| Propriedade | Valor |
|---|---|
| Biblioteca | nestjs-pino (wrapper do Pino) |
| Formato | JSON estruturado |
| Nível dev | debug |
| Nível prod | warn |
| Destino | stdout → CloudWatch Logs |
| Redação | CPF, CNPJ, tokens, senhas |
4.2 Campos Obrigatórios em Todo Log
| Campo | Origem | Exemplo |
|---|---|---|
correlationId | AsyncLocalStorage | "a1b2c3d4-..." |
userId | JWT payload | "user-uuid" |
companyId | Header x-company-id | "company-uuid" |
module | Logger name | "InvoicesService" |
level | Pino level | "info" |
timestamp | Automático | 1711670400000 |
msg | Mensagem | "Invoice created" |
4.3 Dados Sensíveis (Redação Automática)
Pino redact paths obrigatórios:
redact:
- "req.headers.authorization"
- "req.headers.cookie"
- "*.password"
- "*.certificatePassword"
- "*.token"
- "*.accessToken"
- "*.refreshToken"
- "*.document" # CPF/CNPJ
- "*.pfxBase64"4.4 Estrutura
src/
├── config/
│ └── logger.config.ts # Configuração do Pino5. Métricas e Tracing
OpenTelemetry como padrão para traces distribuídos e métricas técnicas.
5.1 Traces
| Propriedade | Valor |
|---|---|
| SDK | @opentelemetry/sdk-node |
| Propagação | W3C TraceContext |
| Exportador | OTLP (compatível com CloudWatch, Jaeger, etc.) |
| Auto-instrumentação | HTTP, Prisma, BullMQ |
| Sample rate prod | 10% (ajustável) |
5.2 Métricas Técnicas
| Métrica | Tipo | Labels |
|---|---|---|
http_request_duration_seconds | Histogram | method, route, status |
http_requests_total | Counter | method, route, status |
db_query_duration_seconds | Histogram | operation, model |
queue_job_duration_seconds | Histogram | queue, status |
queue_depth | Gauge | queue |
queue_failed_total | Counter | queue |
circuit_breaker_state | Gauge | service (open/closed/half) |
cache_hit_total | Counter | key_prefix |
cache_miss_total | Counter | key_prefix |
external_api_duration_seconds | Histogram | service, method |
5.3 Métricas de Negócio
| Métrica | Tipo | Labels |
|---|---|---|
invoices_emitted_total | Counter | companyId, status |
contracts_created_total | Counter | companyId |
pdfs_generated_total | Counter | status (success/error) |
compliance_notifications_total | Counter | type |
onboarding_completions_total | Counter | companyId |
5.4 Alarmes Obrigatórios
| Alarme | Condição | Ação |
|---|---|---|
| Error Rate Alto | error rate > 5% por 5min | Alerta imediato |
| Latência Alta | p99 > 3s por 5min | Alerta |
| DLQ Acumulando | DLQ depth > 50 por 10min | Alerta imediato |
| Circuit Open | circuit_breaker_state = open | Alerta imediato |
| DB Pool Esgotando | connections > 80% pool | Alerta |
5.5 Estrutura
src/
├── config/
│ └── telemetry.config.ts
├── common/
│ └── metrics/
│ └── metrics.service.ts # Custom metrics registry6. Circuit Breaker e Timeout
Todas as chamadas a serviços externos devem ter circuit breaker, timeout e retry.
6.1 Serviços Externos e Suas Políticas
| Serviço | Timeout | Retries | Backoff | Circuit Breaker |
|---|---|---|---|---|
| NFSe API | 30s | 3 | exponential (1s, 2s, 4s) | 5 falhas → open 60s |
| CNPJA (company lookup) | 10s | 2 | exponential (500ms, 1s) | 3 falhas → open 30s |
| OAuth Providers | 10s | 2 | exponential (500ms, 1s) | 5 falhas → open 60s |
| AWS SES (email) | 15s | 3 | exponential (1s, 2s, 4s) | 5 falhas → open 60s |
| Twilio (SMS) | 10s | 2 | exponential (1s, 2s) | 3 falhas → open 30s |
| AWS S3 (storage) | 30s | 3 | exponential (1s, 2s, 4s) | 5 falhas → open 60s |
| Puppeteer (PDF) | 60s | 1 | 3 falhas → open 120s | |
| ui-avatars.com | 5s | 1 | 3 falhas → open 30s |
6.2 Padrão de Implementação com cockatiel
┌──────────┐ ┌─────────┐ ┌───────────────┐ ┌──────────┐
│ Service │───►│ Timeout │───►│ Circuit │───►│ External │
│ │ │ Policy │ │ Breaker │ │ API │
│ │ │ │ │ │ │ │
│ │ │ │ │ ┌───────────┐ │ │ │
│ │ │ │ │ │Retry+Back │ │ │ │
│ │ │ │ │ │off inside │ │ │ │
│ │ │ │ │ └───────────┘ │ │ │
└──────────┘ └─────────┘ └───────────────┘ └──────────┘6.3 Regras
- Timeout é a policy mais externa (nunca esperar mais que o limite)
- Circuit breaker envolve retry (se circuit abrir, nem tenta retry)
- Retry usa exponential backoff com jitter
- Retry apenas em erros transientes (5xx, timeout, ECONNRESET): nunca em 4xx
- Quando circuit abrir: logar warning, incrementar métrica, retornar erro descritivo
- Fallback opcional: dados em cache quando disponível (ex: parâmetros municipais NFSe)
6.4 Estrutura
src/
├── common/
│ └── resilience/
│ ├── resilience.module.ts
│ ├── policies/
│ │ ├── nfse.policy.ts
│ │ ├── cnpja.policy.ts
│ │ ├── oauth.policy.ts
│ │ ├── email.policy.ts
│ │ ├── sms.policy.ts
│ │ └── storage.policy.ts
│ └── resilience.service.ts # Factory de policies7. Cache
Redis como store centralizado para cache de leitura, locks distribuídos e session do BullMQ.
7.1 Infraestrutura
| Propriedade | Valor |
|---|---|
| Engine | Redis 7+ (ElastiCache) |
| Biblioteca | @nestjs/cache-manager + cache-manager-redis-yet |
| Serialização | JSON |
| Key prefix | contrasync:cache: |
| Lock prefix | contrasync:lock: |
7.2 Pontos de Cache
| Endpoint / Dado | TTL | Estratégia de Invalidação |
|---|---|---|
GET /contracts/:id | 30s | Invalidar no update/delete |
GET /providers/:id/compliance | 60s | Invalidar em mudança de status |
GET /dashboard/* | 5min | Invalidar por TTL |
GET /invoices (listagem) | 30s | Invalidar na criação/update |
| Parâmetros municipais NFSe | 24h | Invalidar por TTL |
| Alíquotas de serviço NFSe | 24h | Invalidar por TTL |
| Company settings | 5min | Invalidar no update |
| Permission profiles | 5min | Invalidar no update |
| Certificado digital (agente HTTPS) | Existente | Manter padrão atual |
7.3 Padrão de Invalidação
Cache-Aside Pattern:
READ:
1. Buscar no Redis
2. Se encontrou → retornar (cache hit)
3. Se não → buscar no banco → salvar no Redis → retornar
WRITE:
1. Atualizar no banco
2. Deletar chave do Redis (invalidação)
3. Próximo read reconstrói o cache7.4 Regras
- Nunca cachear dados sensíveis (tokens, certificados, senhas)
- Sempre incluir
companyIdna chave para respeitar multi-tenancy - Formato da chave:
contrasync:cache:{entity}:{companyId}:{id} - Listagens:
contrasync:cache:{entity}:list:{companyId}:{hash-dos-filtros} - Usar
@CacheKey()e@CacheTTL()nos controllers quando possível - Cache não substitui paginação, só cachear resultados já paginados
7.5 Locks Distribuídos
Para operações que não devem rodar em paralelo (ex: cron jobs em múltiplas instâncias):
| Operação | Lock Key | TTL do Lock |
|---|---|---|
| Compliance cron | contrasync:lock:cron:compliance | 5min |
| Contract overdue cron | contrasync:lock:cron:contract-overdue | 2min |
| Contract PDF cron | contrasync:lock:cron:contract-pdf | 10min |
| Onboarding cron | contrasync:lock:cron:onboarding | 5min |
| NFSe emission (por invoice) | contrasync:lock:nfse:{invoiceId} | 2min |
7.6 Estrutura
src/
├── config/
│ ├── redis.config.ts
│ └── cache.config.ts
├── common/
│ └── cache/
│ ├── cache.module.ts
│ └── cache-invalidation.service.ts8. Filas, Idempotência e Dead-Letter Queue
Processamento assíncrono via BullMQ com garantia de idempotência e DLQ para falhas.
8.1 Filas e Seus Propósitos
| Fila | Processador | Concorrência | Prioridade |
|---|---|---|---|
email | Envio de emails (SES) | 5 | Normal |
pdf-generation | Geração de PDFs (Puppeteer) | 2 | Normal |
nfse-emission | Emissão de NFSe | 3 | Alta |
nfse-query | Consulta de status NFSe | 5 | Normal |
push-notification | Push via Expo SDK | 10 | Normal |
compliance-check | Processamento de deadlines | 3 | Normal |
onboarding-reminder | Envio de lembretes | 5 | Baixa |
8.2 Migração dos Cron Jobs para Filas
ANTES (Cron direto):
@Cron('0 6 * * *')
processCompliance() → executa tudo sincronamente
DEPOIS (Cron como producer):
@Cron('0 6 * * *')
scheduleCompliance() → enfileira jobs individuais
@Processor('compliance-check')
processOne(job) → processa um compliance por vez com retry8.3 Idempotência
| Mecanismo | Aplicação |
|---|---|
jobId no BullMQ | Previne jobs duplicados na fila |
| Idempotency key em endpoints | Previne criação duplicada via API |
| Lock distribuído (Redis) | Previne execução paralela de cron schedulers |
| Status check antes de processar | Verificar se job já foi processado |
Idempotency Key em endpoints críticos:
| Endpoint | Header | Formato |
|---|---|---|
POST /invoices/emit | X-Idempotency-Key | UUID v4 (client-generated) |
POST /contracts | X-Idempotency-Key | UUID v4 |
POST /signing/:id/sign | X-Idempotency-Key | UUID v4 |
Regras:
- Client envia
X-Idempotency-Keyno header - Backend armazena no Redis:
contrasync:idempotency:{key}→ response (TTL 24h) - Se key já existir, retorna response armazenada sem processar novamente
- Aplicar apenas em endpoints de criação/mutação críticos
8.4 Dead-Letter Queue
┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐
│ Producer │───►│ Queue │───►│ Consumer │───►│ Sucesso │
│ │ │ │ │ │ │ │
└──────────┘ └──────────┘ └────┬─────┘ └──────────┘
│
│ Falha após N retries
▼
┌──────────┐
│ DLQ │
│ │
│ Alerta + │
│ Dashboard│
└──────────┘Configuração por fila:
| Fila | Max Retries | Backoff | DLQ |
|---|---|---|---|
email | 5 | exponential (30s base) | email-dlq |
pdf-generation | 3 | exponential (60s base) | pdf-generation-dlq |
nfse-emission | 5 | exponential (60s base) | nfse-emission-dlq |
push-notification | 3 | exponential (10s base) | push-notification-dlq |
compliance-check | 3 | exponential (30s base) | compliance-check-dlq |
onboarding-reminder | 3 | exponential (30s base) | onboarding-reminder-dlq |
Regras da DLQ:
- Jobs que falharem após max retries vão automaticamente para a DLQ
- Alerta quando DLQ depth > 50
- Endpoint admin para listar, inspecionar e reprocessar jobs da DLQ
- Reter jobs na DLQ por 7 dias
8.5 Estrutura
src/
├── config/
│ └── queue.config.ts
├── common/
│ ├── queue/
│ │ ├── queue.module.ts # Registro global das filas
│ │ └── dlq.service.ts # Gestão de DLQ
│ └── idempotency/
│ ├── idempotency.guard.ts # Guard para X-Idempotency-Key
│ └── idempotency.service.ts # Armazenamento Redis
├── modules/
│ ├── invoices/
│ │ └── processors/
│ │ ├── nfse-emission.processor.ts
│ │ └── nfse-query.processor.ts
│ ├── contracts/
│ │ └── processors/
│ │ └── pdf-generation.processor.ts
│ ├── notifications/
│ │ └── processors/
│ │ └── push-notification.processor.ts
│ └── compliance/
│ └── processors/
│ └── compliance-check.processor.ts9. Health Checks
Health check profundo que verifica todos os componentes críticos.
9.1 Endpoints
| Endpoint | Uso | Verifica |
|---|---|---|
GET /health | Load balancer (Elastic Beanstalk) | Status básico |
GET /health/ready | Readiness probe | DB + Redis + filas |
GET /health/live | Liveness probe | Processo está respondendo |
9.2 Checks do Readiness
| Check | Biblioteca | Threshold |
|---|---|---|
| PostgreSQL | @nestjs/terminus PrismaHealthIndicator | Timeout 3s |
| Redis | @nestjs/terminus RedisHealthIndicator | Timeout 2s |
| BullMQ Queues | Custom health indicator | Queue respondendo |
| Disk | @nestjs/terminus DiskHealthIndicator | < 90% uso |
| Memory | @nestjs/terminus MemoryHealthIndicator | Heap < 512MB |
9.3 Estrutura
src/
├── common/
│ └── health/
│ ├── health.module.ts
│ ├── health.controller.ts
│ └── indicators/
│ ├── redis.health.ts
│ └── queue.health.ts10. Autoscaling
Políticas de scaling baseadas em métricas reais da aplicação.
10.1 Métricas de Scaling
| Métrica | Threshold Scale Up | Threshold Scale Down | Cooldown |
|---|---|---|---|
| CPU | > 70% por 3min | < 30% por 10min | 5min |
| Memória | > 80% por 3min | < 40% por 10min | 5min |
| Request Latency p99 | > 3s por 5min | < 1s por 15min | 10min |
| Queue Depth total | > 100 por 3min | < 10 por 10min | 5min |
| Active Connections | > 80% max por 3min | < 30% max por 10min | 5min |
10.2 Limites
| Propriedade | Valor |
|---|---|
| Instâncias mínimas | 2 |
| Instâncias máximas | 8 |
| Tipo de instância | Configurável via Elastic Beanstalk |
| Health check grace period | 300s |
10.3 Separação de Workers
┌─────────────────────────────────────────────────────┐
│ DEPLOY TOPOLOGY │
├─────────────────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────────────────┐ │
│ │ API Servers (Elastic Beanstalk) │ │
│ │ │ │
│ │ - Controllers, Services, Repositories │ │
│ │ - WebSocket Gateway │ │
│ │ - Queue Producers │ │
│ │ - Escala por CPU/Memória/Latency │ │
│ │ - Min: 2, Max: 8 │ │
│ └────────────────────────────────────────────┘ │
│ │ │
│ Redis (ElastiCache) │
│ │ │
│ ┌────────────────────────────────────────────┐ │
│ │ Queue Workers (EC2 / ECS) │ │
│ │ │ │
│ │ - BullMQ Processors │ │
│ │ - Cron Schedulers (apenas 1 instância) │ │
│ │ - Escala por Queue Depth │ │
│ │ - Min: 1, Max: 4 │ │
│ └────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────┘11. Variáveis de Ambiente
| Variável | Obrigatória | Exemplo | Descrição |
|---|---|---|---|
REDIS_HOST | Sim | redis.internal | Host do Redis |
REDIS_PORT | Não | 6379 | Porta do Redis (default 6379) |
REDIS_PASSWORD | Prod | **** | Senha do Redis |
REDIS_TLS | Não | true | Habilitar TLS (prod) |
LOG_LEVEL | Não | warn | Nível de log (default: info) |
OTEL_EXPORTER_OTLP_ENDPOINT | Não | http://collector:4318 | Endpoint do OpenTelemetry |
OTEL_TRACES_SAMPLE_RATE | Não | 0.1 | Taxa de amostragem de traces |
QUEUE_CONCURRENCY_DEFAULT | Não | 3 | Concorrência padrão dos workers |
CACHE_DEFAULT_TTL | Não | 30 | TTL padrão do cache em segundos |
PROCESS_MODE | Não | api | Modo: api, worker, ou all |
THROTTLE_DISABLED | Não | true | Desliga o rate limit (local/dev) |
THROTTLE_LIMIT | Não | 300 | Requisições por janela, por usuário/IP |
THROTTLE_TTL_MS | Não | 60000 | Janela do rate limit em ms |
TRUST_PROXY_HOPS | Não | 1 | Proxies confiáveis na frente da API |
12. Ordem de Implementação
Fase 1: Fundação (Quick Wins)
1. Correlation ID + AsyncLocalStorage
2. Pino (substituir Logger nativo)
3. Redis connection + config
4. Health checks profundos (@nestjs/terminus)Fase 2: Resiliência
5. Timeout em todas chamadas externas
6. Retry com backoff (cockatiel)
7. Circuit breaker por serviço externo
8. Cache nos endpoints de leitura críticosFase 3: Processamento Assíncrono
9. BullMQ setup + filas
10. Migrar cron jobs para queue producers
11. Processors com DLQ
12. Idempotency guard
13. Locks distribuídos nos cron schedulersFase 4: Observabilidade Completa
14. OpenTelemetry SDK + métricas custom
15. Métricas de negócio
16. Alarmes CloudWatch
17. Autoscaling policies avançadas13. Checklist de Qualidade
Para cada componente implementado:
- [ ] Correlation ID propagado em logs e chamadas externas
- [ ] Logs estruturados em JSON com campos obrigatórios
- [ ] Dados sensíveis redatados nos logs
- [ ] Chamadas externas com timeout explícito
- [ ] Chamadas externas com circuit breaker
- [ ] Retry apenas em erros transientes (5xx, timeout)
- [ ] Cache com invalidação correta e multi-tenancy na chave
- [ ] Jobs de fila idempotentes
- [ ] DLQ configurada para toda fila
- [ ] Health check cobrindo todos os componentes
- [ ] Métricas emitidas para operações críticas
Interceptação de Email e SMS em Desenvolvimento
Em ambiente de desenvolvimento, todo envio de email e SMS pode ser redirecionado para um destinatário fixo, evitando envios acidentais para usuários reais.
Variáveis de Ambiente
| Variável | Descrição | Exemplo |
|---|---|---|
INTERCEPT_EMAIL_VERIFICATION | Redireciona todos os emails para este endereço. Quando preenchida, o código de verificação de email usa o bypass 123456. | [email protected] |
INTERCEPT_SMS_VERIFICATION | Redireciona todos os SMS para este número. Quando preenchida, o código de verificação de SMS usa o bypass 123456. | 31999999999 |
BYPASS_CNPJ_CARD_VALIDATION | true libera o upload do cartão CNPJ para qualquer PDF: o controller aceita ?document=<cnpj>, o service pula scrape/validate e popula os dados da empresa via CompanyLookupService.lookup (CNPJa). | true |
Comportamento
- Variável vazia ou ausente: envio normal para o destinatário original.
- Variável preenchida: todo envio é redirecionado para o valor configurado. O destinatário original é registrado no log como
warn. - A interceptação é centralizada nos services
EmailServiceeSmsService: nenhum chamador precisa tratar isso. - Códigos de verificação (email e SMS) usam o bypass
123456quando a interceptação está ativa. BYPASS_CNPJ_CARD_VALIDATION: só ativa se o controller receber a query?document=<cnpj>. Sem o param, o scrape+validate normal é executado mesmo com a flag ligada. O file uploadado continua sendo persistido (auditoria preservada). Ativação gera logwarnBYPASS_CNPJ_CARD_VALIDATION ativo: scrape+validate ignorados....
Exemplo de .env
env
[email protected]
INTERCEPT_SMS_VERIFICATION=31999999999
BYPASS_CNPJ_CARD_VALIDATION=trueEndpoint Dev de Upgrade de Plano
POST /subscriptions/dev/upgrade é um atalho para QA promover uma empresa a um plano pago sem rodar o checkout real (Mercado Pago) nem mexer no banco via SQL. Mesmo padrão do bypass de OTP 123456 e do BYPASS_CNPJ_CARD_VALIDATION: só existe fora de produção.
Comportamento
- Quando
NODE_ENV === 'production', o handler retorna404 NotFoundantes de qualquer side-effect. A rota some efetivamente em produção. - Em dev/staging, marca como
@Public()(sem JWT/owner role): chamada viacurlé suficiente. - A subscription é criada como FREE inativa antes do upgrade caso ainda não exista (
ensureCompanyHasSubscription). applyPaidPlanno repository: status → ACTIVE, periodicidade → MONTHLY,renewAt→ +30 dias,subscribedFor+= 1,providerQtd= valor recebido (default 1).- Logger
SubscriptionServiceregistraDev upgrade applied company=<id> plan=<code> providerQtd=<n>em sucesso ewarnem rejeições (plano não encontrado, plano FREE).
Request / Response
http
POST /api/v1/subscriptions/dev/upgrade
Content-Type: application/json
{ "companyId": "479206ac-...", "planCode": "SYNC", "providerQtd": 2 }planCode: enumSubscriptionPlanType(FREE,SYNCLOW,SYNC,HYPER_SYNC,CUSTOM).FREEé rejeitado com400, usePOST /subscriptions/cancelpara reverter.providerQtd: opcional, default1, mínimo1.
Resposta 201: SubscriptionResponseDto com plano e quotas resolvidos.
Erros
| Status | Cenário |
|---|---|
400 | planCode inválido / companyId não-UUID / planCode=FREE |
404 | Plano informado não encontrado / NODE_ENV=production |
Documento atualizado em Maio 2026
Rate limiting (ThrottlerGuard)
ConditionalThrottlerGuard(src/common/throttle/) é global viaAPP_GUARDe roda depois deJwtAuthGuardeCompanyGuard, então já enxergarequest.user. O BFF tem um guard equivalente.
Como o tráfego é agrupado
| Tráfego | Chave (tracker) | Por quê |
|---|---|---|
| Autenticado | user:<id> | O core só recebe conexões do BFF; agrupar por IP colocaria a plataforma inteira num balde só |
| Anônimo (login, OTP, portais públicos) | ip:<ip real> | Protege brute force sem depender de sessão |
O IP real sai de resolveClientIp (src/common/utils/ip.util.ts): usa o hop de X-Forwarded-For mais próximo do servidor (req.ips[last]), com fallback para req.ip e para o socket. Isso só funciona com app.set('trust proxy', appConfig.trustProxyHops) no main.ts; TRUST_PROXY_HOPS conta os proxies confiáveis (Caddy = 1, e o BFF repassa o mesmo X-Forwarded-For para o core). Aumentar esse número sem ter o proxy correspondente permite que o cliente forje o IP e escape do limite; diminuir joga todos os clientes na mesma chave e devolve 429 para todo mundo.
Limites
| Escopo | Default | Onde |
|---|---|---|
| Global | THROTTLE_LIMIT (300) por THROTTLE_TTL_MS (60s) | ThrottlerModule.forRoot em app.module.ts |
| Rotas públicas de auth | 20 por 5 min | PUBLIC_AUTH_THROTTLE em src/common/throttle/public-throttle.const.ts |
| Rotas sensíveis | @Throttle({ default: { ttl, limit } }) no controller | Ex.: signing, lookup, contract-part-search |
Quando o guard não aplica
| Condição | Efeito |
|---|---|
THROTTLE_DISABLED=true | Pula o rate limit inteiro (local, dev e demo) |
Ambiente não é produção real (isRealProduction) | Pula o rate limit |
Header x-internal-agent batendo INTERNAL_API_KEY e requisição autenticada | Isenta o tráfego interno do Agente |
Resposta do 429
getErrorMessage devolve texto pronto para o usuário (buildThrottleMessage), no lugar do ThrottlerException: Too Many Requests do pacote: "Muitas tentativas em pouco tempo. Aguarde N segundos e tente novamente." O guard também envia Retry-After (em segundos); o BFF repassa esse header e o expõe no CORS (exposedHeaders) para o front conseguir ler o tempo de espera e montar a contagem regressiva.