382 lines
13 KiB
PL/PgSQL
382 lines
13 KiB
PL/PgSQL
-- Migration 003: Tenant Context Management
|
|
-- Data: 2026-04-02
|
|
-- Description: Configuração de contexto de tenant e validações automáticas
|
|
|
|
-- ==========================================
|
|
-- FUNÇÃO: Validar se tenant existe e está ativo
|
|
-- ==========================================
|
|
|
|
CREATE OR REPLACE FUNCTION validate_tenant_active(tenant_id INTEGER)
|
|
RETURNS BOOLEAN AS $$
|
|
DECLARE
|
|
tenant_status TEXT;
|
|
BEGIN
|
|
IF tenant_id IS NULL THEN
|
|
RETURN TRUE; -- Algumas tabelas permitem NULL (globais)
|
|
END IF;
|
|
|
|
SELECT status INTO tenant_status
|
|
FROM tenants
|
|
WHERE id = tenant_id;
|
|
|
|
IF tenant_status IS NULL THEN
|
|
RAISE EXCEPTION 'Tenant % não encontrado', tenant_id;
|
|
END IF;
|
|
|
|
IF tenant_status != 'active' THEN
|
|
RAISE EXCEPTION 'Tenant % não está ativo (status: %)', tenant_id, tenant_status;
|
|
END IF;
|
|
|
|
RETURN TRUE;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- ==========================================
|
|
-- FUNÇÃO: Obter hierarquia de tenants (para master/partner ver subs)
|
|
-- ==========================================
|
|
|
|
CREATE OR REPLACE FUNCTION get_tenant_hierarchy(parent_tenant_id INTEGER)
|
|
RETURNS TABLE (tenant_id INTEGER) AS $$
|
|
BEGIN
|
|
RETURN QUERY
|
|
WITH RECURSIVE hierarchy AS (
|
|
-- Base: o próprio tenant
|
|
SELECT id FROM tenants WHERE id = parent_tenant_id
|
|
|
|
UNION ALL
|
|
|
|
-- Recursão: filhos diretos
|
|
SELECT t.id
|
|
FROM tenants t
|
|
JOIN hierarchy h ON t.parent_tenant_id = h.id
|
|
)
|
|
SELECT id FROM hierarchy;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- ==========================================
|
|
-- FUNÇÃO: Verificar se usuário pode acessar tenant específico
|
|
-- ==========================================
|
|
|
|
CREATE OR REPLACE FUNCTION can_access_tenant(user_id VARCHAR, target_tenant_id INTEGER)
|
|
RETURNS BOOLEAN AS $$
|
|
DECLARE
|
|
user_tenant_id INTEGER;
|
|
user_role TEXT;
|
|
target_parent_id INTEGER;
|
|
is_owner BOOLEAN;
|
|
BEGIN
|
|
-- Busca informações do usuário
|
|
SELECT
|
|
tu.tenant_id,
|
|
tu.role,
|
|
tu.is_owner = 'true'
|
|
INTO
|
|
user_tenant_id,
|
|
user_role,
|
|
is_owner
|
|
FROM tenant_users tu
|
|
WHERE tu.user_id = can_access_tenant.user_id
|
|
LIMIT 1;
|
|
|
|
IF user_tenant_id IS NULL THEN
|
|
RETURN FALSE;
|
|
END IF;
|
|
|
|
-- Se é o próprio tenant, permite
|
|
IF user_tenant_id = target_tenant_id THEN
|
|
RETURN TRUE;
|
|
END IF;
|
|
|
|
-- Se é master/partner, permite ver filhos
|
|
SELECT parent_tenant_id INTO target_parent_id
|
|
FROM tenants
|
|
WHERE id = target_tenant_id;
|
|
|
|
IF target_parent_id = user_tenant_id THEN
|
|
RETURN TRUE;
|
|
END IF;
|
|
|
|
-- Se é admin master, pode ver todos
|
|
IF is_owner AND user_role = 'owner' THEN
|
|
-- Verifica se é master
|
|
DECLARE
|
|
user_tenant_type TEXT;
|
|
BEGIN
|
|
SELECT tenant_type INTO user_tenant_type
|
|
FROM tenants
|
|
WHERE id = user_tenant_id;
|
|
|
|
IF user_tenant_type = 'master' THEN
|
|
RETURN TRUE;
|
|
END IF;
|
|
END;
|
|
END IF;
|
|
|
|
RETURN FALSE;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- ==========================================
|
|
-- TRIGGER FUNCTION: Validar tenant_id antes de inserir/atualizar
|
|
-- ==========================================
|
|
|
|
CREATE OR REPLACE FUNCTION validate_tenant_id()
|
|
RETURNS TRIGGER AS $$
|
|
DECLARE
|
|
context_tenant_id INTEGER;
|
|
BEGIN
|
|
-- Se não tem tenant_id, permite (tabelas globais)
|
|
IF NEW.tenant_id IS NULL THEN
|
|
RETURN NEW;
|
|
END IF;
|
|
|
|
-- Valida se tenant está ativo
|
|
PERFORM validate_tenant_active(NEW.tenant_id);
|
|
|
|
-- Opcional: validar contra contexto da aplicação
|
|
BEGIN
|
|
context_tenant_id := current_setting('app.current_tenant_id', true)::INTEGER;
|
|
IF context_tenant_id IS NOT NULL AND NEW.tenant_id != context_tenant_id THEN
|
|
RAISE EXCEPTION 'Tenant ID % não corresponde ao contexto atual %',
|
|
NEW.tenant_id, context_tenant_id;
|
|
END IF;
|
|
EXCEPTION WHEN OTHERS THEN
|
|
-- Se não conseguiu ler o contexto, continua (para migrations, seeds, etc)
|
|
NULL;
|
|
END;
|
|
|
|
RETURN NEW;
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- ==========================================
|
|
-- CRIAR TRIGGERS PARA VALIDAÇÃO AUTOMÁTICA
|
|
-- ==========================================
|
|
|
|
-- workspace_pages
|
|
DROP TRIGGER IF EXISTS trigger_validate_workspace_pages_tenant ON "workspace_pages";
|
|
CREATE TRIGGER trigger_validate_workspace_pages_tenant
|
|
BEFORE INSERT OR UPDATE ON "workspace_pages"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- quick_notes
|
|
DROP TRIGGER IF EXISTS trigger_validate_quick_notes_tenant ON "quick_notes";
|
|
CREATE TRIGGER trigger_validate_quick_notes_tenant
|
|
BEFORE INSERT OR UPDATE ON "quick_notes"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- activity_feed
|
|
DROP TRIGGER IF EXISTS trigger_validate_activity_feed_tenant ON "activity_feed";
|
|
CREATE TRIGGER trigger_validate_activity_feed_tenant
|
|
BEFORE INSERT OR UPDATE ON "activity_feed"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- conversations
|
|
DROP TRIGGER IF EXISTS trigger_validate_conversations_tenant ON "conversations";
|
|
CREATE TRIGGER trigger_validate_conversations_tenant
|
|
BEFORE INSERT OR UPDATE ON "conversations"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- knowledge_base
|
|
DROP TRIGGER IF EXISTS trigger_validate_knowledge_base_tenant ON "knowledge_base";
|
|
CREATE TRIGGER trigger_validate_knowledge_base_tenant
|
|
BEFORE INSERT OR UPDATE ON "knowledge_base"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- chat_threads
|
|
DROP TRIGGER IF EXISTS trigger_validate_chat_threads_tenant ON "chat_threads";
|
|
CREATE TRIGGER trigger_validate_chat_threads_tenant
|
|
BEFORE INSERT OR UPDATE ON "chat_threads"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- manus_runs
|
|
DROP TRIGGER IF EXISTS trigger_validate_manus_runs_tenant ON "manus_runs";
|
|
CREATE TRIGGER trigger_validate_manus_runs_tenant
|
|
BEFORE INSERT OR UPDATE ON "manus_runs"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- tenant_empresas
|
|
DROP TRIGGER IF EXISTS trigger_validate_tenant_empresas_tenant ON "tenant_empresas";
|
|
CREATE TRIGGER trigger_validate_tenant_empresas_tenant
|
|
BEFORE INSERT OR UPDATE ON "tenant_empresas"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- pc_clients
|
|
DROP TRIGGER IF EXISTS trigger_validate_pc_clients_tenant ON "pc_clients";
|
|
CREATE TRIGGER trigger_validate_pc_clients_tenant
|
|
BEFORE INSERT OR UPDATE ON "pc_clients"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- pc_projects
|
|
DROP TRIGGER IF EXISTS trigger_validate_pc_projects_tenant ON "pc_projects";
|
|
CREATE TRIGGER trigger_validate_pc_projects_tenant
|
|
BEFORE INSERT OR UPDATE ON "pc_projects"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- pc_crm_leads
|
|
DROP TRIGGER IF EXISTS trigger_validate_pc_crm_leads_tenant ON "pc_crm_leads";
|
|
CREATE TRIGGER trigger_validate_pc_crm_leads_tenant
|
|
BEFORE INSERT OR UPDATE ON "pc_crm_leads"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- pc_crm_opportunities
|
|
DROP TRIGGER IF EXISTS trigger_validate_pc_crm_opportunities_tenant ON "pc_crm_opportunities";
|
|
CREATE TRIGGER trigger_validate_pc_crm_opportunities_tenant
|
|
BEFORE INSERT OR UPDATE ON "pc_crm_opportunities"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- pc_pdca_cycles
|
|
DROP TRIGGER IF EXISTS trigger_validate_pc_pdca_cycles_tenant ON "pc_pdca_cycles";
|
|
CREATE TRIGGER trigger_validate_pc_pdca_cycles_tenant
|
|
BEFORE INSERT OR UPDATE ON "pc_pdca_cycles"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_partners
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_partners_tenant ON "crm_partners";
|
|
CREATE TRIGGER trigger_validate_crm_partners_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_partners"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_contracts
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_contracts_tenant ON "crm_contracts";
|
|
CREATE TRIGGER trigger_validate_crm_contracts_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_contracts"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_channels
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_channels_tenant ON "crm_channels";
|
|
CREATE TRIGGER trigger_validate_crm_channels_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_channels"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_threads
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_threads_tenant ON "crm_threads";
|
|
CREATE TRIGGER trigger_validate_crm_threads_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_threads"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_campaigns
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_campaigns_tenant ON "crm_campaigns";
|
|
CREATE TRIGGER trigger_validate_crm_campaigns_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_campaigns"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_events
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_events_tenant ON "crm_events";
|
|
CREATE TRIGGER trigger_validate_crm_events_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_events"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_products
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_products_tenant ON "crm_products";
|
|
CREATE TRIGGER trigger_validate_crm_products_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_products"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_clients
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_clients_tenant ON "crm_clients";
|
|
CREATE TRIGGER trigger_validate_crm_clients_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_clients"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_leads
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_leads_tenant ON "crm_leads";
|
|
CREATE TRIGGER trigger_validate_crm_leads_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_leads"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_opportunities
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_opportunities_tenant ON "crm_opportunities";
|
|
CREATE TRIGGER trigger_validate_crm_opportunities_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_opportunities"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- crm_proposals
|
|
DROP TRIGGER IF EXISTS trigger_validate_crm_proposals_tenant ON "crm_proposals";
|
|
CREATE TRIGGER trigger_validate_crm_proposals_tenant
|
|
BEFORE INSERT OR UPDATE ON "crm_proposals"
|
|
FOR EACH ROW
|
|
EXECUTE FUNCTION validate_tenant_id();
|
|
|
|
-- ==========================================
|
|
-- VIEW: Resumo de segurança por tenant
|
|
-- ==========================================
|
|
|
|
CREATE OR REPLACE VIEW tenant_security_summary AS
|
|
SELECT
|
|
t.id AS tenant_id,
|
|
t.name AS tenant_name,
|
|
t.slug,
|
|
t.tenant_type,
|
|
t.status,
|
|
(SELECT COUNT(*) FROM tenant_users tu WHERE tu.tenant_id = t.id) AS user_count,
|
|
(SELECT COUNT(*) FROM workspace_pages wp WHERE wp.tenant_id = t.id) AS workspace_pages_count,
|
|
(SELECT COUNT(*) FROM pc_clients pc WHERE pc.tenant_id = t.id) AS clients_count,
|
|
(SELECT COUNT(*) FROM pc_projects pp WHERE pp.tenant_id = t.id) AS projects_count,
|
|
(SELECT COUNT(*) FROM crm_leads cl WHERE cl.tenant_id = t.id) AS leads_count,
|
|
(SELECT COUNT(*) FROM crm_opportunities co WHERE co.tenant_id = t.id) AS opportunities_count
|
|
FROM tenants t
|
|
ORDER BY t.id;
|
|
|
|
-- ==========================================
|
|
-- FUNÇÃO: Setar contexto de tenant (para uso na aplicação)
|
|
-- ==========================================
|
|
|
|
CREATE OR REPLACE FUNCTION set_tenant_context(p_tenant_id INTEGER, p_user_id VARCHAR, p_role TEXT DEFAULT 'member')
|
|
RETURNS VOID AS $$
|
|
BEGIN
|
|
PERFORM set_config('app.current_tenant_id', p_tenant_id::TEXT, FALSE);
|
|
PERFORM set_config('app.current_user_id', p_user_id, FALSE);
|
|
PERFORM set_config('app.current_tenant_role', p_role, FALSE);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- ==========================================
|
|
-- FUNÇÃO: Limpar contexto de tenant
|
|
-- ==========================================
|
|
|
|
CREATE OR REPLACE FUNCTION clear_tenant_context()
|
|
RETURNS VOID AS $$
|
|
BEGIN
|
|
PERFORM set_config('app.current_tenant_id', '', FALSE);
|
|
PERFORM set_config('app.current_user_id', '', FALSE);
|
|
PERFORM set_config('app.current_tenant_role', '', FALSE);
|
|
END;
|
|
$$ LANGUAGE plpgsql SECURITY DEFINER;
|
|
|
|
-- ==========================================
|
|
-- COMENTÁRIOS DE DOCUMENTAÇÃO
|
|
-- ==========================================
|
|
|
|
COMMENT ON FUNCTION get_current_tenant_id() IS 'Retorna o ID do tenant atual do contexto da aplicação';
|
|
COMMENT ON FUNCTION is_tenant_admin() IS 'Verifica se o usuário atual é admin/owner do tenant';
|
|
COMMENT ON FUNCTION validate_tenant_active(INTEGER) IS 'Valida se um tenant existe e está ativo';
|
|
COMMENT ON FUNCTION can_access_tenant(VARCHAR, INTEGER) IS 'Verifica se um usuário pode acessar um tenant específico';
|
|
COMMENT ON FUNCTION set_tenant_context(INTEGER, VARCHAR, TEXT) IS 'Define o contexto de tenant para a sessão atual';
|
|
COMMENT ON FUNCTION clear_tenant_context() IS 'Limpa o contexto de tenant da sessão atual';
|
|
COMMENT ON VIEW tenant_security_summary IS 'Resumo de segurança e dados por tenant';
|