Scaffold do back-end Rails + fundação da USER-001

TLDR: cria a app Rails (--api) dentro de modules/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 em modules/backend com health_controller.rb, base_controller.rb e paginatable.rb, então o status foi corrigido para done na 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 de modules/backend, seguindo o layout multi-módulo (modules.md).
  • Postgres local via Docker Compose, usando o stack ruby + shared/database.yml do commons.
  • make backend.setup / make backend.up funcionando 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 --api gerada (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 — adapter postgresql, lendo host/porta/user/senha/nome via env (mesmo padrão do trgclub-api)
  • Postgres sobe via docker compose (service database do shared/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 em app/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.up sobe a app + Postgres local sem erro
  • curl localhost:4010/health → 200 { status: "ok", version, time } com banco no ar; 503 com 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_page e devolve meta no formato padrão
  • X-Request-Id aparece nos logs de um request
  • Requests de origem fora de CORS_ALLOWED_ORIGINS são bloqueados
  • Excesso de requests devolve 429 com Retry-After
  • make backend.test roda a suíte Minitest e passa

Documentação

  • Convenção de módulos — marcar modules/backend como 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 projeto 10 para nectar-charges: frontend 3010, API 4010, banco 5010