Pular para conteúdo

As quatro variáveis fora do .env.example, e por que o APP_BASE_URL de produção não tem /app

Motivação

Terceira e última ponta solta da revisão de documentação deste dia.

Duas coisas, e a segunda é a que importa. O .env.example da API não listava quatro variáveis que o config.py lê — quem monta ambiente novo não descobre que existem a não ser lendo o código. E o APP_BASE_URL de produção, que termina sem o sufixo /app que local e dev-remote têm, parecia defeito: os consumidores concatenam direto (f"{settings.APP_BASE_URL}/reset-password?token={token}"), então um valor errado geraria link de e-mail quebrado em produção.

Não é defeito, e a razão merecia estar escrita onde alguém a encontre. A produção serve o app na raiz — builda com VITE_BASE_PATH=/ (infra/prd/web.Dockerfile:23) e o Caddy de lá manda / para o app —, ao contrário de local e dev-remote, que servem sob /app. O valor sem sufixo está certo justamente porque o ambiente é diferente.

O que torna isso digno de comentário é como esse erro falharia: em silêncio. O servidor devolve 200 com a casca da SPA para qualquer path, e quem não casa a rota é o React Router, já no navegador de quem recebeu o e-mail. Não há log, não há 404, não há alerta.

Comportamento atual

  • config.py lê PASSWORD_RESET_TOKEN_EXPIRE_MINUTES, PASSWORD_RESET_MAX_REQUESTS, MAX_UPLOAD_SIZE_MB e BCRYPT_ROUNDS; nenhuma aparece no .env.example.
  • O comentário de APP_BASE_URL no config.py afirmava que "o app é servido sob /app na mesma origem da API", o que vale para local e dev-remote e não para produção.

Comportamento desejado

O .env.example lista o que o código lê, e a diferença de base entre produção e os outros ambientes está escrita nos dois lugares onde alguém a procuraria.

Escopo

apps/api/.env.example, o comentário de APP_BASE_URL em apps/api/app/core/config.py, e o item correspondente da dívida técnica no ROADMAP.md.

Fora do escopo

BCRYPT_ROUNDS entrou comentada, de propósito. Expô-la como linha ativa num arquivo de exemplo convida a baixá-la, e baixar o custo do bcrypt em ambiente que guarda senha de verdade é regressão de segurança. Quem a baixa para 4 é o conftest da suíte, e só lá.

Alterações propostas

  • apps/api/.env.example — as três variáveis benignas, mais BCRYPT_ROUNDS comentada com o aviso, e o bloco de comentário explicando a base por ambiente.
  • apps/api/app/core/config.py — só comentário, nenhuma linha de código: o de APP_BASE_URL passa a dizer que o sufixo não é universal, citar infra/prd/web.Dockerfile:23 e nomear o modo de falha silenciosa.
  • ROADMAP.md — o item sai da lista de dívida aberta e entra na de resolvidos, com a explicação. A redação anterior, escrita horas antes neste mesmo dia, dizia "conferir antes de confiar" e deixava no ar a suspeita de defeito.

Impactos

Nenhum no produto. O diff em config.py foi conferido linha a linha e é exclusivamente comentário — nada de valor default, nada de comportamento —, então este ajuste não publica versão nem tag.

Validação

make preflight — limpo. ruff check no config.py real (copiado para o container, porque a imagem embute o código e checar o container mediria a versão anterior) — passou; nenhuma linha acrescentada passa de 120 colunas.

A afirmação central foi medida, não deduzida: VITE_BASE_PATH=/ em infra/prd/web.Dockerfile:23, o comentário do infra/prd/Caddyfile dizendo que só /api* desvia, e curl na produção confirmando que a raiz responde. O /app/... também devolve 200 — e é exatamente por isso que o erro seria silencioso, o que reforça o comentário em vez de contradizê-lo.

Documentos relacionados

ADR-0003 — a origem única roteada por path, que é a razão de local e dev-remote servirem sob /app. Os dois tweaks anteriores deste dia, de que este é a continuação.