Files
RDO/documentation/ARQUITETURA_MULTI_TENANT.md
2026-02-20 07:25:32 -03:00

532 lines
28 KiB
Markdown

# 🏗️ ARQUITETURA MULTI-TENANT - SISTEMA RDO
## 📐 VISÃO GERAL DA ARQUITETURA
```
┌─────────────────────────────────────────────────────────────────┐
│ FRONTEND (React) │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Org A │ │ Org B │ │ Org C │ │
│ │ /acme-const │ │ /silva-eng │ │ /metal-xyz │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └──────────────────┴──────────────────┘ │
│ │ │
│ ┌────────▼────────┐ │
│ │ OrganizationCtx │ │
│ │ + AuthContext │ │
│ └────────┬────────┘ │
└────────────────────────────┼─────────────────────────────────────┘
┌────────▼────────┐
│ Supabase API │
│ (RLS Enabled) │
└────────┬────────┘
┌────────────────────────────▼─────────────────────────────────────┐
│ BANCO DE DADOS (PostgreSQL) │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ORGANIZAÇÕES │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ Org A │ │ Org B │ │ Org C │ │ │
│ │ │ (ID: 1) │ │ (ID: 2) │ │ (ID: 3) │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ └───────┼─────────────┼─────────────┼───────────────────┘ │
│ │ │ │ │
│ ┌───────▼─────────────▼─────────────▼───────────────────┐ │
│ │ DADOS ISOLADOS POR ORG_ID │ │
│ │ │ │
│ │ Usuários │ Obras │ RDOs │ Tarefas │ Anexos │ │
│ │ org_id=1 │ org_id=1│org_id=1│ org_id=1 │ org_id=1 │ │
│ │ org_id=2 │ org_id=2│org_id=2│ org_id=2 │ org_id=2 │ │
│ │ org_id=3 │ org_id=3│org_id=3│ org_id=3 │ org_id=3 │ │
│ └─────────────────────────────────────────────────────────┘ │
│ │
│ ┌─────────────────────────────────────────────────────────┐ │
│ │ ROW LEVEL SECURITY (RLS) │ │
│ │ • Usuário só vê dados da própria organização │ │
│ │ • Validação automática em TODAS as queries │ │
│ │ • Impossível acessar dados de outra org │ │
│ └─────────────────────────────────────────────────────────┘ │
└───────────────────────────────────────────────────────────────────┘
```
---
## 🔐 FLUXO DE AUTENTICAÇÃO E AUTORIZAÇÃO
```
┌─────────────────────────────────────────────────────────────────┐
│ 1. SIGNUP DE ORGANIZAÇÃO │
└─────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────┐
│ Usuário preenche formulário: │
│ • Nome da organização │
│ • Slug (URL amigável) │
│ • Email │
│ • Senha │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 1. Criar conta no Supabase Auth │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 2. Chamar função SQL: │
│ criar_organizacao_com_owner() │
│ • Cria organização │
│ • Cria perfil de usuário │
│ • Define como 'owner' │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 3. Redirecionar para: │
│ /:slug/dashboard │
└────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 2. CONVITE DE USUÁRIO │
└─────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────┐
│ Admin/Owner convida: │
│ • Email do convidado │
│ • Role (engenheiro, mestre, etc) │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ Sistema gera: │
│ • Token único │
│ • Link: /convite/:token │
│ • Email de convite │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ Convidado clica no link │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 1. Validar token │
│ 2. Criar conta no Auth │
│ 3. Chamar aceitar_convite() │
│ 4. Vincular à organização │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ Redirecionar para: │
│ /:slug/dashboard │
└────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ 3. LOGIN E ACESSO │
└─────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────┐
│ Usuário faz login │
│ • Email + Senha │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ Supabase Auth valida │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ Carregar organização(ões): │
│ SELECT * FROM organizacao_usuarios │
│ WHERE usuario_id = auth.uid() │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ Se tem 1 org: │
│ → /:slug/dashboard │
│ │
│ Se tem múltiplas orgs: │
│ → /selecionar-organizacao │
└────────────────────────────────────┘
```
---
## 🗄️ ESTRUTURA DO BANCO DE DADOS
### **Tabelas Principais**
```
organizacoes (TENANTS)
├── id (UUID, PK)
├── slug (VARCHAR, UNIQUE) ← URL amigável
├── nome (VARCHAR)
├── plano (VARCHAR) ← trial, basic, professional, enterprise
├── max_usuarios (INTEGER)
├── max_obras (INTEGER)
├── max_rdos_mes (INTEGER)
├── max_storage_mb (INTEGER)
├── cor_primaria (VARCHAR)
├── cor_secundaria (VARCHAR)
├── configuracoes (JSONB)
└── status (VARCHAR)
usuarios
├── id (UUID, PK, FK → auth.users)
├── organizacao_id (UUID, FK → organizacoes) ← ISOLAMENTO
├── nome (VARCHAR)
├── email (VARCHAR)
└── ativo (BOOLEAN)
organizacao_usuarios (ROLES)
├── id (UUID, PK)
├── organizacao_id (UUID, FK → organizacoes)
├── usuario_id (UUID, FK → usuarios)
├── role (VARCHAR) ← owner, admin, engenheiro, mestre_obra, usuario
└── ativo (BOOLEAN)
convites
├── id (UUID, PK)
├── organizacao_id (UUID, FK → organizacoes)
├── email (VARCHAR)
├── role (VARCHAR)
├── token (VARCHAR, UNIQUE)
├── status (VARCHAR) ← pendente, aceito, expirado
└── expira_em (TIMESTAMP)
obras
├── id (UUID, PK)
├── organizacao_id (UUID, FK → organizacoes) ← ISOLAMENTO
├── nome (VARCHAR)
├── responsavel_id (UUID, FK → usuarios)
└── ... (outros campos)
rdos
├── id (UUID, PK)
├── organizacao_id (UUID, FK → organizacoes) ← ISOLAMENTO
├── obra_id (UUID, FK → obras)
├── criado_por (UUID, FK → usuarios)
└── ... (outros campos)
rdo_atividades
├── id (UUID, PK)
├── organizacao_id (UUID, FK → organizacoes) ← ISOLAMENTO
├── rdo_id (UUID, FK → rdos)
└── ... (outros campos)
... (todas as outras tabelas seguem o mesmo padrão)
```
### **Índices Importantes**
```sql
-- Índices compostos para performance multi-tenant
CREATE INDEX idx_usuarios_org ON usuarios(organizacao_id, id);
CREATE INDEX idx_obras_org ON obras(organizacao_id, id);
CREATE INDEX idx_rdos_org ON rdos(organizacao_id, id);
-- Índices para busca por slug
CREATE INDEX idx_organizacoes_slug ON organizacoes(slug);
-- Índices para roles
CREATE INDEX idx_org_usuarios_org ON organizacao_usuarios(organizacao_id);
CREATE INDEX idx_org_usuarios_user ON organizacao_usuarios(usuario_id);
```
---
## 🔒 ROW LEVEL SECURITY (RLS)
### **Como Funciona**
```sql
-- Exemplo: Política para tabela 'obras'
CREATE POLICY "Ver obras da organização" ON obras
FOR SELECT USING (
organizacao_id IN (
SELECT organizacao_id
FROM organizacao_usuarios
WHERE usuario_id = auth.uid()
AND ativo = true
)
);
```
### **Fluxo de Query com RLS**
```
┌─────────────────────────────────────────────────────────────────┐
│ Frontend executa: │
│ const { data } = await supabase.from('obras').select('*') │
└────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Supabase adiciona automaticamente: │
│ WHERE organizacao_id IN ( │
│ SELECT organizacao_id FROM organizacao_usuarios │
│ WHERE usuario_id = auth.uid() AND ativo = true │
│ ) │
└────────────────────────┬────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ Resultado: Apenas obras da organização do usuário │
│ • Impossível ver dados de outras organizações │
│ • Validação no nível do banco de dados │
│ • Não depende do frontend │
└─────────────────────────────────────────────────────────────────┘
```
---
## 🎨 FLUXO DE PERSONALIZAÇÃO
```
┌─────────────────────────────────────────────────────────────────┐
│ CARREGAMENTO DA APLICAÇÃO │
└─────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────┐
│ 1. Extrair slug da URL │
│ Exemplo: /acme-const/dashboard │
│ slug = 'acme-const' │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 2. Carregar organização: │
│ SELECT * FROM organizacoes │
│ WHERE slug = 'acme-const' │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 3. Aplicar personalização: │
│ • Logo │
│ • Cores (primária, secundária) │
│ • Configurações │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 4. Carregar configurações: │
│ • Tipos de atividade │
│ • Funções de mão de obra │
│ • Tipos de equipamento │
│ • Condições climáticas │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 5. Aplicar tema CSS: │
│ --color-primary: #3B82F6 │
│ --color-secondary: #1E40AF │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 6. Renderizar aplicação │
│ personalizada │
└────────────────────────────────────┘
```
---
## 📊 SISTEMA DE QUOTAS E LIMITES
```
┌─────────────────────────────────────────────────────────────────┐
│ VALIDAÇÃO DE QUOTAS │
└─────────────────────────────────────────────────────────────────┘
┌────────────────────────────────────┐
│ Usuário tenta criar recurso │
│ (obra, RDO, usuário, etc) │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ 1. Trigger no banco verifica: │
│ • Contar recursos existentes │
│ • Buscar limite do plano │
│ • Comparar │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ Se dentro do limite: │
│ ✅ Permitir criação │
│ │
│ Se excedeu limite: │
│ ❌ RAISE EXCEPTION │
│ "Limite atingido. Faça upgrade" │
└────────────────┬───────────────────┘
┌────────────────────────────────────┐
│ Frontend captura erro: │
│ • Mostrar modal │
│ • Sugerir upgrade de plano │
│ • Link para billing │
└────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────┐
│ MÉTRICAS DE USO │
└─────────────────────────────────────────────────────────────────┘
organizacao_metricas
├── organizacao_id
├── mes_referencia
├── total_usuarios (atual)
├── total_obras (atual)
├── total_rdos (no mês)
├── storage_usado_mb (atual)
├── limite_usuarios (do plano)
├── limite_obras (do plano)
├── limite_rdos_mes (do plano)
└── limite_storage_mb (do plano)
Atualização automática via triggers:
• Ao criar/deletar usuário
• Ao criar/deletar obra
• Ao criar/deletar RDO
• Ao fazer upload de arquivo
```
---
## 🔄 FLUXO COMPLETO DE UMA OPERAÇÃO
### **Exemplo: Criar um RDO**
```
┌─────────────────────────────────────────────────────────────────┐
│ 1. FRONTEND │
└─────────────────────────────────────────────────────────────────┘
const { organization } = useOrganization();
const { data, error } = await supabase
.from('rdos')
.insert({
obra_id: obraId,
criado_por: user.id,
data_relatorio: new Date(),
condicoes_climaticas: 'Ensolarado',
// organizacao_id será preenchido automaticamente
});
┌─────────────────────────────────────────────────────────────────┐
│ 2. SUPABASE API │
└─────────────────────────────────────────────────────────────────┘
• Validar autenticação (JWT token)
• Aplicar RLS policies
• Executar INSERT
┌─────────────────────────────────────────────────────────────────┐
│ 3. BANCO DE DADOS - TRIGGERS │
└─────────────────────────────────────────────────────────────────┘
BEFORE INSERT:
├── set_rdo_organizacao_id()
│ └── Copia organizacao_id da obra
├── set_rdo_numero()
│ └── Define próximo número sequencial
└── verificar_limite_rdos()
└── Valida se não excedeu quota
INSERT executado ✅
AFTER INSERT:
└── atualizar_metricas_organizacao()
└── Incrementa contador de RDOs
┌─────────────────────────────────────────────────────────────────┐
│ 4. RESPOSTA │
└─────────────────────────────────────────────────────────────────┘
• RDO criado com sucesso
• organizacao_id preenchido automaticamente
• Número sequencial atribuído
• Métricas atualizadas
• Retorna dados para o frontend
```
---
## 🎯 PONTOS-CHAVE DA ARQUITETURA
### ✅ **Isolamento Total**
- Cada organização é completamente isolada
- RLS garante segurança no nível do banco
- Impossível acessar dados de outra organização
### ✅ **Escalabilidade**
- Índices otimizados para queries multi-tenant
- Cache de configurações
- Lazy loading de recursos
### ✅ **Flexibilidade**
- Cada organização pode ter configurações únicas
- Personalização de marca (logo, cores)
- Tipos de atividade customizáveis
### ✅ **Segurança**
- RLS em todas as tabelas
- Validação de permissões por role
- Tokens seguros para convites
- Service role key nunca exposta
### ✅ **Automação**
- Triggers para propagação de dados
- Validação automática de quotas
- Atualização automática de métricas
- Numeração sequencial automática
---
## 📈 CRESCIMENTO E EVOLUÇÃO
### **Fase Atual: MVP**
- Multi-tenancy básico
- Isolamento de dados
- Sistema de convites
- Quotas por plano
### **Próximas Fases:**
- Billing e pagamentos
- Analytics avançado
- API pública
- Webhooks
- Integrações (ERPs, etc)
- Mobile app nativo
- IA para análise de produtividade
---
Esta arquitetura foi projetada para ser:
- 🔒 **Segura** - RLS em todas as camadas
- 📈 **Escalável** - Suporta milhares de organizações
- 🎨 **Flexível** - Personalizável por organização
- 🚀 **Performática** - Índices otimizados
- 🔧 **Manutenível** - Código limpo e documentado