API backend para emissão de receita e atestado digital.
- Projeto focado em API only com Ruby on Rails.
- Não haverá frontend neste MVP.
- Backend inclui autenticação, autorização, pacientes, documentos, assinatura, validação e envios assíncronos.
- Infraestrutura com Docker faz parte do escopo (base já existente com
Dockerfile). - Nginx local opcional para proxy reverso de domínio (ex.:
api.prescsign.local).
- Ruby:
3.3.1 - Rails:
7.1.6 - Banco de dados: PostgreSQL
- Jobs assíncronos: Sidekiq + Redis
- Autenticação: Devise + JWT
- Autorização: Pundit
ruby -v
bundle exec rails -vSaída esperada:
ruby 3.3.1Rails 7.1.6
- Arquivo existente:
Dockerfile docker-compose.ymlcom serviços:api(Rails)nginx(proxy reverso para a API)db(PostgreSQL)redissidekiq
# subir ambiente completo
docker compose up --build
# subir em background
docker compose up --build -d
# derrubar ambiente
docker compose down
# derrubar ambiente e volumes (reset de banco/redis)
docker compose down -vDefina no .env:
API_PORT_HOST=3300
NGINX_PORT_HOST=8080
POSTGRES_PORT_HOST=55432
REDIS_PORT_HOST=56379Assim, no host você acessa:
- API em
http://localhost:3300 - Nginx em
http://localhost:8080(proxy para a API) - PostgreSQL em
localhost:55432 - Redis em
localhost:56379
# logs da API
docker compose logs -f api
# logs do Sidekiq
docker compose logs -f sidekiq
# shell no container da API
docker compose exec api bash
# rodar migrações manualmente
docker compose exec api bin/rails db:migrate- API:
GET /up - PostgreSQL:
pg_isready - Redis:
redis-cli ping - Sidekiq: verificação de processo do worker
make up-d
make logs-api
make migrate
make console
make rails cmd='db:seed'Todos os comandos bin/rails devem ser executados no container da API via docker compose exec api ... (ou via make).
- Arquivo adicional:
docker-compose.prod.yml - Uso:
make prod-up-d
make prod-logs
make prod-downO entrypoint usa bin/wait-for-services quando WAIT_FOR_DEPENDENCIES=true para aguardar:
- PostgreSQL (
pg_isready) - Redis (
redis-cli ping)
Isso reduz falhas de boot em cenários onde o container inicia antes dos serviços ficarem prontos.
Este projeto usa três ambientes padrão:
development: foco em produtividade local, reload habilitado,active_jobpadrãoasync.test: foco em previsibilidade,active_jobem:test, mailer em:test.production: foco em segurança/performance, eager load ligado, SSL forçado e configurações por variáveis de ambiente.
- Arquivo versionado:
.env.example.erb - Uso local:
- copie
.env.example.erbpara.env - ajuste os valores para o seu ambiente
- copie
Observação: o repositório ignora .env*, então o template versionável foi definido como .env.example.erb.
development:POSTGRES_HOST,POSTGRES_PORT,POSTGRES_USER,POSTGRES_PASSWORDPOSTGRES_DB_DEVELOPMENT- opcionais:
APP_HOST(defaultapi.prescsign.local),APP_PORT,APP_PROTOCOL,RAILS_LOG_LEVEL
test:POSTGRES_HOST,POSTGRES_PORT,POSTGRES_USER,POSTGRES_PASSWORDPOSTGRES_DB_TEST
production:APP_HOST(obrigatória, exemploapi.prescsign.com)CORS_ALLOWED_ORIGINS(obrigatória; lista separada por vírgula com origens confiáveis)- recomendado:
DATABASE_URL - alternativamente:
POSTGRES_HOST,POSTGRES_PORT,POSTGRES_USER,POSTGRES_PASSWORD,POSTGRES_DB_PRODUCTION APP_PROTOCOL(defaulthttps)ACTIVE_JOB_QUEUE_ADAPTER(recomendadosidekiq)ACTIVE_STORAGE_SERVICE(recomendados3para provider cloud)JWT_SECRET_KEY(obrigatória)
- Leitura padronizada:
config/initializers/app_config.rb(viaRails.application.config.x) - Estratégia:
- em
production, variáveis críticas sem valor levantam erro explícito no boot - em
development/test, o app usa defaults seguros para não bloquear setup local - integrações externas ficam desabilitadas por padrão até receberem credenciais
- em
- Redis:
REDIS_URL(redis://localhost:6379/1por padrão)
- JWT:
JWT_SECRET_KEY(obrigatória emproduction; default localdev-only-change-me)AUTH_USERS_REQUIRED(defaultfalse; habilita exigência de identidade emusers)AUTH_USERS_FALLBACK_PROVISIONING(defaulttrue; permite provisionamento de fallback)
- Migração de
users:USERS_MIGRATION_PHASE(defaultphase2_users_auth_enabled; identifica a fase ativa do rollout)phase2_users_auth_enabled: transição com fallback controladophase3_users_required: cutover final, exige identidade emusers
USERS_MIGRATION_ALLOW_DOCTOR_FALLBACK(defaulttrue; liga/desliga fallback de médicos)
- Observabilidade de rollout:
OBS_ROLLOUT_PHASE(defaultusers_migration; etiqueta a fase nos eventos de observabilidade)
- CORS:
CORS_ALLOWED_ORIGINSdefine allowlist de origens (CSV)- default local:
http://localhost:5173,http://127.0.0.1:5173 - em
production, deve conter apenas domínios confiáveis do frontend
- S3/R2:
S3_BUCKEThabilita integração- quando habilitada em
production, exigeS3_ACCESS_KEY_ID,S3_SECRET_ACCESS_KEY,S3_REGION - opcionais:
S3_ENDPOINT,S3_FORCE_PATH_STYLE
- Diretório:
documents/{document_id}/v{version_number} - Nome do arquivo:
{document_kind}_{timestamp_utc}.pdf- exemplo:
prescription_20260414T123456Z.pdf
- exemplo:
- Chave completa (Active Storage):
documents/{document_id}/v{version_number}/{document_kind}_{timestamp_utc}.pdf - Retenção operacional (MVP): ver docs/RETENTION_POLICY.md
- SendGrid:
SENDGRID_API_KEYhabilita integração- quando habilitada em
production, exigeSENDGRID_FROM_EMAIL - timeout de envio por canal:
DELIVERIES_TIMEOUT_SECONDS(default10)
- Twilio:
TWILIO_ACCOUNT_SIDhabilita integração- quando habilitada em
production, exigeTWILIO_AUTH_TOKEN,TWILIO_FROM_NUMBER
- WhatsApp:
WHATSAPP_ACCESS_TOKENhabilita integração- quando habilitada em
production, exigeWHATSAPP_PHONE_NUMBER_ID - opcional com default:
WHATSAPP_API_VERSION=v20.0
- Sentry:
SENTRY_DSNhabilita integração- opcionais com default:
SENTRY_ENVIRONMENT(Rails.env),SENTRY_TRACES_SAMPLE_RATE=0.0,SENTRY_TIMEOUT_SECONDS=2
- Geração de PDF:
- timeout de renderização:
PDF_GENERATION_TIMEOUT_SECONDS(default20)
- timeout de renderização:
- Regressão crítica:
bundle exec rake qa:users_migration_regression
- Gate de prontidão para cutover:
bundle exec rake users:migration:readiness
- Runbook:
A definição detalhada do MVP e checklist operacional estão mantidas em documentos locais de trabalho (fora do versionamento do Git).
- Documento de referência de endpoints e payloads: docs/API_CONTRACTS.md
- Prefixo oficial versionado:
/api/v1 - Compatibilidade temporária: endpoints legados em
/v1permanecem ativos
- Sucesso:
{ "data": ..., "meta": ... } - Erro:
{ "errors": [{ "code": "...", "message": "..." }], "error": "mensagem principal", "error_code": "...", "meta": { "request_id": "...", "status": 4xx/5xx } } - Compatibilidade temporária: respostas com
dataem objeto também expõem os campos no topo.
- Query params padrão em endpoints de listagem:
page(default1)per_page(default20, máximo100)sort_by(whitelist por endpoint)sort_dir(asc/desc, com default por endpoint)
metainclui:page,per_page,total,total_pages,sort_by,sort_dir.
Fluxo disponível na API para o frontend:
- Usuário informa e-mail no formulário "Esqueci minha senha".
- Front chama
POST /v1/auth/password. - API sempre retorna
200com mensagem neutra para evitar enumeração de contas. - Usuário recebe token de reset pelos canais definidos pela implementação do frontend.
- Front abre tela "Nova senha" e envia token + nova senha para
PUT /v1/auth/password. - Com sucesso, redirecionar para login e autenticar com a nova senha.
POST /v1/auth/password
Payload:
{
"doctor": {
"email": "medico@exemplo.com"
}
}Resposta (200):
{
"message": "If this email exists, reset instructions were sent"
}PUT /v1/auth/password
Payload:
{
"doctor": {
"reset_password_token": "token_recebido_no_fluxo_de_reset",
"password": "novaSenha123",
"password_confirmation": "novaSenha123"
}
}Resposta de sucesso (200):
{
"message": "Password updated successfully"
}Resposta de validação (422):
{
"errors": [
"Reset password token is invalid"
]
}- Exigir senha e confirmação iguais.
- Exibir erro de token inválido/expirado com ação de "solicitar novo link".
- Não revelar se o e-mail existe no sistema na etapa de solicitação.
A API usa pundit para autorização por recurso.
- Integração central em
ApplicationControllercom:include Pundit::Authorizationrescue_from Pundit::NotAuthorizedErrorretornando403pundit_userbaseado nocurrent_doctorautenticado
- Fluxo de perfil do médico (
/v1/auth/me) protegido porDoctorPolicy.
DoctorPolicy:- permite
show/update/destroyapenas para o próprio médico autenticado.
- permite
PrescriptionPolicy:- escopo por
doctor_id - bloqueia
update/destroyquandostatus == "signed".
- escopo por
MedicalCertificatePolicy:- escopo por
doctor_id - bloqueia
update/destroyquandostatus == "signed".
- escopo por
DocumentPolicy:- escopo por
doctor_id - permite mutação apenas quando
statusé mutável (issued).
- escopo por
PatientPolicy:- escopo retorna apenas pacientes vinculados ao médico autenticado
- vínculo considerado por registros de receitas, atestados ou documentos.
Foram adicionados specs para policies em spec/policies.
# executar somente policies
docker compose run --rm api bundle exec rspec spec/policies
# executar suíte completa
docker compose run --rm api bundle exec rspec- Formatação base de arquivos:
.editorconfig - Guia de organização de classes:
docs/CODE_CONVENTIONS.md