Estudo de Caso 2: Uma Pequena Ferramenta SaaS com Autenticação e Banco de Dados
A ideia
Uma designer freelancer queria um painel privado para registrar horas cobráveis por cliente e exportar um CSV (Comma-Separated Values — um arquivo de planilha em texto simples que qualquer programa de planilhas consegue abrir) mensal. Nada sofisticado, mas precisava de contas (para que os dados dela fossem só dela) e persistência.
A especificação
Autenticação e um banco de dados elevam a aposta, então a especificação ficou mais específica sobre limites:
A logged-in web app where a user can: sign up / log in with email,
create clients, log time entries (date, client, hours, note),
see a table of entries filtered by month, and download that month
as CSV. Each user only ever sees their own data. Mobile-friendly.
"Each user only ever sees their own data" (cada usuário só vê seus próprios dados) parece uma linha de experiência do usuário. Na verdade é o modelo de segurança de todo o app, comprimido em poucas palavras. Nomear isso na especificação significou que podíamos apontar de volta para ela sempre que a IA se desviasse.
A stack
Para quem constrói sozinho, a jogada vencedora é uma stack em que autenticação e banco de dados são serviços gerenciados, não código que você escreve. Escolhemos um app Next.js implantado em uma hospedagem , com um banco de dados Postgres hospedado e um provedor de autenticação plug-and-play que cuida de login por e-mail, sessões e redefinição de senha por nós. Menos código para dar errado é menos código que uma IA pode errar em nosso nome. Autenticação em particular é uma categoria que você quase nunca quer construir do zero com uma IA: os modos de falha são silenciosos, o raio de impacto é a conta de todo mundo, e um provedor gerenciado já teve seus casos extremos testados por milhões de logins.
As peças gerenciadas ficam por fora; a única regra que você é dono fica no meio — toda consulta filtra por user_id, para que um usuário nunca consiga ler as linhas de outro:
┌──────┐ ┌─────────────┐ ┌──────────────────┐
│ USER │─ login ─▶│ AUTH │─ user ─▶│ NEXT.JS APP │
└──────┘ │ (managed) │ id │ │
└─────────────┘ │ every query: │
│ WHERE user_id=? ─┼──┐
└──────────────────┘ │
▼
┌──────────────────┐
│ POSTGRES (managed)│
│ clients · entries │
└──────────────────┘
↑ managed = less to get wrong ↑ the one boundary YOU enforce
Os prompts principais
Deixamos que o próprio template do provedor de autenticação fizesse o trabalho pesado, e depois direcionamos a IA a colocar a lógica de domínio por cima:
We're using [auth provider]'s Next.js starter. Add a Postgres
schema with two tables: clients (id, user_id, name) and
time_entries (id, user_id, client_id, date, hours, note). Every
query MUST filter by the logged-in user's id from the session.
Generate the migration and the typed data-access functions.
A linha "MUST filter by user_id" foi a frase mais importante de todo o projeto. Repetimos essa restrição em quase todo que tocava em dados, porque o bug mais assustador em um app multiusuário é um usuário ver as linhas de outro. A repetição parece redundante enquanto você a digita; é exatamente essa redundância que salva você, porque o modelo não tem memória de o quanto essa restrição importa entre prompts separados.
Para a exportação:
Add a /api/export route that takes a month (YYYY-MM), pulls the
logged-in user's time_entries for that month joined to client
names, and streams a CSV download. Reject the request if there's
no valid session.
O obstáculo
Durante os testes, criamos duas contas e descobrimos que a conta B conseguia ver os clientes da conta A em um menu suspenso. Esse é exatamente o bug que temíamos. Em vez de pedir para a IA "consertar", fizemos com que ela primeiro comprovasse o problema:
Account B is seeing Account A's clients. Show me every database
query in the codebase that reads the clients table, and for each
one tell me whether it filters by the session user_id. Don't fix
anything yet — just audit.
A auditoria trouxe à tona uma consulta — o carregador do menu suspenso — que tinha sido escrita antes de adicionarmos a restrição e escapou pelas frestas. Pedimos que ela adicionasse o filtro que faltava, e depois pedimos uma proteção:
Add a single helper that every read goes through, which takes the
session and injects the user_id filter, so no future query can
forget it. Refactor the existing queries to use it.
Isso transformou um conserto pontual em uma garantia estrutural. Depois tornamos a garantia testável, porque uma proteção que você não consegue verificar é só uma esperança:
Write a test that creates two users, has each create a client,
then asserts that user A's session can never read user B's client
through any of the data-access functions.
A lição: quando uma IA introduz um bug de segurança, não fique apenas consertando a instância — direcione-a a remover a categoria do erro, depois tranque a categoria com um teste que falha ruidosamente se alguém reabrir a brecha.
O lançamento
Semeamos um mês de teste com dados, exportamos o CSV, abrimos em uma planilha para confirmar que os números e a codificação estavam certos, depois definimos uma senha forte para o banco de dados e rotacionamos as credenciais para fora de qualquer arquivo local. Implantamos na hospedagem serverless, adicionamos as variáveis de ambiente de produção no painel dela, e demos a para ela. Ela mesma se cadastrou com um cadastro real. A construção inteira levou um fim de semana.