PhoenixKitDocumentCreator.Documents (PhoenixKitDocumentCreator v0.2.8)

Copy Markdown View Source

Context module for managing templates and documents via Google Drive.

Google Drive is the single source of truth for file content. This module mirrors file metadata (name, google_doc_id, status, thumbnails, variables) to the local database for fast listing and audit tracking.

API layers

This module provides combined operations (Drive + DB). For direct access:

Summary

Functions

Broadcast {:files_changed, self()} on the document_creator:files topic.

Create a blank document in the documents folder. Returns {:ok, %{doc_id, name, url}}.

Create a document from a template by copying and filling variables.

Create a blank template in the templates folder. Returns {:ok, %{doc_id, name, url}}.

Move a document to the deleted/documents folder.

Move a template to the deleted/templates folder.

Detect {{ variables }} in a Google Doc's text content.

Get the Google Drive URL for the documents folder.

Export a Google Doc to PDF. Returns {:ok, pdf_binary}.

Fetch thumbnails for a list of Drive files asynchronously.

Get the folder IDs (auto-discovers if not cached).

List documents from the local DB. Returns maps compatible with the LiveView.

List templates from the local DB. Returns maps compatible with the LiveView.

List trashed documents from the local DB.

List trashed templates from the local DB.

Load cached thumbnails from DB for a list of google_doc_ids.

Log a manual user action to the activity feed.

Move a file into the managed documents folder and classify it as a document.

Move a file into the managed templates folder and classify it as a template.

Persist a thumbnail data URI to the DB by google_doc_id.

The PubSub topic on which {:files_changed, self()} messages are broadcast whenever a template or document DB record is mutated.

Re-discover folder IDs from Drive.

Register a Drive document that the caller has already created (or already knows about) into the local DB.

Register a Drive template that the caller has already created or knows about.

Restore a trashed document back to the documents folder.

Restore a trashed template back to the templates folder.

Persist the file's current parent folder as its accepted location.

Sync local DB with Google Drive.

Get the Google Drive URL for the templates folder.

Upsert a document record from a Google Drive file map.

Upsert a template record from a Google Drive file map.

Functions

broadcast_files_changed()

@spec broadcast_files_changed() :: :ok

Broadcast {:files_changed, self()} on the document_creator:files topic.

Use this after a bulk register_existing_document/2 / register_existing_template/2 call that passed emit_pubsub: false, to trigger a single resync in any connected admin LiveViews.

Silently no-ops if the PubSub system isn't available (e.g. background jobs or tests without a running PubSub registry).

create_document(name \\ "Untitled Document", opts \\ [])

@spec create_document(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, term()}

Create a blank document in the documents folder. Returns {:ok, %{doc_id, name, url}}.

Options

  • :actor_uuid — UUID of the user performing the action (for activity logging)

create_document_from_template(template_file_id, variable_values, opts \\ [])

@spec create_document_from_template(String.t(), map(), keyword()) ::
  {:ok, map()} | {:error, term()}

Create a document from a template by copying and filling variables.

  1. Copies the template Google Doc into the target folder
  2. Replaces all {{variable}} placeholders with values
  3. Persists the document record with variable_values and template link
  4. Returns {:ok, %{doc_id, url}}

Options

  • :name — document name (default "New Document")
  • :actor_uuid — UUID of the user performing the action (activity log)
  • :parent_folder_id — Drive folder ID to copy into. Defaults to the managed documents folder. Supply this to place the new document in a subfolder (e.g. order-123/sub-4/) you manage yourself.
  • :path — human-readable path string to store on the record. Only meaningful when :parent_folder_id is also supplied. If omitted when :parent_folder_id is given, the stored path is left unset and the next sync_from_drive/0 fills it from the walker.

create_template(name \\ "Untitled Template", opts \\ [])

@spec create_template(
  String.t(),
  keyword()
) :: {:ok, map()} | {:error, term()}

Create a blank template in the templates folder. Returns {:ok, %{doc_id, name, url}}.

Options

  • :actor_uuid — UUID of the user performing the action (for activity logging)

delete_document(file_id, opts \\ [])

@spec delete_document(
  String.t(),
  keyword()
) :: :ok | {:error, term()}

Move a document to the deleted/documents folder.

Options

  • :actor_uuid — UUID of the user performing the action (for activity logging)

delete_template(file_id, opts \\ [])

@spec delete_template(
  String.t(),
  keyword()
) :: :ok | {:error, term()}

Move a template to the deleted/templates folder.

Options

  • :actor_uuid — UUID of the user performing the action (for activity logging)

detect_variables(file_id)

@spec detect_variables(String.t()) :: {:ok, [String.t()]} | {:error, term()}

Detect {{ variables }} in a Google Doc's text content.

documents_folder_url()

@spec documents_folder_url() :: String.t() | nil

Get the Google Drive URL for the documents folder.

export_pdf(file_id, opts \\ [])

@spec export_pdf(
  String.t(),
  keyword()
) :: {:ok, binary()} | {:error, term()}

Export a Google Doc to PDF. Returns {:ok, pdf_binary}.

Options

  • :actor_uuid — UUID of the user performing the action (for activity logging)
  • :name — document name (for activity metadata)

fetch_thumbnails_async(files, caller_pid)

@spec fetch_thumbnails_async([map()], pid()) :: :ok

Fetch thumbnails for a list of Drive files asynchronously.

Spawns a single supervised parent task under PhoenixKit.TaskSupervisor that fans out via Task.async_stream/3 with a bounded max_concurrency so opening a folder with hundreds of files doesn't fire hundreds of simultaneous Drive requests. Each completion sends {:thumbnail_result, file_id, data_uri} back to caller_pid and persists the thumbnail to the DB. The parent is restart: :temporary so it dies cleanly if the caller LV closes mid-fetch — but in-flight persists still complete.

get_folder_ids()

@spec get_folder_ids() :: map()

Get the folder IDs (auto-discovers if not cached).

list_documents_from_db()

@spec list_documents_from_db() :: [map()]

List documents from the local DB. Returns maps compatible with the LiveView.

list_templates_from_db()

@spec list_templates_from_db() :: [map()]

List templates from the local DB. Returns maps compatible with the LiveView.

list_trashed_documents_from_db()

@spec list_trashed_documents_from_db() :: [map()]

List trashed documents from the local DB.

list_trashed_templates_from_db()

@spec list_trashed_templates_from_db() :: [map()]

List trashed templates from the local DB.

load_cached_thumbnails(google_doc_ids)

@spec load_cached_thumbnails([String.t()] | any()) :: %{
  required(String.t()) => String.t()
}

Load cached thumbnails from DB for a list of google_doc_ids.

log_manual_action(action, opts \\ [])

@spec log_manual_action(
  String.t(),
  keyword()
) :: :ok

Log a manual user action to the activity feed.

move_to_documents(file_id, opts \\ [])

@spec move_to_documents(
  String.t(),
  keyword()
) :: :ok | {:error, term()}

Move a file into the managed documents folder and classify it as a document.

Options

  • :actor_uuid — UUID of the user performing the action (for activity logging)

move_to_templates(file_id, opts \\ [])

@spec move_to_templates(
  String.t(),
  keyword()
) :: :ok | {:error, term()}

Move a file into the managed templates folder and classify it as a template.

Options

  • :actor_uuid — UUID of the user performing the action (for activity logging)

persist_thumbnail(google_doc_id, data_uri)

@spec persist_thumbnail(String.t(), String.t()) :: :ok

Persist a thumbnail data URI to the DB by google_doc_id.

pubsub_topic()

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

The PubSub topic on which {:files_changed, self()} messages are broadcast whenever a template or document DB record is mutated.

Admin LiveViews subscribe to this topic in mount/3. Prefer calling this helper over hard-coding the topic string so the two stay in sync.

refresh_folders()

@spec refresh_folders() :: map()

Re-discover folder IDs from Drive.

register_existing_document(attrs, opts \\ [])

@spec register_existing_document(
  map(),
  keyword()
) ::
  {:ok, PhoenixKitDocumentCreator.Schemas.Document.t()}
  | {:error, Ecto.Changeset.t() | term()}

Register a Drive document that the caller has already created (or already knows about) into the local DB.

Use this when your own wrapper code handles the Drive-side work (copy, placement in a subfolder, variable substitution) and you just need the file to appear in list_documents_from_db/0 and be classified correctly by future sync_from_drive/0 runs.

Makes no Drive API calls. This is a pure DB upsert — garbage inputs do not error, they self-correct on the next sync (the walker rewrites path/folder_id, and files that are not actually in the managed tree get classified :unfiled or :lost per the usual reconciliation rules).

attrs — map keyed by atoms or strings

KeyRequiredNotes
google_doc_idyesDrive file ID
nameyesDisplay name
template_uuidnoUUID of the source template, if applicable
variable_valuesnoMap of variables substituted during generation
folder_idnoActual Drive folder holding the file. Defaults to managed
pathnoHuman-readable path. Defaults to managed root path
statusnoDefaults to "published"
thumbnailnoOptional data URI

If :folder_id points outside the managed documents tree, the next sync_from_drive/0 will classify the record as :unfiled and surface the resolution popup in the admin UI.

opts

  • :actor_uuid — user UUID for the activity log

  • :emit_pubsub — default true. Broadcasts :files_changed on the document_creator:files topic so connected admin LiveViews re-sync. Bulk callers (e.g. a backfill script registering hundreds of rows) should pass false and trigger one broadcast or sync at the end:

    Enum.each(rows, &Documents.register_existing_document(&1, emit_pubsub: false))
    Documents.broadcast_files_changed()

register_existing_template(attrs, opts \\ [])

@spec register_existing_template(
  map(),
  keyword()
) ::
  {:ok, PhoenixKitDocumentCreator.Schemas.Template.t()}
  | {:error, Ecto.Changeset.t() | term()}

Register a Drive template that the caller has already created or knows about.

Symmetric to register_existing_document/2 — see its documentation for the attrs shape and options. Unlike documents, template registration does not accept template_uuid or variable_values.

restore_document(file_id, opts \\ [])

@spec restore_document(
  String.t(),
  keyword()
) :: :ok | {:error, term()}

Restore a trashed document back to the documents folder.

restore_template(file_id, opts \\ [])

@spec restore_template(
  String.t(),
  keyword()
) :: :ok | {:error, term()}

Restore a trashed template back to the templates folder.

set_correct_location(file_id, opts \\ [])

@spec set_correct_location(
  String.t(),
  keyword()
) :: :ok | {:error, term()}

Persist the file's current parent folder as its accepted location.

Options

  • :actor_uuid — UUID of the user performing the action (for activity logging)

sync_from_drive()

@spec sync_from_drive() :: :ok | {:error, :sync_failed}

Sync local DB with Google Drive.

Recursively walks both managed trees (templates and documents), upserts every Google Doc found (including those nested in subfolders) with its actual parent folder_id and human-readable path, then reconciles DB records against the walk — records whose google_doc_id is missing from the walk are re-classified via a per-file Drive API call as trashed, lost, or unfiled according to their current Drive parents.

Files in any descendant of a managed folder are treated as published.

templates_folder_url()

@spec templates_folder_url() :: String.t() | nil

Get the Google Drive URL for the templates folder.

upsert_document_from_drive(file, extra_attrs \\ %{})

@spec upsert_document_from_drive(map(), map()) ::
  {:ok, PhoenixKitDocumentCreator.Schemas.Document.t()}
  | {:error, Ecto.Changeset.t()}

Upsert a document record from a Google Drive file map.

upsert_template_from_drive(file, extra_attrs \\ %{})

@spec upsert_template_from_drive(map(), map()) ::
  {:ok, PhoenixKitDocumentCreator.Schemas.Template.t()}
  | {:error, Ecto.Changeset.t()}

Upsert a template record from a Google Drive file map.