Skip to content

Latest commit

 

History

History
99 lines (77 loc) · 7.63 KB

File metadata and controls

99 lines (77 loc) · 7.63 KB

Architecture — DeskcommCRM

Visão de 1 página. Profundidade vive em docs/specs/ e docs/stories/epics/MASTER.md. Mapa de toda a documentação: docs/index.md. Estado real de implementação (o que está pronto vs. incompleto): docs/current-state.md.

Camadas

  • App (Next.js 16 App Router): UI + Route Handlers no mesmo repo. Server Components por default, Client onde precisa de estado. Middleware de borda em proxy.ts (Next 16 renomeou middleware.tsproxy.ts).
  • DB (Supabase Postgres): RLS em toda tabela tenant-aware via fn_user_org_ids(). Migrations versionadas em supabase/migrations/.
  • Auth (Supabase Auth + @supabase/ssr): cookie SameSite=Strict. Sempre getUser() no server, nunca getSession(). MFA TOTP é opcional e ligado por quem administra — duas políticas independentes que somam (platform_admins.mfa_required e organizations.settings.security.mfa_required), ambas com padrão não exigir; regra pura em lib/auth/politica-mfa.ts. Esta linha dizia "forçado pra admin/super-admin", que era a regra antiga: como o install.sh cria o dono como platform admin, toda instalação self-host recebia um bloqueador de tela cheia logo após o onboarding. Cadastrar e provar são coisas diferentes — quem TEM fator prova na sessão sempre, independente da política.
  • Realtime (Supabase Realtime): postgres_changes para inbox/kanban; broadcast para sinais leves.
  • Storage (Supabase Storage): bucket whatsapp-media privado, URLs assinadas.
  • WhatsApp (WAHA Plus / engine NOWEB): HMAC-SHA512 webhooks; throttle anti-banimento; STOP detection.
  • Filas (event sourcing leve): event_log table + workers via cron. Trigger Postgres NUNCA faz HTTP.
  • Rate limit (Upstash Redis): contador de janela fixa (INCR + EXPIRE) em lib/ai/dispatcher/rate-limit.ts, com fallback in-memory quando Redis falta. ⚠️ Aplicado hoje em apenas 2 pontos (webhook de captação e dispatcher de IA) — o surface público de auth está sem. Ver docs/threat-model.md §T1.
  • AI (Vercel AI Gateway): Anthropic primário, OpenAI backup pra embeddings.
  • Observability (Sentry): beforeSend scrubs PII (CPF/email/phone) e headers sensíveis.

Multi-tenancy

organization_id uuid not null em toda tabela tenant-aware. RLS via helper. Service role bypassa RLS — handlers admin DEVEM filtrar organization_id manualmente, resolvido de fonte confiável (cookie/JWT/webhook secret/path token), nunca do body.

Detalhes: docs/specs/01-spec-platform-base.md.

API REST /api/v1/

  • JSON snake_case. UUID v4. ISO-8601 UTC. Dinheiro _cents + currency.
  • Wrappers ok() / fail() em lib/api/wrappers.ts.
  • Auth dual: cookie session (frontend) ou Authorization: Bearer tok_... (server-to-server).
  • X-Request-Id em toda response, injetado em proxy.ts e correlacionado com o audit log.
  • Idempotency-Key é o contrato para POSTs de criação; duas rotas gravam recibo (lgpd/requests/[id]/approve e admin/tenants) e existe o helper reutilizável lib/api/idempotency.ts, aplicado em message-templates. Ainda não cobre as demais rotas de criação, e não fecha a corrida entre requisições simultâneas com a mesma chave (exige mudança de schema — issue #778). Meça em vez de citar: grep -rln 'Idempotency-Key' app/api/v1 --include='route.ts'.
  • Detalhes: docs/specs/01-spec-platform-base.md §API.

Fluxo de uma requisição

Rota autenticada de tenant (/api/v1/*, 166 handlers):

request → proxy.ts (X-Request-Id, x-pathname; isPublicPath? → bypass;
                    senão valida sessão Supabase via cookie sb-deskcomm-auth)
        → route handler:
             1. Zod valida o input externo
             2. guard: requireRole() | requirePlatformAdmin() | secret/HMAC
             3. resolveActiveOrg() → organization_id de fonte confiável (nunca do body)
             4. query (RLS pelo client de sessão, ou filtro manual de org com service role)
             5. audit() fire-and-forget se houve mutação
             6. ok(data, meta) | fail(code, message, status)

Superfícies não-cookie: /api/v1/cron/* (Bearer INTERNAL_CRON_SECRET, fail-closed), /api/internal/* (x-internal-secret), /api/mcp (Bearer tok_... contra api_tokens), /api/v1/webhooks/* (HMAC + path token). Inventário completo em docs/threat-model.md §1.

Turno do agente de IA: inbound WhatsApp → HMAC + idempotência → event_log → worker → runAgentTurn (RAG + tools MCP) → guardrails before-send → adapter WAHA → handoff humano se gatilho. Diagrama: docs/architecture/agent-turn.html.

Event log + workers

Triggers Postgres emitem linhas em event_log. Workers (cron / Realtime listener) consomem e disparam side effects. Idempotência via unique (organization_id, external_id) + captura code === '23505'.

Workers vivem em workers/ (ai-response, ai-sentiment, rag-indexer, media-persist, media-derive, lgpd-export, lgpd-redact, storage-cleanup, agent-worker), drenados pelos 10 endpoints em app/api/v1/cron/. Contrato: docs/specs/07-spec-events-workers.md.

Integrações externas

Serviço Uso Onde Falta ⇒
Supabase Postgres + Auth + Realtime + Storage lib/supabase/{browser,server,admin}.ts app não sobe (obrigatório sempre)
WAHA Plus (NOWEB) WhatsApp: envio, recebimento, sessões multi-número lib/waha/ canal indisponível; obrigatório em produção
Upstash Redis rate limit + debounce de RAG lib/ai/dispatcher/rate-limit.ts, lib/ai/rag/debounce.ts degrada para memória com warn
Vercel AI Gateway LLM + embeddings (@ai-sdk/anthropic|openai|google) lib/ai/ agente não responde
Nuvemshop e-commerce: pedidos, produtos, webhooks LGPD lib/nuvemshop/ opcional (NUVEMSHOP_ENABLED)
Sentry erros + performance, beforeSend higieniza PII sentry.*.config.ts, instrumentation*.ts opcional
Resend e-mail transacional (convite de time) lib/email/ opcional — o convite cai em copy-to-clipboard
MCP CRM exposto como tools para agentes app/api/mcp/, lib/mcp/

Hardening

  • Error boundaries em app/error.tsx, app/app/error.tsx, app/(public)/error.tsx, app/global-error.tsx (Sentry capture + eventId visível).
  • Páginas customizadas 404/403/500/503 com copy PT-BR canônica.
  • Loading skeletons em rotas P0.
  • E2E Playwright + axe-core.
  • Detalhes: docs/stories/epics/EPIC-12-hardening.md.

Onde olhar a fundo