0042_account_purge_fk_set_null.sql32 lines · main
1-- 0042_account_purge_fk_set_null — unblock GDPR hard-deletion.
2--
3-- `hardDeleteExpiredAccounts` (services/account-deletion.ts) runs
4-- DELETE FROM users WHERE deleted_at < now() - 30 days
5-- which ALWAYS failed with FK violation 23503 (silently swallowed by the
6-- account-deletion-gc worker) because two FKs onto users.id were ON DELETE
7-- NO ACTION:
8-- * organizations.created_by (and NOT NULL)
9-- * audit_logs.actor_id (already nullable)
10-- Net effect: no expired account was ever purged.
11--
12-- Fix: make organizations.created_by nullable and flip both FKs to
13-- ON DELETE SET NULL. A surviving shared org keeps its row (creator nulls
14-- out); audit rows keep their action + timestamp (actor nulls out). The
15-- purge transaction hard-deletes the user's own sole-owner soft-deleted
16-- orgs first (their projects cascade via projects.org_id ON DELETE CASCADE),
17-- then deletes the user — which now succeeds instead of raising 23503.
18--
19-- DROP CONSTRAINT statements list both the drizzle-style name and the
20-- postgres-default `_fkey` name with IF EXISTS so the migration is robust
21-- whichever name the live constraint carries.
22
23-- organizations.created_by: drop NOT NULL, recreate FK as ON DELETE SET NULL.
24ALTER TABLE "organizations" ALTER COLUMN "created_by" DROP NOT NULL;
25ALTER TABLE "organizations" DROP CONSTRAINT IF EXISTS "organizations_created_by_fkey";
26ALTER TABLE "organizations" DROP CONSTRAINT IF EXISTS "organizations_created_by_users_id_fk";
27ALTER TABLE "organizations" ADD CONSTRAINT "organizations_created_by_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action;
28
29-- audit_logs.actor_id: already nullable; recreate FK as ON DELETE SET NULL.
30ALTER TABLE "audit_logs" DROP CONSTRAINT IF EXISTS "audit_logs_actor_id_users_id_fk";
31ALTER TABLE "audit_logs" DROP CONSTRAINT IF EXISTS "audit_logs_actor_id_fkey";
32ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_actor_id_users_id_fk" FOREIGN KEY ("actor_id") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action;