/ Fundamentos
Arquitetura
Camadas, fluxo de uma requisição e integrações.
4 min de leitura Editar no GitHub
O RoboStage é um monólito Next.js (App Router) implantado na Vercel. A mesma aplicação serve as páginas públicas, o Dashboard logado e as rotas de API. Os serviços externos são poucos e bem delimitados: Clerk (identidade), PostgreSQL no Neon (dados), Cloudflare R2 (arquivos grandes) e Supabase (conteúdo do NECTA).
Visão de camadas
Navegador
│
┌─────────▼──────────┐
│ proxy.ts │ Clerk: sessão, rotas
└─────────┬──────────┘ públicas, onboarding
│
┌─────────────────────▼──────────────────────┐
│ app/ │
│ Server Components → leem dados │
│ Server Actions → gravam │
│ Route Handlers → /api │
└─────────────────────┬──────────────────────┘
│
┌─────────────────────▼──────────────────────┐
│ utils/ regras, escopo e permissões │
└─────────────────────┬──────────────────────┘
│
┌─────────────────────▼──────────────────────┐
│ db/ Drizzle ORM → PostgreSQL (Neon) │
└────────────────────────────────────────────┘| Camada | Onde fica | Responsabilidade |
|---|---|---|
| Middleware | proxy.ts |
Exige sessão nas rotas protegidas e envia para o onboarding quando preciso. |
| Interface | app/**/page.tsx, components/ |
Server Components renderizam no servidor; componentes "use client" cuidam da interação. |
| Mutação | app/**/actions.ts |
Server Actions: validam a entrada, conferem escopo e permissão e gravam no banco. |
| API | app/api/**/route.ts |
Dados das temporadas, competições do usuário, LabTest, documentos legais e webhooks. |
| Domínio | utils/ |
Regras puras e reutilizáveis: pontuação, escopo, permissões, estatísticas. |
| Persistência | db/ |
Schema Drizzle e cliente do banco. |
Fluxo de uma ação
Exemplo: criar um marco (milestone) no espaço de uma equipe.
- O componente cliente chama uma Server Action. As de marcos ficam em
utils/stagebook/actions/milestones.ts. - A action resolve o escopo com
resolveStagebookScope()(utils/stagebook/scope.ts). O escopo diz se o usuário está no espaço pessoal ou de uma equipe, e com qual cargo. - A permissão é exigida com
requireCapability(scope, "milestone.manage")(utils/stagebook/permissions.ts), que consulta a matriz deteam-permissions.ts. No espaço pessoal o dono pode tudo. - A gravação usa
scopeOwnership(scope)para preencher exatamente um entreuser_ideteam_id. UmCHECKno banco garante isso. - A action chama
revalidatePath(...), e os Server Components relêem os dados atualizados.
Importante
As Server Actions são a barreira de segurança real. A interface consulta a mesma matriz de permissões apenas para esconder ou desabilitar controles.
Escopos: pessoal e equipe
Quase tudo no Dashboard (páginas, pastas, Kanban, calendário, LabTest, ThinkLab, etiquetas e marcos) pertence a um escopo:
- Pessoal:
user_idpreenchido eteam_idnulo. - Equipe:
team_idpreenchido euser_idnulo.
A equipe ativa fica no cookie stagebook_team_id. Se o cookie apontar para uma equipe da qual a pessoa não faz mais parte, o escopo volta silenciosamente para o pessoal, sem nunca conceder acesso indevido. Os detalhes estão em Autenticação e permissões.
Integrações
| Serviço | Uso | Onde |
|---|---|---|
| Clerk | Login, sessões, organizações (equipes) e convites. Um webhook espelha usuários e membros no banco. | proxy.ts, db/clerk-sync.ts, app/api/webhooks/clerk |
| Neon (PostgreSQL) | Todos os dados da plataforma, via Drizzle sobre HTTP. | db/client.ts |
| Cloudflare R2 | Fotos do robô e projetos .io do Studio 2.0 no QuickBrick. |
utils/quickbrick/r2.ts |
| Supabase | Publicações do NECTA, lidas pela API REST com a chave pública. | utils/institutional/news.ts |
| Vercel | Hospedagem, Analytics e Speed Insights. | app/layout.tsx |
Uploads diretos para o R2
Arquivos grandes não passam pelas funções serverless, que têm limite de corpo. O fluxo é este:
- O servidor gera uma URL pré-assinada para o objeto.
- O navegador envia o arquivo direto para o R2.
- O servidor confere o objeto com
HEADe grava apenas a chave no banco.
Cache das notícias (NECTA)
As publicações são buscadas com a tag de cache news e revalidadas a cada 5 minutos. Quando algo é publicado, o editor do NECTA chama POST /api/revalidate/news com o segredo NEWS_REVALIDATE_SECRET, e o site atualiza na hora.
"Tempo real"
O RoboStage não usa WebSockets. O componente LiveRefresh (components/stagebook/live-refresh.tsx) chama router.refresh() a cada 30 segundos, e só enquanto a aba está visível. Ele nunca empilha atualizações e faz uma atualização imediata quando a pessoa volta para a aba. O intervalo é conservador de propósito: um refresh re-renderiza a rota inteira, e intervalos curtos geram erros 429 no Clerk e no Neon.
Dados das temporadas
Missões, documentos oficiais e catálogos que não variam por usuário ficam em JSON dentro de public/data/. Eles são servidos pelas rotas /api/data/*, o que permite atualizar uma temporada sem migração de banco.
Testes
A suíte em tests/ roda as Server Actions contra um Postgres em memória (PGlite) com o schema real e com o Clerk simulado. Assim é possível testar regras de escopo, permissão e integridade sem nenhum serviço externo.
Leia também
- Arquitetura das rotas: grupos de rotas, segmentos dinâmicos e acesso.
- Tipagem: tipos do banco,
types/e validação de entrada. - Competições: o que cada competição oferece.
Algo errado ou faltando nesta página? Sugira uma edição.