fix(audit): log_audit_change quebrava INSERT em tabelas globais
Regressao do schema-per-tenant: o trigger de auditoria deriva tenant_id via tenant_id_for_schema(TG_TABLE_SCHEMA), que retorna NULL para public.* (ex.: tenant_members) -> violava audit_logs.tenant_id NOT NULL -> QUALQUER novo membership (provisionamento, aceite de convite) falhava. Fix: quando o schema nao resolve tenant, cai no tenant_id da propria linha; se ainda NULL, nao audita mas nunca quebra a operacao. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,53 @@
|
||||
-- =============================================================================
|
||||
-- Fix (regressão schema-per-tenant): log_audit_change quebra INSERT em tabelas
|
||||
-- GLOBAIS auditadas.
|
||||
--
|
||||
-- log_audit_change deriva o tenant via tenant_id_for_schema(TG_TABLE_SCHEMA).
|
||||
-- Para tabelas em tenant_<slug> isso resolve certo. Mas o trigger também está
|
||||
-- em public.tenant_members (tabela global) — e tenant_id_for_schema('public')
|
||||
-- retorna NULL, violando audit_logs.tenant_id (NOT NULL). Resultado: QUALQUER
|
||||
-- INSERT em tenant_members falhava (provisionamento, aceite de convite).
|
||||
--
|
||||
-- Fix: quando o schema não resolve um tenant (tabela global), usa o tenant_id
|
||||
-- da própria linha (tenant_members.tenant_id). Se ainda assim for NULL, não
|
||||
-- audita — mas NUNCA quebra a operação de negócio.
|
||||
-- =============================================================================
|
||||
|
||||
CREATE OR REPLACE FUNCTION public.log_audit_change()
|
||||
RETURNS trigger
|
||||
LANGUAGE plpgsql SECURITY DEFINER SET search_path TO 'public','pg_temp'
|
||||
AS $function$
|
||||
DECLARE
|
||||
v_tenant_id uuid; v_entity_id text; v_old jsonb; v_new jsonb; v_changed text[];
|
||||
v_heavy text[] := ARRAY['content','content_html','content_json','raw_data','signature_data','pdf_blob','binary','body_html','body_text'];
|
||||
v_noise text[] := ARRAY['updated_at','last_seen_at','last_activity_at'];
|
||||
BEGIN
|
||||
v_tenant_id := public.tenant_id_for_schema(TG_TABLE_SCHEMA);
|
||||
|
||||
-- tabela global (public.*): cai no tenant_id da própria linha, se existir
|
||||
IF v_tenant_id IS NULL THEN
|
||||
v_tenant_id := NULLIF(to_jsonb(COALESCE(NEW, OLD)) ->> 'tenant_id', '')::uuid;
|
||||
END IF;
|
||||
|
||||
-- sem tenant resolvível → não audita, mas não quebra a operação
|
||||
IF v_tenant_id IS NULL THEN
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END IF;
|
||||
|
||||
IF TG_OP = 'DELETE' THEN
|
||||
v_entity_id := OLD.id::text; v_old := to_jsonb(OLD) - v_heavy; v_new := NULL;
|
||||
ELSIF TG_OP = 'INSERT' THEN
|
||||
v_entity_id := NEW.id::text; v_old := NULL; v_new := to_jsonb(NEW) - v_heavy;
|
||||
ELSE
|
||||
v_entity_id := NEW.id::text; v_old := to_jsonb(OLD) - v_heavy; v_new := to_jsonb(NEW) - v_heavy;
|
||||
SELECT array_agg(key ORDER BY key) INTO v_changed
|
||||
FROM jsonb_each(to_jsonb(NEW)) AS kv(key, value)
|
||||
WHERE (to_jsonb(OLD))->kv.key IS DISTINCT FROM kv.value;
|
||||
IF v_changed IS NULL THEN RETURN NEW; END IF;
|
||||
IF v_changed <@ v_noise THEN RETURN NEW; END IF;
|
||||
END IF;
|
||||
|
||||
INSERT INTO public.audit_logs (tenant_id, user_id, entity_type, entity_id, action, old_values, new_values, changed_fields)
|
||||
VALUES (v_tenant_id, auth.uid(), TG_TABLE_NAME, v_entity_id, lower(TG_OP), v_old, v_new, v_changed);
|
||||
RETURN COALESCE(NEW, OLD);
|
||||
END $function$;
|
||||
Reference in New Issue
Block a user