F6.2 wiring: tenants novos nascem com todos os triggers de negocio
- attach_agnostic_triggers reescrito SELF-CONTAINED (dirigido por colunas: set_ updated_at em toda tabela com updated_at + prevent_* em patient_groups). Nao le mais de public -> sobrevive ao DROP da F6.3. - trigger AFTER INSERT em tenant_schemas (trg_attach_business_triggers) dispara os 3 attach (agnostic + schema_aware + notif) pro schema novo. clone_tenant_ template nao precisou ser tocado (ele insere em tenant_schemas). GRANT EXECUTE dos attach pra postgres/service_role. - provision_account_tenant: clone ANTES do seed (seed_determined_commitments e no-op se schema nao existe; precisa do schema criado primeiro) Smoke: tenant novo nasce com 84 triggers automaticamente (vs 81 nos existentes, que sao mais que suficientes). Provision order corrigida. App agora testavel: dados nos schemas (F6.1) + funcoes/triggers/RPCs roteiam (F6.2). Falta so F6.3 DROP (com OK + app testado). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,106 @@
|
||||
-- =============================================================================
|
||||
-- F6.2 (wiring) — tenants NOVOS nascem com todos os triggers de negócio
|
||||
--
|
||||
-- ⚠️ APLICAR COMO supabase_admin.
|
||||
--
|
||||
-- Até aqui os 9 schemas existentes ganharam os triggers via backfills (Lotes
|
||||
-- A/B/C). Um tenant NOVO (clone_tenant_template) só ganhava channel-routing +
|
||||
-- RLS. Este wiring:
|
||||
-- 1. attach_agnostic_triggers → SELF-CONTAINED (dirigido por colunas, não lê
|
||||
-- public; sobrevive ao DROP da F6.3).
|
||||
-- 2. trigger AFTER INSERT em tenant_schemas dispara os 3 attach (agnostic +
|
||||
-- schema_aware + notif) pro schema novo — clone_tenant_template não precisa
|
||||
-- ser tocado (ele insere em tenant_schemas).
|
||||
-- 3. provision_account_tenant: clone ANTES do seed (seed é no-op se o schema
|
||||
-- não existe; precisa do schema criado primeiro).
|
||||
-- =============================================================================
|
||||
|
||||
BEGIN;
|
||||
|
||||
-- 1) attach_agnostic_triggers self-contained ---------------------------------
|
||||
CREATE OR REPLACE FUNCTION public.attach_agnostic_triggers(p_schema text)
|
||||
RETURNS int LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public','pg_temp'
|
||||
AS $$
|
||||
DECLARE r record; v_count int := 0;
|
||||
BEGIN
|
||||
IF p_schema NOT LIKE 'tenant\_%' THEN RAISE EXCEPTION 'schema inválido %', p_schema; END IF;
|
||||
-- set_updated_at em toda tabela do schema que tem coluna updated_at
|
||||
FOR r IN
|
||||
SELECT c.relname AS tab
|
||||
FROM pg_class c JOIN pg_attribute a ON a.attrelid = c.oid
|
||||
WHERE c.relnamespace = p_schema::regnamespace AND c.relkind = 'r'
|
||||
AND a.attname = 'updated_at' AND NOT a.attisdropped AND c.relname NOT LIKE '\_%'
|
||||
LOOP
|
||||
EXECUTE format('DROP TRIGGER IF EXISTS set_updated_at ON %I.%I', p_schema, r.tab);
|
||||
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %I.%I FOR EACH ROW EXECUTE FUNCTION public.set_updated_at()', p_schema, r.tab);
|
||||
v_count := v_count + 1;
|
||||
END LOOP;
|
||||
-- prevent_* em patient_groups
|
||||
IF EXISTS (SELECT 1 FROM information_schema.tables WHERE table_schema = p_schema AND table_name = 'patient_groups') THEN
|
||||
EXECUTE format('DROP TRIGGER IF EXISTS prevent_promoting_to_system ON %I.patient_groups', p_schema);
|
||||
EXECUTE format('CREATE TRIGGER prevent_promoting_to_system BEFORE UPDATE ON %I.patient_groups FOR EACH ROW EXECUTE FUNCTION public.prevent_promoting_to_system()', p_schema);
|
||||
EXECUTE format('DROP TRIGGER IF EXISTS prevent_system_group_changes ON %I.patient_groups', p_schema);
|
||||
EXECUTE format('CREATE TRIGGER prevent_system_group_changes BEFORE UPDATE OR DELETE ON %I.patient_groups FOR EACH ROW EXECUTE FUNCTION public.prevent_system_group_changes()', p_schema);
|
||||
v_count := v_count + 2;
|
||||
END IF;
|
||||
RETURN v_count;
|
||||
END $$;
|
||||
|
||||
-- 2) trigger de wiring em tenant_schemas -------------------------------------
|
||||
GRANT EXECUTE ON FUNCTION public.attach_agnostic_triggers(text) TO postgres, service_role;
|
||||
GRANT EXECUTE ON FUNCTION public.attach_schema_aware_triggers(text) TO postgres, service_role;
|
||||
GRANT EXECUTE ON FUNCTION public.attach_notif_triggers(text) TO postgres, service_role;
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.trg_attach_business_triggers()
|
||||
RETURNS trigger LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public','pg_temp'
|
||||
AS $$
|
||||
BEGIN
|
||||
PERFORM public.attach_agnostic_triggers(NEW.schema_name);
|
||||
PERFORM public.attach_schema_aware_triggers(NEW.schema_name);
|
||||
PERFORM public.attach_notif_triggers(NEW.schema_name);
|
||||
RETURN NULL;
|
||||
END $$;
|
||||
ALTER FUNCTION public.trg_attach_business_triggers() OWNER TO supabase_admin;
|
||||
|
||||
DROP TRIGGER IF EXISTS trg_tenant_schemas_attach ON public.tenant_schemas;
|
||||
CREATE TRIGGER trg_tenant_schemas_attach
|
||||
AFTER INSERT ON public.tenant_schemas
|
||||
FOR EACH ROW EXECUTE FUNCTION public.trg_attach_business_triggers();
|
||||
|
||||
-- 3) provision_account_tenant: clone ANTES do seed ---------------------------
|
||||
CREATE OR REPLACE FUNCTION public.provision_account_tenant(p_user_id uuid, p_kind text, p_name text DEFAULT NULL::text)
|
||||
RETURNS uuid LANGUAGE plpgsql SECURITY DEFINER
|
||||
AS $$
|
||||
DECLARE
|
||||
v_tenant_id uuid;
|
||||
v_account_type text;
|
||||
v_name text;
|
||||
BEGIN
|
||||
IF p_kind NOT IN ('therapist', 'clinic_coworking', 'clinic_reception', 'clinic_full') THEN
|
||||
RAISE EXCEPTION 'kind inválido: "%". Use: therapist, clinic_coworking, clinic_reception, clinic_full.', p_kind USING ERRCODE = 'P0001';
|
||||
END IF;
|
||||
v_account_type := CASE WHEN p_kind = 'therapist' THEN 'therapist' ELSE 'clinic' END;
|
||||
IF EXISTS (
|
||||
SELECT 1 FROM public.tenant_members tm JOIN public.tenants t ON t.id = tm.tenant_id
|
||||
WHERE tm.user_id = p_user_id AND tm.role = 'tenant_admin' AND tm.status = 'active' AND t.kind = p_kind
|
||||
) THEN
|
||||
RAISE EXCEPTION 'Usuário já possui um tenant do tipo "%".', p_kind USING ERRCODE = 'P0001';
|
||||
END IF;
|
||||
v_name := COALESCE(NULLIF(TRIM(p_name), ''),
|
||||
(SELECT COALESCE(NULLIF(TRIM(pr.full_name), ''), SPLIT_PART(au.email, '@', 1))
|
||||
FROM public.profiles pr JOIN auth.users au ON au.id = pr.id WHERE pr.id = p_user_id),
|
||||
'Conta');
|
||||
|
||||
INSERT INTO public.tenants (name, kind, created_at) VALUES (v_name, p_kind, now()) RETURNING id INTO v_tenant_id;
|
||||
INSERT INTO public.tenant_members (tenant_id, user_id, role, status, created_at)
|
||||
VALUES (v_tenant_id, p_user_id, 'tenant_admin', 'active', now());
|
||||
UPDATE public.profiles SET account_type = v_account_type WHERE id = p_user_id;
|
||||
|
||||
-- F6 wiring: clone PRIMEIRO (cria o schema), seed DEPOIS (escreve no schema)
|
||||
PERFORM public.clone_tenant_template(v_tenant_id);
|
||||
PERFORM public.seed_determined_commitments(v_tenant_id);
|
||||
|
||||
RETURN v_tenant_id;
|
||||
END $$;
|
||||
|
||||
COMMIT;
|
||||
Reference in New Issue
Block a user