Context module for managing the Document Creator Category → Type hierarchy.
Provides CRUD, cascade soft-delete/restore, reorder, and picker helpers
for Category and Type schemas. Modeled on
PhoenixKitCatalogue.Catalogue.
Soft-Delete Cascade
trash_category/1— soft-deletes the category, all its types, and all templates reachable viacategory_uuidortype_uuid. Records affected template uuids in the activity log sorestore_category/1can scope its restore precisely.trash_type/1— soft-deletes the type and templates whosetype_uuidpoints at it.- Documents are never cascaded; they keep pointing at their (now trashed) category and remain usable.
PubSub
Every successful write broadcasts {:doc_taxonomy_changed, level, uuid}
on the shared PhoenixKit PubSub. level is :category or :type.
Consumers subscribe via PhoenixKit.PubSubHelper.subscribe/1.
Summary
Functions
Returns [{label, value}] for all active categories, ordered by position,
preceded by a {"No category", nil} empty option.
Counts categories, applying the same :status filter semantics as
list_categories/1. Counts in SQL instead of loading rows — use when only
the number is needed (e.g. a "Trash (N)" badge).
Counts types for a category, applying the same :status filter semantics as
list_types_for_category/2. Counts in SQL instead of loading rows.
Creates a category.
Creates a type.
Fetches a category by UUID. Returns nil if not found.
Fetches a category by UUID. Raises Ecto.NoResultsError if not found.
Fetches a type by UUID. Returns nil if not found.
Fetches a type by UUID. Raises Ecto.NoResultsError if not found.
Lists categories ordered by position then name.
Returns a list of {category, [types]} tuples for all active categories,
ordered by position. Types within each category are ordered by position.
Lists types for a category ordered by position then name.
Permanently deletes a category and all its types from the database.
Permanently deletes a type from the database.
Reorders categories by assigning positions from the given ordered list.
Reorders types within a category by assigning positions from the given ordered list.
Restores a soft-deleted category.
Restores a soft-deleted type and templates trashed by its cascade.
Subscribes the caller to taxonomy change events.
Soft-deletes a category by setting its status to "deleted".
Soft-deletes a type by setting its status to "deleted".
Returns [{label, value}] for all active types within a category, ordered
by position, preceded by a {"No type", nil} empty option.
Updates a category with the given attributes.
Updates a type with the given attributes.
Functions
@spec category_options() :: [{String.t(), Ecto.UUID.t() | nil}]
Returns [{label, value}] for all active categories, ordered by position,
preceded by a {"No category", nil} empty option.
Suitable for options_for_select/2. The empty option lets users clear the
FK (which is nullable).
Drop-in replacement for the hard-coded category_options/0 in
documents_live.ex.
@spec count_categories(keyword()) :: non_neg_integer()
Counts categories, applying the same :status filter semantics as
list_categories/1. Counts in SQL instead of loading rows — use when only
the number is needed (e.g. a "Trash (N)" badge).
@spec count_types_for_category( Ecto.UUID.t(), keyword() ) :: non_neg_integer()
Counts types for a category, applying the same :status filter semantics as
list_types_for_category/2. Counts in SQL instead of loading rows.
@spec create_category( map(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Category.t()} | {:error, Ecto.Changeset.t(PhoenixKitDocumentCreator.Schemas.Category.t())}
Creates a category.
Required attributes
:name— category name (1-255 chars)
Optional attributes
:description,:position(default 0),:status(default"active"),:data
@spec create_type( map(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Type.t()} | {:error, Ecto.Changeset.t(PhoenixKitDocumentCreator.Schemas.Type.t())}
Creates a type.
Required attributes
:name— type name (1-255 chars):category_uuid— parent category
Optional attributes
:description,:position,:status,:data
@spec get_category(Ecto.UUID.t()) :: PhoenixKitDocumentCreator.Schemas.Category.t() | nil
Fetches a category by UUID. Returns nil if not found.
@spec get_category!(Ecto.UUID.t()) :: PhoenixKitDocumentCreator.Schemas.Category.t()
Fetches a category by UUID. Raises Ecto.NoResultsError if not found.
@spec get_type(Ecto.UUID.t()) :: PhoenixKitDocumentCreator.Schemas.Type.t() | nil
Fetches a type by UUID. Returns nil if not found.
@spec get_type!(Ecto.UUID.t()) :: PhoenixKitDocumentCreator.Schemas.Type.t()
Fetches a type by UUID. Raises Ecto.NoResultsError if not found.
@spec list_categories(keyword()) :: [PhoenixKitDocumentCreator.Schemas.Category.t()]
Lists categories ordered by position then name.
Options
:status— when provided, returns only categories with this exact status (e.g."active","deleted"). When nil (default), returns all non-deleted categories.
@spec list_category_tree() :: [ {PhoenixKitDocumentCreator.Schemas.Category.t(), [PhoenixKitDocumentCreator.Schemas.Type.t()]} ]
Returns a list of {category, [types]} tuples for all active categories,
ordered by position. Types within each category are ordered by position.
Useful for building grouped pickers in LiveViews.
@spec list_types_for_category( Ecto.UUID.t(), keyword() ) :: [PhoenixKitDocumentCreator.Schemas.Type.t()]
Lists types for a category ordered by position then name.
Options
:status— when provided, returns only types with this exact status. Defaults to non-deleted types.
@spec permanently_delete_category( PhoenixKitDocumentCreator.Schemas.Category.t(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Category.t()} | {:error, term()}
Permanently deletes a category and all its types from the database.
Relies on ON DELETE CASCADE for child types and ON DELETE SET NULL for
template/document FK columns.
@spec permanently_delete_type( PhoenixKitDocumentCreator.Schemas.Type.t(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Type.t()} | {:error, term()}
Permanently deletes a type from the database.
Relies on ON DELETE SET NULL for template/document FK columns.
@spec reorder_categories( [Ecto.UUID.t()], keyword() ) :: :ok | {:error, term()}
Reorders categories by assigning positions from the given ordered list.
Each uuid in the list gets position = index. UUIDs not present keep
their existing positions.
@spec reorder_types(Ecto.UUID.t(), [Ecto.UUID.t()], keyword()) :: :ok | {:error, term()}
Reorders types within a category by assigning positions from the given ordered list.
@spec restore_category( PhoenixKitDocumentCreator.Schemas.Category.t(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Category.t()} | {:error, term()}
Restores a soft-deleted category.
Cascades in one transaction:
- Category
status → "active" - Types whose uuids were recorded in the trash activity log
status → "active"(only those, so types the user had trashed manually before the cascade stay trashed) - Templates whose uuids were recorded in the trash activity log
status → "published"(only those, to avoid restoring manually trashed templates)
When PhoenixKit.Activity is not loaded (or no matching activity entry
exists), cascade-trashed types and templates are not restored — they
must be restored manually.
@spec restore_type( PhoenixKitDocumentCreator.Schemas.Type.t(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Type.t()} | {:error, term()}
Restores a soft-deleted type and templates trashed by its cascade.
@spec subscribe() :: :ok | {:error, term()}
Subscribes the caller to taxonomy change events.
@spec trash_category( PhoenixKitDocumentCreator.Schemas.Category.t(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Category.t()} | {:error, term()}
Soft-deletes a category by setting its status to "deleted".
Cascades in one transaction:
- Category
status → "deleted" - Its currently-active types
status → "deleted" - All templates reachable via
category_uuid(directly) or via any of the category'stype_uuidvalues →status → "trashed"
Documents are NOT cascaded.
Affected type and template uuids are stored in the activity log payload
so restore_category/1 restores only what this cascade trashed — types
the user had already trashed manually stay trashed.
@spec trash_type( PhoenixKitDocumentCreator.Schemas.Type.t(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Type.t()} | {:error, term()}
Soft-deletes a type by setting its status to "deleted".
Cascades to templates whose type_uuid points at this type. Affected
template uuids are stored in the activity log.
@spec type_options(Ecto.UUID.t() | nil) :: [{String.t(), Ecto.UUID.t() | nil}]
Returns [{label, value}] for all active types within a category, ordered
by position, preceded by a {"No type", nil} empty option.
Pass nil as category_uuid (or when no category is selected) to get
only the empty option.
Suitable for options_for_select/2.
@spec update_category( PhoenixKitDocumentCreator.Schemas.Category.t(), map(), keyword() ) :: {:ok, PhoenixKitDocumentCreator.Schemas.Category.t()} | {:error, Ecto.Changeset.t(PhoenixKitDocumentCreator.Schemas.Category.t())}
Updates a category with the given attributes.
@spec update_type(PhoenixKitDocumentCreator.Schemas.Type.t(), map(), keyword()) :: {:ok, PhoenixKitDocumentCreator.Schemas.Type.t()} | {:error, Ecto.Changeset.t(PhoenixKitDocumentCreator.Schemas.Type.t())}
Updates a type with the given attributes.