First commit - backup RDOC
This commit is contained in:
531
documentation/ARQUITETURA_MULTI_TENANT.md
Normal file
531
documentation/ARQUITETURA_MULTI_TENANT.md
Normal file
@@ -0,0 +1,531 @@
|
||||
# 🏗️ 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
|
||||
Reference in New Issue
Block a user