Esse projeto implementa todos os requisitos do desafio tecnico FURY. A API em Node.js + TypeScript recebe um webhook de violacao de anuncio, valida o payload com Zod, enfileira um job de takedown com BullMQ/Redis e executa um worker que chama a API publica JSONPlaceholder como simulacao da Meta Ads API.
A API esta publicada em:
https://api-fury.onrender.com
Ao abrir a URL base no navegador, a API responde um JSON simples de status com os endpoints disponiveis.
Nao e necessario rodar o projeto localmente para validar o fluxo principal do desafio. O deploy usa:
- Render Web Service para rodar a API NestJS e o worker BullMQ no mesmo processo.
- Upstash Redis como Redis gerenciado para armazenar a fila e o estado dos jobs.
- JSONPlaceholder como endpoint externo de simulacao da Meta API.
O deploy no Render foi feito com apoio do MCP do Render no Claude Code, mantendo a escolha da arquitetura e das variaveis de ambiente sob responsabilidade do autor.
Documentacao interativa da API:
https://api-fury.onrender.com/docs
OpenAPI JSON:
https://api-fury.onrender.com/openapi.json
Documentos auxiliares para revisao:
Health check operacional:
curl https://api-fury.onrender.com/healthCrie um job:
curl -X POST https://api-fury.onrender.com/webhook/violation \
-H "Content-Type: application/json" \
-d '{
"adId": "ad_render_001",
"tenantId": "tenant_render",
"violationType": "BRAND_VIOLATION",
"severity": "HIGH",
"detectedAt": "2026-05-21T14:00:00.000Z"
}'Use o jobId retornado para consultar o status:
curl https://api-fury.onrender.com/jobs/<jobId>Exemplo de retorno esperado apos o worker processar:
{
"jobId": "takedown-...",
"status": "completed",
"attempts": 1,
"result": {
"ok": true,
"externalStatus": 200,
"processedAt": "2026-05-21T15:41:14.027Z"
},
"error": null
}Tambem e possivel testar validacao enviando um payload invalido. A API retorna 400 com os erros detalhados do Zod.
Foi adicionado Scalar para expor uma documentacao interativa baseada em OpenAPI. A especificacao e gerada pelo @nestjs/swagger, a partir dos controllers e dos DTOs de documentacao, e renderizada pelo @scalar/nestjs-api-reference em /docs.
Essa decisao deixa a avaliacao mais direta: quem revisar o projeto consegue ver os endpoints, payloads, enums, respostas esperadas e erros sem precisar ler todo o codigo primeiro. Como o desafio nao pede front-end, a documentacao interativa entrega uma experiencia de exploracao leve sem adicionar uma interface desnecessaria ao produto.
Foram consideradas algumas opcoes para deploy gratuito ou de baixo custo:
- Oracle Cloud com VM e Docker Compose, mantendo Redis e API em containers separados na mesma maquina.
- Google Cloud Compute Engine com VM, tambem usando Docker Compose com uma porta/container para a API e Redis isolado na rede Docker.
- Render com Upstash Redis.
Para este desafio tecnico, a escolha final foi Render + Upstash Redis. O motivo foi pragmatismo: como o objetivo e demonstrar a integracao com BullMQ, idempotencia, retry e processamento do worker, usar um Redis gerenciado e um Web Service simples evita complexidade operacional desnecessaria para uma entrega de teste. Uma VM com Docker funcionaria bem, mas adicionaria manutencao de servidor, firewall, atualizacoes do SO, rede e persistencia manual. Para este contexto, isso seria mais proximo de overengineering do que de valor para o desafio.
Observacao: no plano gratuito do Render, o servico pode dormir apos periodo de inatividade. Os jobs continuam armazenados no Upstash Redis, mas o worker so processa enquanto o servico esta acordado. Para producao real, a recomendacao seria usar um plano sem sleep ou separar API e worker em servicos dedicados.
O projeto foi desenvolvido com apoio de Claude Code para acelerar ciclos de implementacao e revisao, mantendo as decisoes tecnicas, arquitetura e validacoes sob responsabilidade do autor. Tambem utilizei uma skill de iniciacao de projeto para estruturar uma base consistente de arquitetura limpa, documentacao para agentes (AGENTS.md e CLAUDE.md), Jest desde o primeiro ciclo e regras explicitas de qualidade.
O uso dessas ferramentas nao substituiu as decisoes tecnicas: a arquitetura, as fronteiras de dependencia, os testes, o deploy e os trade-offs de infraestrutura foram documentados no README para facilitar avaliacao e manutencao.
- API publica ja deployada e testada, sem exigir setup local para uma primeira avaliacao.
- Arquitetura em camadas dentro do modulo NestJS, separando dominio, aplicacao, infraestrutura e interfaces.
- Validacao de payload com Zod em pipe proprio, retornando
400com erros por campo. - Idempotencia com
jobIddeterministico baseado em SHA-256 detenantId + adId, evitando expor esses valores diretamente. - BullMQ configurado com retry automatico e backoff exponencial.
- Dead-letter queue para preservar jobs que falham depois de todas as tentativas.
- Worker tratando sucesso
2xx, falha HTTP4xx/5xxe timeout/erro de rede. - Redis local via Docker para desenvolvimento e Upstash Redis com TLS para deploy.
- Testes unitarios cobrindo regra de idempotencia, validacao do webhook e cenarios de sucesso/falha/timeout da chamada externa.
- Testes e2e cobrindo
POST /webhook/violation, payload invalido, idempotencia eGET /jobs/:id. - CI no GitHub Actions rodando typecheck, lint, testes e build a cada push/PR na
main. - Documentacao Scalar com contrato OpenAPI para facilitar avaliacao, teste manual e entendimento dos payloads.
docs/ARCHITECTURE.md,docs/TRADEOFFS.mdedocs/requests.httppara deixar claro o raciocinio tecnico e acelerar a validacao.- Cuidados de seguranca:
.envfora do Git,.env.exampledocumentado,helmet, logs operacionais sem payload completo e sem chaves sensiveis versionadas.
- Node.js 20+
- TypeScript strict
- NestJS
- Zod
- BullMQ
- Scalar API Reference
- Redis local via Docker Compose ou Upstash Redis no deploy
- Jest
- ESLint
O projeto usa uma organizacao por feature com camadas limpas:
src/
config/
takedown/
domain/
application/
ports/
use-cases/
infrastructure/
http/
queue/
interfaces/
http/
queue/
Regra de dependencia:
interfaces -> application -> domain
infrastructure -> application
O dominio nao importa Nest, BullMQ, Redis, clientes HTTP ou configuracao. A aplicacao define os casos de uso e portas. A infraestrutura implementa as portas. As interfaces entregam HTTP e processamento de fila.
Antes de codar, foi feita uma janela de pelo menos 3 minutos de curl contra https://jsonplaceholder.typicode.com/posts/1. O endpoint retornou 200 em todas as amostras reais, com tempos em torno de 50ms a 75ms. Um timeout induzido com --max-time 0.001 retornou falha de timeout, e uma chamada curta para /posts/999999 retornou 404.
Decisoes derivadas disso:
2xxe tratado como sucesso.4xx/5xxvira erro controlado e deixa o BullMQ aplicar retry.- Timeout/erro de rede tambem vira erro controlado e retryavel.
- O corpo da resposta externa nao e validado nem mapeado, porque o desafio pede apenas testar o fluxo HTTP.
Referencias oficiais consultadas:
- NestJS Queues: https://docs.nestjs.com/techniques/queues
- NestJS Pipes: https://docs.nestjs.com/pipes
- BullMQ retries/backoff: https://docs.bullmq.io/guide/retrying-failing-jobs
POST /webhook/violationrecebe o webhook.GET /retorna status e endpoints disponiveis.GET /healthvalida processo da API, RedisPINGe configuracao da URL externa.GET /docsexibe a documentacao interativa Scalar.GET /openapi.jsonexpoe a especificacao OpenAPI.- Payload validado com Zod e erro
400detalhado em caso invalido. - BullMQ enfileira job
meta-ad-takedown. - Redis roda localmente via Docker Compose.
- Worker chama
https://jsonplaceholder.typicode.com/posts/1. - Retry automatico com backoff exponencial, maximo de 3 tentativas por padrao.
- Jobs que esgotam todas as tentativas sao registrados na fila
takedown-dead-letter. - Idempotencia por
adId + tenantIdusandojobIddeterministico com SHA-256. GET /jobs/:idretorna{ jobId, status, attempts, result, error }.
Instale as dependencias:
npm installSuba o Redis local:
docker compose up -d redisCrie seu .env a partir do exemplo, se quiser sobrescrever defaults:
cp .env.example .envInicie a API e o worker no mesmo processo Nest:
npm run start:devA API sobe em http://localhost:3000 por padrao.
{
"adId": "ad_123",
"tenantId": "tenant_456",
"violationType": "BRAND_VIOLATION",
"severity": "HIGH",
"detectedAt": "2026-05-21T14:00:00.000Z"
}Exemplo:
curl -X POST http://localhost:3000/webhook/violation \
-H "Content-Type: application/json" \
-d '{
"adId": "ad_123",
"tenantId": "tenant_456",
"violationType": "BRAND_VIOLATION",
"severity": "HIGH",
"detectedAt": "2026-05-21T14:00:00.000Z"
}'Resposta esperada:
{
"jobId": "takedown-...",
"status": "waiting",
"deduplicated": false
}Se o mesmo adId + tenantId for enviado novamente, o mesmo jobId sera retornado e nenhum segundo job sera criado para a mesma chave.
curl http://localhost:3000/jobs/takedown-<sha256>Resposta:
{
"jobId": "takedown-...",
"status": "completed",
"attempts": 1,
"result": {
"ok": true,
"externalStatus": 200,
"processedAt": "2026-05-21T14:00:00.000Z"
},
"error": null
}| Nome | Default | Uso |
|---|---|---|
PORT |
3000 |
Porta HTTP da API |
HOST |
0.0.0.0 |
Interface de rede para escutar em deploys como Render |
REDIS_HOST |
localhost |
Host do Redis |
REDIS_PORT |
6379 |
Porta do Redis |
REDIS_PASSWORD |
vazio | Senha do Redis, se existir |
REDIS_TLS |
false |
Use true para Redis hospedado com TLS, como Upstash |
META_API_SIMULATION_URL |
https://jsonplaceholder.typicode.com/posts/1 |
Endpoint HTTP externo usado pelo worker |
META_API_TIMEOUT_MS |
3000 |
Timeout da chamada externa |
TAKEDOWN_JOB_ATTEMPTS |
3 |
Maximo de tentativas do job |
TAKEDOWN_JOB_BACKOFF_MS |
1000 |
Delay base do backoff exponencial |
npm run start:dev: inicia API e worker com watch.npm run build: compila o projeto Nest.npm run start: rodadist/main.js.npm run typecheck: valida TypeScript sem emitir arquivos.npm run lint: roda ESLint.npm test: roda Jest.npm run test:watch: Jest em watch mode.npm run test:coverage: Jest com cobertura.
Os testes focam em comportamento de aplicacao, validacao de entrada e fluxo HTTP:
ReportViolationUseCasegarantejobIddeterministico e nao revelador.violationWebhookSchemagarante rejeicao de payload invalido com issues por campo.test/e2e/takedown.e2e.spec.tscobre webhook valido, payload invalido, duplicidadeadId + tenantIde consulta de status.
Infraestrutura externa e mockada por portas quando necessario. Testes unitarios nao fazem chamadas HTTP reais.
.enve ignorado pelo Git.- Apenas
.env.examplee versionado. - O
jobIdusa SHA-256 detenantId + adId, evitando expor esses valores em URLs. - Os logs cobrem entrada do webhook, validacao invalida, aceite/deduplicacao de job, consulta de status e processamento do worker sem imprimir payload completo.
- Erros da chamada externa sao normalizados sem incluir corpo da resposta.
helmete habilitado na inicializacao do Nest.
Este projeto foi iniciado com uma skill de iniciacao de projeto.
O bootstrap:
- Criou limites de arquitetura limpa dentro do modulo NestJS.
- Adicionou
AGENTS.mdeCLAUDE.mdpara manter as mesmas regras com assistentes de IA. - Configurou Jest desde o inicio.
- Documentou comandos, variaveis, fluxo Docker Redis e padroes de codigo.
- Fez uma pesquisa rapida em documentacao oficial de NestJS/BullMQ antes de consolidar o layout.
- Alterar dominio/aplicacao primeiro.
- Adicionar ou ajustar testes Jest.
- Implementar adaptadores em infraestrutura.
- Expor comportamento via controllers/processors.
- Rodar
npm run typecheck,npm test,npm run lintenpm run build. - Atualizar README quando endpoints, comandos, variaveis ou arquitetura mudarem.