PhoenixKit.Migrations.Postgres.V206 (phoenix_kit v2.45.0)

Copy Markdown View Source

V206: user-owned storage, the schema half (dev_docs/plans/2026-09-22-storage-libraries.md, "Next: V206").

A user may keep a library's bytes in their own S3-compatible bucket. Two nullable columns say whose:

  • phoenix_kit_buckets.owner_uuid and phoenix_kit_storage_profiles.owner_uuid — NULL is the site's (every bucket and profile that exists today). A non-NULL one is a user's, and Storage.list_buckets/0, Storage.list_enabled_buckets/0 and Storage.Profiles.list_profiles/0 leave it out, so nothing that enumerates the site's storage ever sees it.

owner_uuid has no foreign key, on purpose. ON DELETE SET NULL would turn a deleted user's bucket, credentials and all, into a site bucket in the shared pool; RESTRICT would block deleting a user. A dangling owner is inert: the owner's libraries are purged and their buckets and profiles removed with them.

One check keeps an owned bucket safe whatever the application does:

  • phoenix_kit_buckets_owned_check — owner_uuid IS NULL OR (provider <> 'local' AND integration_uuid IS NOT NULL AND access_type <> 'public'). A user bucket is never a filesystem path (that would be arbitrary write access on the server), never carries keys of its own (they live in the user's personal Integrations connection), and never hands out a plain object URL (a private library's files are served through short-lived links only).

Nothing is copied, moved or rewritten, and every existing row has a NULL owner, so the check holds for all of them.

Locks

Two nullable columns without defaults are metadata-only; the partial indexes cover no existing row; the check is added NOT VALID and validated over a handful of bucket rows. Re-runnable.

Summary

Functions

Rolls V206 back. A user-owned bucket or profile cannot exist without the column, so they are removed first, after the library rows that point at the profiles: those libraries go back to the Default profile.

Functions

down(opts)

Rolls V206 back. A user-owned bucket or profile cannot exist without the column, so they are removed first, after the library rows that point at the profiles: those libraries go back to the Default profile.