Arquitetura
Visão geral
O Covil do Mineiro é um monólito modular sobre Next.js 15 (App Router) e Supabase, publicado na Vercel. Não existe um backend separado: o próprio Next.js serve o site, executa a lógica de servidor (Server Actions e o webhook) e conversa com o Supabase, que é o backend gerenciado (Postgres + Auth + Storage), com a segurança ancorada em RLS.
Essa escolha é deliberada para um time pequeno — ver ADR-0001. O que parece "backend e frontend juntos" é, na prática, um sistema em camadas bem separadas.
Diagrama de camadas
flowchart TD
subgraph cliente["Navegador"]
RSC["Server Components<br/>(app/**/page.tsx)"]
CC["Client Components<br/>('use client')"]
end
subgraph servidor["Servidor — Vercel (Next.js)"]
SA["Server Actions<br/>(lib/actions/*)"]
LIB["Regras de negócio<br/>(lib/*)"]
WH["Webhook Sicoob<br/>(api/sicoob/webhook/pix)"]
ADMIN["supabase/admin<br/>(service role · server-only)"]
end
subgraph db["Supabase"]
DB[("Postgres + RLS")]
AUTH["Auth"]
STG["Storage · bucket site-media"]
end
MP["Sicoob — API Pix (BACEN)"]
CC -->|mutações| SA
RSC --> LIB
SA --> LIB
LIB -->|"client servidor/anon<br/>(respeita RLS)"| DB
SA -.->|escrita de pedido| ADMIN
SA -->|"cria cobrança PIX"| MP
MP -->|"webhook (reconsulta por txid)"| WH
WH --> ADMIN
ADMIN -->|"ignora RLS"| DB
LIB --> AUTH
LIB --> STGAs camadas
1. Apresentação (src/app, src/components)
- Server Components (a maioria das
page.tsx) leem dados no servidor e renderizam HTML. Buscam viasrc/lib/content-store. - Client Components (
'use client') cuidam só de interação: estado de UI, formulários, carrinho. Não acessam o banco diretamente. - Componentes de UI reutilizáveis em
src/components/ui; telas admin emsrc/components/admin(padrãoAdminPageFrame/AdminModal/AdminField).
2. Casos de uso — Server Actions (src/lib/actions)
Toda mutação passa por aqui: auth-actions, checkout-actions,
admin-actions, user-actions, loyalty-actions, coupon-actions,
plan-actions, partner-actions, member-photo-actions. Cada action:
- valida autenticação/autorização (guarda de papel e seção);
- valida e recalcula dados no servidor (nunca confia no cliente);
- chama a regra de negócio / o banco;
- revalida os caminhos públicos afetados (
revalidatePath/revalidateTag).
3. Regras de negócio (src/lib)
Lógica reutilizável, idealmente pura e testável:
coupons.ts—evaluateCoupon(puro, testado).loyalty.ts(server-only) +loyalty-format.ts(client-safe).membership-card.ts— número, QR e HMAC de autenticidade da carteirinha.content-store.ts— snapshot do conteúdo público com cache e fallback.sicoob/*— criação de cobrança PIX (createPixCharge) e reconsulta autoritativa portxid(getPixCharge).supabase/*— os clients (anon, browser, server, admin, middleware).
4. Dados e segurança — Supabase
Postgres é a fonte da verdade. Toda tabela tem RLS; a lógica sensível
(pontos, associação, cupons, carteirinha) vive em funções SECURITY DEFINER.
Ver banco-de-dados.md.
Fronteira server/client
| Server Component | Client Component ('use client') | |
|---|---|---|
| Ler dados | ✅ direto via lib/content-store | ❌ recebe por props ou Server Action |
| Mutar dados | via Server Action | via Server Action (nunca banco direto) |
| Segredos | ✅ (server-only) | ❌ nunca |
Módulos com segredo têm import 'server-only' (ex.: supabase/admin.ts,
loyalty.ts, membership-card.ts) — o next build quebra se forem importados
em bundle client. Nos testes, server-only é neutralizado em
src/test/setup.ts.
Autenticação e sessão
- Supabase Auth (e-mail/senha + Google OAuth). O callback OAuth troca o código
por sessão em
/auth/callback. - O
middleware.tsprotege/admin/*e atualiza a sessão (supabase/middleware.ts). Cada página admin revalida o papel comrequireSectionAccess, e cada Server Action revalida de novo — defesa em profundidade, com a RLS como último anteparo. - O cadastro grava o perfil via trigger
handle_new_user()(evita UPDATE bloqueado por RLS no signup).
Integrações externas
- Sicoob (PIX) — cobrança via API Pix Recebimentos (padrão BACEN): o
servidor cria a cobrança e o QR Code + "copia e cola" aparecem na própria
página (
/pedido/retorno, com polling), sem redirect. A confirmação chega pelo webhook emPOST /api/sicoob/webhook/pix, que reconsulta a cobrança portxidna API. Ver o fluxo emseguranca.mdebanco-de-dados.md. - Gmail SMTP (Nodemailer) — e-mails transacionais (recuperação de senha).
- Google Analytics 4 — opcional, ativado por
NEXT_PUBLIC_GA_ID(src/lib/analytics.ts; no-op sem a variável).
Cache e revalidação
- O conteúdo público é lido por
content-store.tscomunstable_cache(tagsite-snapshot, ~60s) e fallback local (src/data/site-fallback.ts) quando o Supabase não está configurado. - As Server Actions admin chamam
revalidateTag('site-snapshot')erevalidatePath(...)para refletir alterações imediatamente.
Estrutura de pastas
src/
app/ rotas: (public), (auth), admin/*, api/*, auth/callback
components/ ui/ (design system), admin/ (managers), layout/, seções
data/ fallback local do conteúdo público
lib/ actions/ (server actions), supabase/, sicoob/, regras
types/ content.ts (domínio) e supabase.ts (schema gerado)
test/ setup do Vitest
supabase/
migrations/ schema evolutivo (SQL, com RLS e funções)
seed.sql dados iniciais
docs/ esta documentaçãoDecisões de arquitetura
Registradas como ADRs em decisoes/. Comece pela
ADR-0001 — Monólito Next.js + Supabase.