Folder-scoped media attachments for a staff person, backed by core
PhoenixKit.Modules.Storage (the same per-resource-folder convention
phoenix_kit_catalogue/phoenix_kit_locations use — no module-owned
table, no migration).
Each person owns a deterministic root folder staff-person-<uuid> for
generic files, with a nested Images subfolder (parent_uuid = the
root) for images — i.e. all of a person's files in one folder, images in a
folder inside it. Folders are resolved by name on every read (never
cached on Person, so an admin renaming/deleting the folder in
/admin/media can't strand a dangling uuid) and created lazily on first
upload. The [:name, :parent_uuid] unique index in core makes
find-or-create race-safe.
Files live in core phoenix_kit_files under the folder; uploading/browsing
is done by MediaSelectorModal (scoped to the folder), so this module only
resolves folders, lists their files, (un)links picked files, and removes
them — mirroring PhoenixKitCatalogue.Attachments' write semantics
(soft-trash a sole-owner file, unlink a shared one). It never hard-deletes
a possibly-shared asset.
Summary
Functions
Ensures file_uuid is attached to folder_uuid: a no-op if already home
there (the modal's scoped uploads land here directly); adopts an orphan file
as home; otherwise adds a FolderLink so a file picked from elsewhere
appears here without being moved from its owner.
The person's avatar File struct, or nil if unset / missing / trashed.
Thumbnail URL for the person's avatar (or nil).
The person's avatar file uuid (from metadata), or nil.
Clears the person's avatar pointer.
Removes a file from folder_uuid. If the file's home is this folder and it
is not linked elsewhere → soft-trash it (recoverable in the media trash). If
it is also linked elsewhere → promote a link to home (keeps it alive). If it
is here only via a FolderLink → just drop the link. Mirrors core's
non-destructive convention; never hard-deletes a shared asset.
Public download URL for a file (nil-safe).
Find-or-create the folder for kind, returning {:ok, uuid} or
{:error, reason}. Race-safe: a lost create (unique [:name, :parent_uuid])
re-resolves the winner. Call when an action needs the folder to exist
(opening the picker / handling a selection).
Heroicon name for a file based on its Storage type / mime.
Resolves the folder uuid for kind (:files → root, :images → the
nested Images subfolder) without creating it. Returns the uuid or
nil (used on render so viewing a tab doesn't spawn empty folders).
Human-readable byte count. Nil-safe.
Whether the file with this uuid is an image (by Storage file_type).
Files attached to folder_uuid (home-folder files plus those linked in via
FolderLink), newest first, excluding trashed. :only narrows by type:
:images (file_type == "image"), :non_images (everything else), or :all
(default). Defensive — keeps a tab showing only its own kind even if a stray
file of the other kind landed in the folder.
Permanently purges a person's media — deletes the root folder and its whole
subtree (the nested Images folder + every file, including bucket copies)
via core's cascading delete_folder_completely/1. Best-effort: logs and
returns :ok on any failure so it never blocks a person deletion. Call
only on a permanent delete (soft-trash keeps the files).
Deterministic root folder name for a person's files.
Points the person's avatar at file_uuid (server-owned metadata write).
Refuses a trashed person ({:error, :person_trashed}) — a removed person
shouldn't gain a new profile photo; clearing the avatar stays unguarded.
Thumbnail URL for an image file, falling back to the original (nil-safe).
Functions
Ensures file_uuid is attached to folder_uuid: a no-op if already home
there (the modal's scoped uploads land here directly); adopts an orphan file
as home; otherwise adds a FolderLink so a file picked from elsewhere
appears here without being moved from its owner.
@spec avatar_file(PhoenixKitStaff.Schemas.Person.t()) :: PhoenixKit.Modules.Storage.File.t() | nil
The person's avatar File struct, or nil if unset / missing / trashed.
@spec avatar_url(PhoenixKitStaff.Schemas.Person.t()) :: String.t() | nil
Thumbnail URL for the person's avatar (or nil).
@spec avatar_uuid(PhoenixKitStaff.Schemas.Person.t()) :: binary() | nil
The person's avatar file uuid (from metadata), or nil.
@spec clear_avatar(PhoenixKitStaff.Schemas.Person.t()) :: {:ok, PhoenixKitStaff.Schemas.Person.t()} | {:error, term()}
Clears the person's avatar pointer.
Removes a file from folder_uuid. If the file's home is this folder and it
is not linked elsewhere → soft-trash it (recoverable in the media trash). If
it is also linked elsewhere → promote a link to home (keeps it alive). If it
is here only via a FolderLink → just drop the link. Mirrors core's
non-destructive convention; never hard-deletes a shared asset.
Public download URL for a file (nil-safe).
@spec ensure_folder(binary(), :files | :images, binary() | nil) :: {:ok, binary()} | {:error, term()}
Find-or-create the folder for kind, returning {:ok, uuid} or
{:error, reason}. Race-safe: a lost create (unique [:name, :parent_uuid])
re-resolves the winner. Call when an action needs the folder to exist
(opening the picker / handling a selection).
Heroicon name for a file based on its Storage type / mime.
Resolves the folder uuid for kind (:files → root, :images → the
nested Images subfolder) without creating it. Returns the uuid or
nil (used on render so viewing a tab doesn't spawn empty folders).
Human-readable byte count. Nil-safe.
Whether the file with this uuid is an image (by Storage file_type).
@spec list_files( binary() | nil, keyword() ) :: [PhoenixKit.Modules.Storage.File.t()]
Files attached to folder_uuid (home-folder files plus those linked in via
FolderLink), newest first, excluding trashed. :only narrows by type:
:images (file_type == "image"), :non_images (everything else), or :all
(default). Defensive — keeps a tab showing only its own kind even if a stray
file of the other kind landed in the folder.
@spec purge_person_media(binary()) :: :ok
Permanently purges a person's media — deletes the root folder and its whole
subtree (the nested Images folder + every file, including bucket copies)
via core's cascading delete_folder_completely/1. Best-effort: logs and
returns :ok on any failure so it never blocks a person deletion. Call
only on a permanent delete (soft-trash keeps the files).
Deterministic root folder name for a person's files.
@spec set_avatar(PhoenixKitStaff.Schemas.Person.t(), binary()) :: {:ok, PhoenixKitStaff.Schemas.Person.t()} | {:error, term()}
Points the person's avatar at file_uuid (server-owned metadata write).
Refuses a trashed person ({:error, :person_trashed}) — a removed person
shouldn't gain a new profile photo; clearing the avatar stays unguarded.
Thumbnail URL for an image file, falling back to the original (nil-safe).