Post CRUD operations for the Publishing module.
Handles creating, reading, updating, and trashing posts, as well as slug/version/language extraction and timestamp management.
Posts are routing shells — versions are the source of truth for status, published_at, and metadata (featured_image, tags, seo, description). Content rows hold per-language title + body + url_slug.
Summary
Functions
Counts posts on a specific date for a group.
Creates a new post for the given publishing group using the current timestamp.
Returns true when the given post is a DB-backed post (has a UUID).
Finds a post by a previous URL slug (for 301 redirects).
Finds a post by URL slug from the database.
Lists posts for a given publishing group slug.
Lists posts filtered by status (e.g. 'trashed', 'published').
Lists raw DB post records for a group, optionally filtered by status.
Lists time values for posts on a specific date.
Reads an existing post.
Reads a post by its database UUID.
Restores a trashed post by UUID, clearing its trashed_at timestamp.
Soft-deletes a post by UUID (sets trashed_at timestamp).
Updates a post in the database.
Updates version.data with metadata like featured_image_uuid, description, seo_title, tags, etc.
Functions
@spec count_posts_on_date(String.t(), Date.t() | String.t()) :: non_neg_integer()
Counts posts on a specific date for a group.
Creates a new post for the given publishing group using the current timestamp.
Returns true when the given post is a DB-backed post (has a UUID).
@spec find_by_previous_url_slug(String.t(), String.t(), String.t()) :: {:ok, map()} | {:error, :not_found | :cache_miss}
Finds a post by a previous URL slug (for 301 redirects).
@spec find_by_url_slug(String.t(), String.t(), String.t()) :: {:ok, map()} | {:error, :not_found | :cache_miss}
Finds a post by URL slug from the database.
Lists posts for a given publishing group slug.
Queries the database directly via DBStorage. The optional second argument is accepted for API compatibility but unused.
Lists posts filtered by status (e.g. 'trashed', 'published').
Lists raw DB post records for a group, optionally filtered by status.
Lists time values for posts on a specific date.
@spec read_post(String.t(), String.t(), String.t() | nil, integer() | nil) :: {:ok, map()} | {:error, any()}
Reads an existing post.
For slug-mode groups, accepts an optional version parameter. If version is nil, reads the latest version.
Reads from the database.
@spec read_post_by_uuid(String.t(), String.t() | nil, integer() | nil) :: {:ok, map()} | {:error, any()}
Reads a post by its database UUID.
Resolves the UUID to a group slug and post slug, then delegates to read_post/4.
Invalid version/language params gracefully fall back to latest/primary.
@spec restore_post(String.t(), String.t(), keyword() | map()) :: {:ok, String.t()} | {:error, term()}
Restores a trashed post by UUID, clearing its trashed_at timestamp.
Regenerates the group cache and broadcasts the update. Returns {:ok, post_uuid} on success or {:error, reason} on failure.
Soft-deletes a post by UUID (sets trashed_at timestamp).
Returns {:ok, post_uuid} on success or {:error, reason} on failure.
Updates a post in the database.
Updates version.data with metadata like featured_image_uuid, description, seo_title, tags, etc.
Version is the source of truth for all post metadata beyond title and body.
Merges new values into existing version.data, preserving keys not present in
the update. The optional legacy_promotions map is merged in BEFORE the
user updates so legacy content.data values fall through unchanged when the
user didn't touch them — the promotion path that pairs with
preserve_content_data's whitelist (see posts.ex do_update_post_in_db).