Scaffold do back-end Rails + fundação da USER-001
TLDR: cria a app Rails (
--api) dentro demodules/backend, conecta a um Postgres local via Docker, e implementa as convenções transversais da USER-001 (healthcheck, versionamento, contrato de erro, paginação, correlation-id, CORS, rate limiting) — sem entidades de domínio ainda.
Nota de status: a spec original ficou marcada como
proposed. A app Rails existe emmodules/backendcomhealth_controller.rb,base_controller.rbepaginatable.rb, então o status foi corrigido paradonena migração da documentação.
Contexto
O back-end do nectar-charges hoje é só um placeholder (modules/backend/.module/make/main.mk e
.module/docker/compose.yml com o comentário “to be defined when stack is chosen”). A stack escolhida
é Ruby on Rails.
A feature USER-001 já documenta as convenções transversais de forma agnóstica de linguagem — esta spec é a versão concreta dessa feature para Rails: gera a app, conecta a um banco local, e implementa o que a USER-001 exige antes de qualquer domínio de negócio.
Objetivos
- App Rails (
--api) rodando dentro demodules/backend, seguindo o layout multi-módulo (modules.md). - Postgres local via Docker Compose, usando o stack
ruby+shared/database.ymldo commons. make backend.setup/make backend.upfuncionando ponta a ponta.- Convenções da USER-001 implementadas: healthcheck,
/api/v1, contrato de erro, paginação,X-Request-Id, CORS, rate limiting. - Testes em Minitest (padrão dos demais projetos wehive), sem RSpec.
Fora de escopo
- Qualquer entidade de domínio — esta spec para antes do primeiro model de negócio.
- Idempotência (
Idempotency-Key), que a USER-001 exige mas fica para depois. - Autenticação e RBAC (USER-002).
Mudanças
Scaffold
modules/backend/— app Rails--apigerada (Ruby 4.0.6, Rails 8.1.3, Postgres, Minitest)modules/backend/.ruby-version—4.0.6
Wiring do módulo
| Arquivo | Mudança |
|---|---|
modules/backend/Makefile |
Já inclui $(COMMONS_DIR)/make/main.makefile + .module/make/main.mk — só preenchimento |
modules/backend/.module/make/main.mk |
Targets install, setup (db:prepare), dev/server, test, lint |
modules/backend/.module/docker/compose.yml |
Service server (extends stacks/_base.yml, working_dir: /source/modules/backend, porta ${PORT:-4010}) |
compose.yml (raiz) |
Adiciona ao include: stacks/ruby.yml, shared/database.yml, modules/backend/.module/docker/compose.yml |
.tool-versions (raiz) |
Adiciona ruby 4.0.6 |
.env.example (raiz) |
Adiciona PORT=4010, DATABASE_PORT=5010, DATABASE_NAME, DATABASE_USERNAME, DATABASE_PASSWORD, RUBY_VERSION=4.0.6 |
Banco local
modules/backend/config/database.yml— adapterpostgresql, lendo host/porta/user/senha/nome via env (mesmo padrão dotrgclub-api)- Postgres sobe via
docker compose(servicedatabasedoshared/database.yml), com healthcheck
Fundação USER-001
| Arquivo | Entrega |
|---|---|
config/routes.rb |
Namespace /api/v1 |
app/controllers/health_controller.rb |
GET /health — sem auth, checa banco, 200/503 |
app/controllers/api/v1/base_controller.rb |
rescue_from central traduzindo exceções para { error: { code, message, details? } } com o status correto (400/401/403/404/409/422/429/500/502) |
app/controllers/concerns/paginatable.rb |
Helper de paginação (?page&per_page → { data, meta }) |
config/application.rb |
Geração/propagação de X-Request-Id nos logs (correlation-id) |
Gemfile |
rack-cors (CORS restrito a CORS_ALLOWED_ORIGINS), rack-attack (rate limiting, 429 com Retry-After) |
Datas em ISO-8601 e dinheiro em centavos inteiros ficam como convenção documentada — sem código específico ainda, já que não há entidades de domínio nesta spec.
Os controllers acabaram em
app/ports/controllers/, não emapp/controllers/. Essa mudança veio logo depois, em backend no padrão de skills — ver Camadas do app Rails.
Testes
Minitest (default do Rails), em test/: test/controllers/health_controller_test.rb, testes de
paginação e de contrato de erro. Fixtures onde fizer sentido — sem entidades de domínio ainda, o
volume é mínimo.
Sequência de implementação
Cada passo deixa a suíte verde antes do próximo. Os passos 4–13 seguem o ciclo TDD completo por feature (test → feat), já que a partir do healthcheck há lógica real a testar.
| # | Tipo | Entrega |
|---|---|---|
| 1 | feat | Scaffold da app Rails --api (Ruby 4.0.6, Rails 8.1.3, Postgres, Minitest) |
| 2 | feat | Wiring do módulo — Makefile, .module/**, compose.yml, .tool-versions, .env.example |
| 3 | feat | Postgres local conectado (config/database.yml, make backend.setup rodando db:prepare) |
| 4 | test | Healthcheck — 200 ok / 503 degradado |
| 5 | feat | GET /health |
| 6 | test | Contrato de erro — cada status/code do rescue_from |
| 7 | feat | Api::V1::BaseController + namespace /api/v1 |
| 8 | test | Paginação — ?page&per_page → data/meta |
| 9 | feat | Concern Paginatable |
| 10 | feat | X-Request-Id gerado/propagado nos logs |
| 11 | feat | CORS (rack-cors) restrito a CORS_ALLOWED_ORIGINS |
| 12 | feat | Rate limiting (rack-attack, 429 + Retry-After) |
| 13 | refactor | Revisão geral mantendo os testes verdes |
Como verificar
make backend.setup && make backend.upsobe a app + Postgres local sem errocurl localhost:4010/health→200 { status: "ok", version, time }com banco no ar;503com banco fora- Qualquer rota fora de
/api/v1(exceto/health) não existe - Erros seguem
{ error: { code, message, details? } }com o status HTTP correto - Listagem paginada aceita
?page&per_pagee devolvemetano formato padrão X-Request-Idaparece nos logs de um request- Requests de origem fora de
CORS_ALLOWED_ORIGINSsão bloqueados - Excesso de requests devolve
429comRetry-After make backend.testroda a suíte Minitest e passa
Documentação
- Convenção de módulos — marcar
modules/backendcomo Rails (deixa de ser placeholder) - USER-001 — Fundação do serviço — marcar critérios de aceite atendidos (exceto idempotência, fora desta spec)
commons/ai/rules/shared/ports.md(repo separado, commit à parte) — registrar o código de projeto10paranectar-charges: frontend3010, API4010, banco5010