Files
teamvis-selfhost/supabase/migrations/0030_dsgvo_anonymize.sql
T
2026-06-25 16:38:31 +02:00

60 lines
2.5 KiB
PL/PgSQL

-- DSGVO Art. 17 (Recht auf Vergessenwerden) fuer Empfangs-Audit-Daten.
-- ====================================================================
-- Problem: reception_visit_audit ist per Trigger append-only (GoBD).
-- Bei einem Loeschanspruch nach DSGVO muessen aber personenbezogene
-- Daten aus dem `payload`-JSON entfernt werden — die strukturelle
-- Zeile (id, occurred_at, event_kind, actor_kind, actor_id, lead_id)
-- bleibt fuer den Audit-Trail erhalten.
--
-- Loesung:
-- 1. Spalte `redacted_at` markiert anonymisierte Eintraege.
-- 2. UPDATE-Trigger erlaubt EINEN spezifischen UPDATE-Pfad: das
-- Setzen von `redacted_at` UND gleichzeitiges Nullen / Redacten
-- der payload-PII. Alle anderen UPDATEs werden weiterhin geblockt.
-- 3. card_leads bekommt auch `redacted_at` — Anonymisierung sowohl
-- im Hauptdatensatz als auch im Audit-Log.
alter table public.reception_visit_audit
add column if not exists redacted_at timestamptz;
alter table public.card_leads
add column if not exists redacted_at timestamptz;
-- Trigger neu definieren: erlaubt UPDATEs die NUR redacted_at + payload
-- ändern (Anonymisierungs-Pfad). Aenderungen anderer Spalten bleiben
-- blockiert.
create or replace function public.reject_reception_audit_modify()
returns trigger as $$
begin
if tg_op = 'DELETE' then
raise exception 'reception_visit_audit ist append-only — DELETE nicht erlaubt';
end if;
-- UPDATE: nur erlauben wenn redacted_at von NULL auf NOT NULL gesetzt
-- wird UND keine anderen identitaetsrelevanten Felder geaendert werden.
if tg_op = 'UPDATE' then
if old.redacted_at is not null then
raise exception 'Eintrag wurde bereits anonymisiert — keine weiteren UPDATEs';
end if;
if new.redacted_at is null then
raise exception 'reception_visit_audit-UPDATE nur fuer DSGVO-Anonymisierung erlaubt (redacted_at muss gesetzt werden)';
end if;
if new.id <> old.id
or new.lead_id is distinct from old.lead_id
or new.occurred_at <> old.occurred_at
or new.event_kind <> old.event_kind
or new.actor_kind <> old.actor_kind
or new.actor_id is distinct from old.actor_id
then
raise exception 'reception_visit_audit-Anonymisierung darf nur payload + redacted_at aendern';
end if;
return new;
end if;
return new;
end;
$$ language plpgsql;
-- Index fuer schnelle Filterung "noch nicht anonymisiert".
create index if not exists reception_visit_audit_active_idx
on public.reception_visit_audit (occurred_at desc)
where redacted_at is null;