Arquitetura
Stack confirmado (package.json)
- Next.js 16.2.6 + React 19.1 + TS 5 strict
- Cache Components (
cacheComponents: true) + Turbopack em dev/build
- Drizzle ORM 0.45 +
postgres-js 3.4 — schema source-of-truth
- Better Auth 1.6 (email/password) — HTTP adapter custom que delega tudo pra vek1-api
- Tailwind v4 + shadcn/ui (new-york / slate) + Radix
@aws-sdk/client-s3 3.10 — MinIO S3-compat
- Jotai 2 (
selectedStoreAtom, currentPageAtom) + SWR 2 (orders SSE)
- Vitest 4 + Testing Library + jsdom (
@vitejs/plugin-react)
- Bun runtime no CI
openai SDK 5 instalado mas não é caminho real (vek1-api consome direto)
Layout src/
src/
app/
(internal)/ ← rotas auth-gated (sidebar layout)
agents/ ← lista, create, [id]/preview, [id]/settings
products/, products/[id]/
documents/[storeId]/
upload/ ← wizard CSV → produtos → embeddings
leads/, leads/[id]/ ← NEW (PR #48)
orders/, orders/[id]/ ← NEW (PR #50)
settings/ ← NEW (PR #51) — account/company/store payment/stock-sync
dashboard/, client-stats/, database-stats/, token-usage/
layout.tsx ← sidebar + auth gate
actions/ ← Server Actions (thin — chamam apiClient)
api/
auth/[...all]/ ← Better Auth route handler
webhooks/
evolution/ ← WhatsApp webhook (POST)
abacate-pay/ ← PIX webhook (POST, X-Webhook-Token relay)
internal/
orders/ ← endpoints chamados pelo backend (raros)
agents/, leads/, orders/, products/, stores/, dashboard/, company-profile/, ...
orders/stream/ ← SSE realtime (Postgres LISTEN/NOTIFY)
process-file/, upload-file/, delete-file/ — file handling
auth/callback/route.ts ← OAuth callback (legado, não usado)
forgot-password/, reset-password/ ← NEW (PR #72)
instance-settings/ ← QR Code WhatsApp
login/, register/, stores/, page.tsx (landing), layout.tsx
components/
ui/ ← shadcn primitives
agents/, documents/, upload/, sidebar/, dashboard-page/, charts/
[muitos componentes soltos no root — gotcha histórico]
lib/
api-client/ ← canônico pra tudo que toca vek1-api
http.ts ← callApi(path, {scope, method, body, search, actorUserId})
index.ts ← apiClient = { auth, orders, leads, agents, products, documents, stores, company, dashboard, messages, audit, tokenUsage, email }
auth.ts, orders.ts, leads.ts, agents.ts, products.ts, documents.ts, stores.ts, company.ts, dashboard.ts, messages.ts, audit.ts, token-usage.ts, email.ts
auth.ts ← betterAuth({ database: httpAdapter, ... })
auth-http-adapter.ts ← createAdapterFactory({...}) — converte camel↔snake, parse timestamps, dispatcha pra apiClient.auth.*
auth-client.ts, auth-server.ts ← Better Auth client + helpers SSR (getCurrentUser/requireUser)
db/{index,schema}.ts ← Drizzle (schema source-of-truth, reads SSR raros)
storage.ts ← MinIO S3 client + uploadObject/deleteObject/publicUrl
embeddings.ts ← POST ${NEXT_PUBLIC_API_URL}/embed (bge-m3 1024 dim)
evolution-api.ts ← legado (singleton fetch helper)
evolution-instance.ts ← NEW — fetch direto Evolution v2.3.x (createInstance, fetchQrCode, listInstances)
whatsapp-handler.ts ← processWhatsAppMessage (webhook → ensureLead → apiClient.messages → chat → reply)
abacate-pay.ts ← cliente PIX (createCharge, signature verify)
orders.ts, order-notifications.ts, order-proofs.ts
leads.ts ← ensureLead, enqueueExtraction, buildLeadProfileSummary
queries/ ← cache() + 'server-only' (poucas — maioria via apiClient)
supabase.ts ← thin wrapper apiClient (compat snake_case)
format-phone.ts, mock-data.ts, product-handlers.ts, utils.ts, utils/redirect.ts
hooks/ ← use-auth (Better Auth client), use-agents, use-documents, etc.
jotai/
pages.tsx, stores.ts ← currentPageAtom + selectedStoreAtom (atomWithStorage)
types/ ← database.types.ts (shim snake_case), custom.database.types.ts
middleware.ts (proxy.ts no Next 16) — auth gate
drizzle/
schema.ts já é source-of-truth; sem SQL migrations versionadas (db:push --force)
scripts/
recreate-indexes.ts ← hnsw (vector_cosine_ops) + unique constraints custom
.github/workflows/ci.yml ← lint + test + build (Bun)
Camadas
| Camada |
Onde |
Notas |
| Read (SSR canônico) |
lib/api-client/* no servidor |
cache: 'no-store' no fetch interno; Cache Components decide quando re-renderizar |
| Read direto DB |
lib/queries/* (reduzido) |
só pra cenários onde SSR precisa do shape Drizzle puro |
| Mutations |
lib/api-client/* exclusivamente |
Server Actions e Route Handlers viraram thin wrappers (auth + ownership check + chamada apiClient) |
| Auth |
lib/auth.ts + lib/auth-http-adapter.ts |
Better Auth com adapter HTTP custom — toda persistência sai pra vek1-api |
| Apresentação |
app/**/page.tsx (RSC) → components/ |
'use client' só em interativos |
| Estado global |
src/jotai/* |
selectedStoreAtom, currentPageAtom |
| Realtime |
api/orders/stream/route.ts |
SSE + Postgres LISTEN/NOTIFY |
Agrupamentos de rota
(internal) — route group sem prefixo, ClientLayout (sidebar + store-switcher + auth gate). Tudo aqui exige login + store selecionada.
- Públicas —
/, /login, /register, /forgot-password, /reset-password, /auth/callback.
- Standalone autenticadas —
/stores/*, /instance-settings.
Convenções
- Server Components default;
'use client' só interativo
- Mutações só via
apiClient (nunca Drizzle direto do frontend; legado em lib/supabase.ts ainda passa por apiClient internamente)
- Storage centralizado em
lib/storage.ts (single bucket vek1, key prefix companies/{user.id}/)
- Sem
any; interfaces explícitas
- Estilo só Tailwind +
cn()
- Path alias:
@/* → src/*
Token scopes (callApi(path, {scope}))
| Scope |
Header |
Quando usar |
app (default) |
X-Internal-Token |
maioria — orders/leads/products/etc. + X-Actor-User-Id pra ownership check |
auth |
X-Auth-Token |
só httpAdapter Better Auth → /internal/auth/* |
webhook |
X-Webhook-Token |
webhooks que precisam disparar fire-and-forget pro backend (ex: email reset, AbacatePay relay) |
Cada token vive em env separada no Vercel: INTERNAL_API_TOKEN, INTERNAL_AUTH_TOKEN, INTERNAL_WEBHOOK_TOKEN. Fallback: app token serve pros 3 se dedicados ausentes (transição).