PhoenixKitWeb.Attachments (phoenix_kit v2.40.1)

Copy Markdown View Source

The pieces every module's file form shares, so each upload behaves the same wherever it happens: the upload config, storing a finished upload into a record's folder, the messages people see, and the files grid's featured-image and order rules.

socket = Attachments.allow(socket, :attachment_files, &handle_progress/3)

def handle_progress(:attachment_files, %{done?: true} = entry, socket) do
  stored =
    consume_uploaded_entry(socket, entry, fn %{path: path} ->
      {:ok, Attachments.store(path, entry, Actor.uuid(socket), folder_uuid)}
    end)

  case stored do
    {:ok, _file} -> …
    {:already_attached, existing} -> put_flash(socket, :info, Attachments.duplicate_notice(entry.client_name, existing))
    {:error, reason} -> put_flash(socket, :error, Attachments.failed_message(entry.client_name, reason))
  end
end

What each form keeps — which folder, when its pointers are written, who hears about a change — is its own; the folder rules underneath are PhoenixKit.Modules.Storage.ResourceFolders.

Summary

Functions

Registers upload name with the attachment defaults — any file type, 20 files, 100 MB each, uploaded as soon as they are chosen — and progress as its progress callback. opts override the defaults.

Files sorted by a saved order of uuids: files the order doesn't know keep their relative place after the ordered ones (new uploads land last), and an empty order changes nothing.

What to tell a person whose upload is byte-identical to a file already in the folder: storage de-duplicates by content, so nothing new appears and the file keeps the earlier upload's name — without this it reads as a lost file.

What to tell a person about an upload error (LiveView's or store/4's).

What to tell a person when their upload client_name could not be stored.

What to tell a person when a record's files folder cannot be prepared.

Stores a finished upload (the temp path of entry) for user_uuid and files it into folder_uuid by core's rules (ResourceFolders.place_stored/2): a content duplicate already in the folder is {:already_attached, file}, a trashed one is restored. The browser's file name is reduced to its base name before it reaches storage, and the type comes from Storage.determine_file_type/2. nil for the folder stores the file without placing it (a form that files it on save); a trashed duplicate is restored then too, so the file returned is always live. Never raises — call it outside a transaction, which a database error would abort.

A folder's files with the record's featured image first when it lives elsewhere — a featured image moved out of the folder is still what the record shows, so the grid shows it too.

Functions

allow(socket, name, progress, opts \\ [])

Registers upload name with the attachment defaults — any file type, 20 files, 100 MB each, uploaded as soon as they are chosen — and progress as its progress callback. opts override the defaults.

apply_order(files, order)

@spec apply_order([map()], [String.t()] | nil) :: [map()]

Files sorted by a saved order of uuids: files the order doesn't know keep their relative place after the ordered ones (new uploads land last), and an empty order changes nothing.

duplicate_notice(client_name, existing)

@spec duplicate_notice(String.t(), map()) :: String.t()

What to tell a person whose upload is byte-identical to a file already in the folder: storage de-duplicates by content, so nothing new appears and the file keeps the earlier upload's name — without this it reads as a lost file.

error_message(other)

@spec error_message(term()) :: String.t()

What to tell a person about an upload error (LiveView's or store/4's).

failed_message(client_name, arg2)

@spec failed_message(String.t(), term()) :: String.t()

What to tell a person when their upload client_name could not be stored.

folder_error_message()

@spec folder_error_message() :: String.t()

What to tell a person when a record's files folder cannot be prepared.

store(path, entry, user_uuid, folder_uuid)

@spec store(String.t(), map(), String.t() | nil, String.t() | nil) ::
  {:ok, PhoenixKit.Modules.Storage.File.t()}
  | {:already_attached, PhoenixKit.Modules.Storage.File.t()}
  | {:error, term()}

Stores a finished upload (the temp path of entry) for user_uuid and files it into folder_uuid by core's rules (ResourceFolders.place_stored/2): a content duplicate already in the folder is {:already_attached, file}, a trashed one is restored. The browser's file name is reduced to its base name before it reaches storage, and the type comes from Storage.determine_file_type/2. nil for the folder stores the file without placing it (a form that files it on save); a trashed duplicate is restored then too, so the file returned is always live. Never raises — call it outside a transaction, which a database error would abort.

with_featured(files, featured)

@spec with_featured([map()], map() | nil) :: [map()]

A folder's files with the record's featured image first when it lives elsewhere — a featured image moved out of the folder is still what the record shows, so the grid shows it too.