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:
- Drive-only — Use
PhoenixKitDocumentCreator.GoogleDocsClientfor raw Google Drive/Docs API calls (create files, list folders, export PDF, move files) without touching the local database. - DB-only — Use
list_templates_from_db/0,list_documents_from_db/0,load_cached_thumbnails/1,persist_thumbnail/2for local DB queries. - Combined — Use
create_template/2,create_document/2,sync_from_drive/0,delete_template/2, etc. which coordinate between Drive and DB.
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
@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 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)
@spec create_document_from_template(String.t(), map(), keyword()) :: {:ok, map()} | {:error, term()}
Create a document from a template by copying and filling variables.
- Copies the template Google Doc into the target folder
- Replaces all
{{variable}}placeholders with values - Persists the document record with variable_values and template link
- 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_idis also supplied. If omitted when:parent_folder_idis given, the storedpathis left unset and the nextsync_from_drive/0fills it from the walker.
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)
Move a document to the deleted/documents folder.
Options
:actor_uuid— UUID of the user performing the action (for activity logging)
Move a template to the deleted/templates folder.
Options
:actor_uuid— UUID of the user performing the action (for activity logging)
Detect {{ variables }} in a Google Doc's text content.
@spec documents_folder_url() :: String.t() | nil
Get the Google Drive URL for the documents folder.
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 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.
@spec get_folder_ids() :: map()
Get the folder IDs (auto-discovers if not cached).
@spec list_documents_from_db() :: [map()]
List documents from the local DB. Returns maps compatible with the LiveView.
@spec list_templates_from_db() :: [map()]
List templates from the local DB. Returns maps compatible with the LiveView.
@spec list_trashed_documents_from_db() :: [map()]
List trashed documents from the local DB.
@spec list_trashed_templates_from_db() :: [map()]
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.
Options
:actor_uuid— UUID of the user performing the action (for activity logging)
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 a thumbnail data URI to the DB by google_doc_id.
@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.
@spec refresh_folders() :: map()
Re-discover folder IDs from Drive.
@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
| Key | Required | Notes |
|---|---|---|
google_doc_id | yes | Drive file ID |
name | yes | Display name |
template_uuid | no | UUID of the source template, if applicable |
variable_values | no | Map of variables substituted during generation |
folder_id | no | Actual Drive folder holding the file. Defaults to managed |
path | no | Human-readable path. Defaults to managed root path |
status | no | Defaults to "published" |
thumbnail | no | Optional 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— defaulttrue. Broadcasts:files_changedon thedocument_creator:filestopic so connected admin LiveViews re-sync. Bulk callers (e.g. a backfill script registering hundreds of rows) should passfalseand trigger one broadcast or sync at the end:Enum.each(rows, &Documents.register_existing_document(&1, emit_pubsub: false)) Documents.broadcast_files_changed()
@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 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.
Options
:actor_uuid— UUID of the user performing the action (for activity logging)
@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.
@spec templates_folder_url() :: String.t() | nil
Get the Google Drive URL for the templates folder.
@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.
@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.