-- Die Fremdschlüssel von auth.users auf app_users umhängen. -- -- Solange profiles.id auf auth.users zeigt, lässt sich **keine zweite Person -- freischalten**. Die Anmeldung läuft über Auth.js und legt dort nichts mehr -- an; wer sich neu anmeldet, bekommt eine app_users-Zeile, aber die dazu -- nötige profiles-Zeile scheitert am Fremdschlüssel. Das Ergebnis wäre -- „Kein HR-Zugriff" ohne Möglichkeit, es zu ändern. -- -- Damit ist das hier keine Aufräumarbeit, sondern die Voraussetzung dafür, -- dass die Anwendung mehr als eine Person bedienen kann. -- -- Vorher geprüft: jeder referenzierte Wert steht bereits in app_users. Die -- Umhängung ändert also keine Daten, nur die Zusicherung. -- ═══ 1. Sicherheitsnetz ══════════════════════════════════════════ -- Was in app_users fehlt, wird aus profiles ergänzt. Im geprüften Bestand -- ist das leer; die Migration soll aber auch auf einer Kopie laufen, in der -- jemand zwischenzeitlich etwas angelegt hat. insert into app_users (id, external_id, email, full_name) select p.id, 'legacy:' || p.id::text, p.email, p.full_name from profiles p where not exists (select 1 from app_users a where a.id = p.id) on conflict (id) do nothing; -- ═══ 2. Umhängen ═════════════════════════════════════════════════ -- Über den Katalog statt acht handgeschriebene Anweisungen: die Namen der -- Zwänge stammen aus verschiedenen Migrationen, und einer davon von Hand -- falsch abgeschrieben hiesse, dass er stehen bleibt. do $$ declare r record; v_delete text; begin for r in select k.conname, c.relname as tabelle, a.attname as spalte, k.confdeltype from pg_constraint k join pg_class c on c.oid = k.conrelid join pg_namespace n on n.oid = c.relnamespace join pg_attribute a on a.attrelid = k.conrelid and a.attnum = any(k.conkey) where k.contype = 'f' and n.nspname = 'public' and k.confrelid = ( select oid from pg_class where relname = 'users' and relnamespace = (select oid from pg_namespace where nspname = 'auth') ) loop -- Das Löschverhalten bleibt, wie es war: profiles hängt kaskadierend am -- Konto, die Protokoll- und Notizfelder nicht — dort soll ein Eintrag -- gerade nicht verschwinden, weil ein Konto entfernt wird. v_delete := case r.confdeltype when 'c' then ' on delete cascade' else '' end; execute format('alter table %I drop constraint %I', r.tabelle, r.conname); execute format('alter table %I add constraint %I foreign key (%I) references app_users(id)%s', r.tabelle, r.conname, r.spalte, v_delete); raise notice '%.% -> app_users%', r.tabelle, r.spalte, v_delete; end loop; end; $$; -- ═══ 3. Gegenprobe ═══════════════════════════════════════════════ do $$ declare v_offen int; v_neu int; begin select count(*) into v_offen from pg_constraint k join pg_class c on c.oid = k.conrelid join pg_namespace n on n.oid = c.relnamespace where k.contype = 'f' and n.nspname = 'public' and k.confrelid = ( select oid from pg_class where relname = 'users' and relnamespace = (select oid from pg_namespace where nspname = 'auth') ); if v_offen > 0 then raise exception '% Fremdschlüssel zeigen weiterhin auf auth.users.', v_offen; end if; select count(*) into v_neu from pg_constraint k join pg_class c on c.oid = k.conrelid join pg_namespace n on n.oid = c.relnamespace where k.contype = 'f' and n.nspname = 'public' and k.confrelid = 'app_users'::regclass; if v_neu < 8 then raise exception 'Nur % Fremdschlüssel zeigen auf app_users — erwartet mindestens 8.', v_neu; end if; -- Und die eigentliche Frage: lässt sich jetzt eine zweite Person anlegen? -- Geprüft und wieder entfernt, damit die Migration keine Daten hinterlässt. declare v_id uuid := gen_random_uuid(); begin insert into app_users (id, external_id, email, full_name) values (v_id, 'probe:' || v_id::text, 'probe@example.invalid', 'Probe'); insert into profiles (id, email, full_name, role, is_active) values (v_id, 'probe@example.invalid', 'Probe', 'hr', false); delete from profiles where id = v_id; delete from app_users where id = v_id; end; end; $$;