0044_account_purge_fk_sweep.sql91 lines · main
| 1 | -- 0044_account_purge_fk_sweep — finish the job 0042 started. |
| 2 | -- |
| 3 | -- 0042 flipped TWO foreign keys onto users.id to ON DELETE SET NULL |
| 4 | -- (organizations.created_by, audit_logs.actor_id) so the GDPR account |
| 5 | -- hard-delete (services/account-deletion.ts → hardDeleteExpiredAccounts) |
| 6 | -- could complete instead of raising FK violation 23503. But the SAME |
| 7 | -- "blocks-the-purge" class of FK was left NO ACTION on 7+ OTHER tables, so |
| 8 | -- the DELETE FROM users still fails for any user who created an api key, |
| 9 | -- sdk key, mcp key, env var, tenant secret, deployment, or invitation, OR |
| 10 | -- whose org owns a project that audit_logs.project_id still references. |
| 11 | -- |
| 12 | -- This migration walks EVERY remaining `*_by` / `project_id` FK of that |
| 13 | -- class and flips it to ON DELETE SET NULL (audit Theme 0 — "fix it |
| 14 | -- everywhere, not in one place"). For the three keys that were also NOT |
| 15 | -- NULL (api_keys / briven_auth_sdk_keys / mcp_keys created_by) we DROP NOT |
| 16 | -- NULL first — a key stays scoped to its project; only the creator |
| 17 | -- attribution is severed when that user is purged. |
| 18 | -- |
| 19 | -- Each DROP CONSTRAINT lists both the drizzle-style `_users_id_fk` / |
| 20 | -- `_projects_id_fk` name AND the postgres-default `_fkey` name with |
| 21 | -- IF EXISTS, so the migration is robust whichever name the live constraint |
| 22 | -- carries (org_invitations.invited_by was created as an inline column |
| 23 | -- REFERENCES, so its live name is the `_fkey` form). |
| 24 | -- |
| 25 | -- Also adds the project_members(user_id) index used by the sole-owner |
| 26 | -- membership sweep + access checks (the composite PK is |
| 27 | -- (project_id, user_id), so user_id alone was unindexed). |
| 28 | |
| 29 | -- api_keys.created_by: drop NOT NULL, recreate FK as ON DELETE SET NULL. |
| 30 | ALTER TABLE "api_keys" ALTER COLUMN "created_by" DROP NOT NULL; |
| 31 | ALTER TABLE "api_keys" DROP CONSTRAINT IF EXISTS "api_keys_created_by_users_id_fk"; |
| 32 | ALTER TABLE "api_keys" DROP CONSTRAINT IF EXISTS "api_keys_created_by_fkey"; |
| 33 | ALTER TABLE "api_keys" ADD CONSTRAINT "api_keys_created_by_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action; |
| 34 | --> statement-breakpoint |
| 35 | |
| 36 | -- briven_auth_sdk_keys.created_by: drop NOT NULL, recreate FK as ON DELETE SET NULL. |
| 37 | ALTER TABLE "briven_auth_sdk_keys" ALTER COLUMN "created_by" DROP NOT NULL; |
| 38 | ALTER TABLE "briven_auth_sdk_keys" DROP CONSTRAINT IF EXISTS "briven_auth_sdk_keys_created_by_users_id_fk"; |
| 39 | ALTER TABLE "briven_auth_sdk_keys" DROP CONSTRAINT IF EXISTS "briven_auth_sdk_keys_created_by_fkey"; |
| 40 | ALTER TABLE "briven_auth_sdk_keys" ADD CONSTRAINT "briven_auth_sdk_keys_created_by_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action; |
| 41 | --> statement-breakpoint |
| 42 | |
| 43 | -- mcp_keys.created_by: drop NOT NULL, recreate FK as ON DELETE SET NULL. |
| 44 | ALTER TABLE "mcp_keys" ALTER COLUMN "created_by" DROP NOT NULL; |
| 45 | ALTER TABLE "mcp_keys" DROP CONSTRAINT IF EXISTS "mcp_keys_created_by_users_id_fk"; |
| 46 | ALTER TABLE "mcp_keys" DROP CONSTRAINT IF EXISTS "mcp_keys_created_by_fkey"; |
| 47 | ALTER TABLE "mcp_keys" ADD CONSTRAINT "mcp_keys_created_by_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action; |
| 48 | --> statement-breakpoint |
| 49 | |
| 50 | -- audit_logs.project_id: recreate FK as ON DELETE SET NULL (sibling of the |
| 51 | -- actor_id FK that 0042 already fixed) so a project cascade during a purge |
| 52 | -- doesn't block on retained audit rows. Column is already nullable. |
| 53 | ALTER TABLE "audit_logs" DROP CONSTRAINT IF EXISTS "audit_logs_project_id_projects_id_fk"; |
| 54 | ALTER TABLE "audit_logs" DROP CONSTRAINT IF EXISTS "audit_logs_project_id_fkey"; |
| 55 | ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "projects"("id") ON DELETE set null ON UPDATE no action; |
| 56 | --> statement-breakpoint |
| 57 | |
| 58 | -- deployments.triggered_by: recreate FK as ON DELETE SET NULL. Already nullable. |
| 59 | ALTER TABLE "deployments" DROP CONSTRAINT IF EXISTS "deployments_triggered_by_users_id_fk"; |
| 60 | ALTER TABLE "deployments" DROP CONSTRAINT IF EXISTS "deployments_triggered_by_fkey"; |
| 61 | ALTER TABLE "deployments" ADD CONSTRAINT "deployments_triggered_by_users_id_fk" FOREIGN KEY ("triggered_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action; |
| 62 | --> statement-breakpoint |
| 63 | |
| 64 | -- project_invitations.invited_by: recreate FK as ON DELETE SET NULL. Already nullable. |
| 65 | ALTER TABLE "project_invitations" DROP CONSTRAINT IF EXISTS "project_invitations_invited_by_users_id_fk"; |
| 66 | ALTER TABLE "project_invitations" DROP CONSTRAINT IF EXISTS "project_invitations_invited_by_fkey"; |
| 67 | ALTER TABLE "project_invitations" ADD CONSTRAINT "project_invitations_invited_by_users_id_fk" FOREIGN KEY ("invited_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action; |
| 68 | --> statement-breakpoint |
| 69 | |
| 70 | -- org_invitations.invited_by: recreate FK as ON DELETE SET NULL. Already |
| 71 | -- nullable; live constraint is the inline-column `_fkey` form. |
| 72 | ALTER TABLE "org_invitations" DROP CONSTRAINT IF EXISTS "org_invitations_invited_by_users_id_fk"; |
| 73 | ALTER TABLE "org_invitations" DROP CONSTRAINT IF EXISTS "org_invitations_invited_by_fkey"; |
| 74 | ALTER TABLE "org_invitations" ADD CONSTRAINT "org_invitations_invited_by_users_id_fk" FOREIGN KEY ("invited_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action; |
| 75 | --> statement-breakpoint |
| 76 | |
| 77 | -- project_env_vars.created_by: recreate FK as ON DELETE SET NULL. Already nullable. |
| 78 | ALTER TABLE "project_env_vars" DROP CONSTRAINT IF EXISTS "project_env_vars_created_by_users_id_fk"; |
| 79 | ALTER TABLE "project_env_vars" DROP CONSTRAINT IF EXISTS "project_env_vars_created_by_fkey"; |
| 80 | ALTER TABLE "project_env_vars" ADD CONSTRAINT "project_env_vars_created_by_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action; |
| 81 | --> statement-breakpoint |
| 82 | |
| 83 | -- tenant_secrets.created_by: recreate FK as ON DELETE SET NULL. Already nullable. |
| 84 | ALTER TABLE "tenant_secrets" DROP CONSTRAINT IF EXISTS "tenant_secrets_created_by_users_id_fk"; |
| 85 | ALTER TABLE "tenant_secrets" DROP CONSTRAINT IF EXISTS "tenant_secrets_created_by_fkey"; |
| 86 | ALTER TABLE "tenant_secrets" ADD CONSTRAINT "tenant_secrets_created_by_users_id_fk" FOREIGN KEY ("created_by") REFERENCES "users"("id") ON DELETE set null ON UPDATE no action; |
| 87 | --> statement-breakpoint |
| 88 | |
| 89 | -- project_members(user_id) reverse-lookup index (composite PK leads with |
| 90 | -- project_id, so user_id alone was unindexed). |
| 91 | CREATE INDEX IF NOT EXISTS "project_members_user_id_idx" ON "project_members" USING btree ("user_id"); |