05c6746e33
- docs/F0_categorizacao.md: varredura completa (137 tabelas -> 84 tenant + 53 global, 66 funcoes, FKs, policies, edge functions) + decisoes Q1-Q4 - F1 (migrations 01-05): tenants.slug, helpers de schema, _tenant_template (84 tabelas sem tenant_id, singletons, views __SCHEMA__/__TENANT_ID__), clone_tenant_template/drop_tenant_schema, channel_routing, tenant_schemas - F2 (migration 06): provision_account_tenant/create_clinic_tenant/ ensure_personal_tenant_for_user clonam schema na mesma transacao - db.cjs: psqlFile agora usa ON_ERROR_STOP=1 (falha de migration nao passa mais como sucesso silencioso) - blueprint original em novo-rumo.txt; wiki Obsidian atualizada Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
74 lines
2.4 KiB
PL/PgSQL
74 lines
2.4 KiB
PL/PgSQL
-- =============================================================================
|
|
-- F1.2 — Schema-per-tenant: helpers de resolução de schema
|
|
-- Adaptação ao modelo multi-membership deste projeto (docs/F0_categorizacao.md D2):
|
|
-- profiles.tenant_id é NULL; membership vive em tenant_members (multi-tenant).
|
|
-- Logo NÃO existe current_tenant_schema() — RPCs recebem p_tenant_id explícito
|
|
-- e validam via tenant_schema_checked(p_tenant_id).
|
|
-- =============================================================================
|
|
|
|
BEGIN;
|
|
|
|
-- slug -> nome de schema (validado). Retorna NULL se slug inválido.
|
|
CREATE OR REPLACE FUNCTION public.tenant_schema_name(p_slug text)
|
|
RETURNS text
|
|
LANGUAGE sql
|
|
IMMUTABLE
|
|
AS $$
|
|
SELECT CASE
|
|
WHEN p_slug ~ '^[a-z][a-z0-9_]{1,47}$' THEN 'tenant_' || p_slug
|
|
ELSE NULL
|
|
END;
|
|
$$;
|
|
|
|
-- tenant_id -> nome de schema
|
|
CREATE OR REPLACE FUNCTION public.tenant_schema_for(p_tenant_id uuid)
|
|
RETURNS text
|
|
LANGUAGE sql
|
|
STABLE
|
|
SECURITY DEFINER
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $$
|
|
SELECT public.tenant_schema_name(t.slug) FROM public.tenants t WHERE t.id = p_tenant_id;
|
|
$$;
|
|
|
|
-- nome de schema -> tenant_id (CRÍTICO pra triggers: a coluna tenant_id não
|
|
-- existe mais nas tabelas tenant; o schema é a identidade)
|
|
CREATE OR REPLACE FUNCTION public.tenant_id_for_schema(p_schema text)
|
|
RETURNS uuid
|
|
LANGUAGE sql
|
|
STABLE
|
|
SECURITY DEFINER
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $$
|
|
SELECT t.id FROM public.tenants t WHERE public.tenant_schema_name(t.slug) = p_schema;
|
|
$$;
|
|
|
|
-- Resolve schema de um tenant COM validação de acesso do usuário logado.
|
|
-- Substitui o current_tenant_schema() do blueprint (que assumia 1 tenant/usuário).
|
|
CREATE OR REPLACE FUNCTION public.tenant_schema_checked(p_tenant_id uuid)
|
|
RETURNS text
|
|
LANGUAGE plpgsql
|
|
STABLE
|
|
SECURITY DEFINER
|
|
SET search_path TO 'public', 'pg_temp'
|
|
AS $$
|
|
DECLARE
|
|
v_schema text;
|
|
BEGIN
|
|
IF p_tenant_id IS NULL THEN
|
|
RAISE EXCEPTION 'tenant_schema_checked: p_tenant_id obrigatório';
|
|
END IF;
|
|
IF NOT public.is_tenant_member(p_tenant_id) AND NOT public.is_saas_admin() THEN
|
|
RAISE EXCEPTION 'acesso negado ao tenant %', p_tenant_id
|
|
USING ERRCODE = '42501';
|
|
END IF;
|
|
v_schema := public.tenant_schema_for(p_tenant_id);
|
|
IF v_schema IS NULL THEN
|
|
RAISE EXCEPTION 'tenant % não encontrado ou slug inválido', p_tenant_id;
|
|
END IF;
|
|
RETURN v_schema;
|
|
END;
|
|
$$;
|
|
|
|
COMMIT;
|