0044_account_purge_fk_sweep.sql 6.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091
  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. -- api_keys.created_by: drop NOT NULL, recreate FK as ON DELETE SET NULL.
  29. ALTER TABLE "api_keys" ALTER COLUMN "created_by" DROP NOT NULL;
  30. ALTER TABLE "api_keys" DROP CONSTRAINT IF EXISTS "api_keys_created_by_users_id_fk";
  31. ALTER TABLE "api_keys" DROP CONSTRAINT IF EXISTS "api_keys_created_by_fkey";
  32. 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;
  33. --> statement-breakpoint
  34. -- briven_auth_sdk_keys.created_by: drop NOT NULL, recreate FK as ON DELETE SET NULL.
  35. ALTER TABLE "briven_auth_sdk_keys" ALTER COLUMN "created_by" DROP NOT NULL;
  36. ALTER TABLE "briven_auth_sdk_keys" DROP CONSTRAINT IF EXISTS "briven_auth_sdk_keys_created_by_users_id_fk";
  37. ALTER TABLE "briven_auth_sdk_keys" DROP CONSTRAINT IF EXISTS "briven_auth_sdk_keys_created_by_fkey";
  38. 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;
  39. --> statement-breakpoint
  40. -- mcp_keys.created_by: drop NOT NULL, recreate FK as ON DELETE SET NULL.
  41. ALTER TABLE "mcp_keys" ALTER COLUMN "created_by" DROP NOT NULL;
  42. ALTER TABLE "mcp_keys" DROP CONSTRAINT IF EXISTS "mcp_keys_created_by_users_id_fk";
  43. ALTER TABLE "mcp_keys" DROP CONSTRAINT IF EXISTS "mcp_keys_created_by_fkey";
  44. 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;
  45. --> statement-breakpoint
  46. -- audit_logs.project_id: recreate FK as ON DELETE SET NULL (sibling of the
  47. -- actor_id FK that 0042 already fixed) so a project cascade during a purge
  48. -- doesn't block on retained audit rows. Column is already nullable.
  49. ALTER TABLE "audit_logs" DROP CONSTRAINT IF EXISTS "audit_logs_project_id_projects_id_fk";
  50. ALTER TABLE "audit_logs" DROP CONSTRAINT IF EXISTS "audit_logs_project_id_fkey";
  51. 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;
  52. --> statement-breakpoint
  53. -- deployments.triggered_by: recreate FK as ON DELETE SET NULL. Already nullable.
  54. ALTER TABLE "deployments" DROP CONSTRAINT IF EXISTS "deployments_triggered_by_users_id_fk";
  55. ALTER TABLE "deployments" DROP CONSTRAINT IF EXISTS "deployments_triggered_by_fkey";
  56. 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;
  57. --> statement-breakpoint
  58. -- project_invitations.invited_by: recreate FK as ON DELETE SET NULL. Already nullable.
  59. ALTER TABLE "project_invitations" DROP CONSTRAINT IF EXISTS "project_invitations_invited_by_users_id_fk";
  60. ALTER TABLE "project_invitations" DROP CONSTRAINT IF EXISTS "project_invitations_invited_by_fkey";
  61. 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;
  62. --> statement-breakpoint
  63. -- org_invitations.invited_by: recreate FK as ON DELETE SET NULL. Already
  64. -- nullable; live constraint is the inline-column `_fkey` form.
  65. ALTER TABLE "org_invitations" DROP CONSTRAINT IF EXISTS "org_invitations_invited_by_users_id_fk";
  66. ALTER TABLE "org_invitations" DROP CONSTRAINT IF EXISTS "org_invitations_invited_by_fkey";
  67. 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;
  68. --> statement-breakpoint
  69. -- project_env_vars.created_by: recreate FK as ON DELETE SET NULL. Already nullable.
  70. ALTER TABLE "project_env_vars" DROP CONSTRAINT IF EXISTS "project_env_vars_created_by_users_id_fk";
  71. ALTER TABLE "project_env_vars" DROP CONSTRAINT IF EXISTS "project_env_vars_created_by_fkey";
  72. 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;
  73. --> statement-breakpoint
  74. -- tenant_secrets.created_by: recreate FK as ON DELETE SET NULL. Already nullable.
  75. ALTER TABLE "tenant_secrets" DROP CONSTRAINT IF EXISTS "tenant_secrets_created_by_users_id_fk";
  76. ALTER TABLE "tenant_secrets" DROP CONSTRAINT IF EXISTS "tenant_secrets_created_by_fkey";
  77. 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;
  78. --> statement-breakpoint
  79. -- project_members(user_id) reverse-lookup index (composite PK leads with
  80. -- project_id, so user_id alone was unindexed).
  81. CREATE INDEX IF NOT EXISTS "project_members_user_id_idx" ON "project_members" USING btree ("user_id");