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

28 KiB

🏗️ 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

-- Í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

-- 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