Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lib/modules/posts/web/edit.ex
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,7 @@ defmodule PhoenixKitWeb.Live.Modules.Posts.Edit do
file_uuids
|> Enum.map_join("; ", fn fid ->
file_url = get_file_url(fid)
encoded_url = Jason.encode!(file_url || "")
encoded_url = Jason.encode!(file_url)

"window.postsEditorInsertMedia && window.postsEditorInsertMedia(#{encoded_url}, '#{media_type}')"
end)
Expand Down
4 changes: 3 additions & 1 deletion lib/modules/publishing/db_storage.ex
Original file line number Diff line number Diff line change
Expand Up @@ -260,7 +260,9 @@ defmodule PhoenixKit.Modules.Publishing.DBStorage do

@doc "Trashes a post by setting status to 'trashed'."
def trash_post(%PublishingPost{} = post) do
update_post(post, %{status: "trashed"})
post
|> Ecto.Changeset.change(status: "trashed")
|> repo().update()
end

@doc "Hard-deletes a post and all its versions/contents (cascade)."
Expand Down
10 changes: 9 additions & 1 deletion lib/modules/publishing/groups.ex
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,13 @@ defmodule PhoenixKit.Modules.Publishing.Groups do
|> Enum.map(fn group -> group |> StaleFixer.fix_stale_group() |> db_group_to_map() end)
end

@doc "Lists groups filtered by status (e.g. 'active', 'trashed')."
@spec list_groups(String.t()) :: [group()]
def list_groups(status) do
DBStorage.list_groups(status)
|> Enum.map(&db_group_to_map/1)
end

@doc """
Gets a publishing group by slug.

Expand Down Expand Up @@ -363,11 +370,12 @@ defmodule PhoenixKit.Modules.Publishing.Groups do
end
end

defp db_group_to_map(%{name: name, slug: slug, mode: mode, data: data}) do
defp db_group_to_map(%{name: name, slug: slug, mode: mode, status: status, data: data}) do
%{
"name" => name,
"slug" => slug,
"mode" => mode || @default_group_mode,
"status" => status || "active",
"type" => Map.get(data, "type", @default_group_type),
"item_singular" => Map.get(data, "item_singular", @default_item_singular),
"item_plural" => Map.get(data, "item_plural", @default_item_plural)
Expand Down
47 changes: 47 additions & 0 deletions lib/modules/publishing/language_helpers.ex
Original file line number Diff line number Diff line change
Expand Up @@ -207,4 +207,51 @@ defmodule PhoenixKit.Modules.Publishing.LanguageHelpers do
nil
end
end

# ===========================================================================
# Post Language Building
# ===========================================================================

@doc """
Builds language data for a post's language switcher.
Returns a list of language maps with status, enabled flag, known flag, and metadata.
"""
def build_post_languages(post, enabled_languages, primary_language \\ nil) do
primary_lang =
primary_language || post[:primary_language] || get_primary_language()

all_languages =
order_languages_for_display(
post.available_languages || [],
enabled_languages,
primary_lang
)

all_languages
|> Enum.map(&build_language_entry(&1, post, enabled_languages, primary_lang))
|> Enum.filter(fn lang -> lang.exists || lang.enabled end)
end

@doc """
Builds a single language entry map for a post.
"""
def build_language_entry(lang_code, post, enabled_languages, primary_lang) do
lang_info = get_language_info(lang_code)
available = post.available_languages || []
content_exists = lang_code in available
post_status = post[:metadata] && post.metadata.status

%{
code: lang_code,
display_code: get_display_code(lang_code, enabled_languages),
name: if(lang_info, do: lang_info.name, else: lang_code),
flag: if(lang_info, do: lang_info.flag, else: ""),
status: if(content_exists, do: post_status, else: nil),
exists: content_exists,
enabled: language_enabled?(lang_code, enabled_languages),
known: lang_info != nil,
is_primary: lang_code == primary_lang,
uuid: post[:uuid]
}
end
end
5 changes: 4 additions & 1 deletion lib/modules/publishing/listing_cache.ex
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,10 @@ defmodule PhoenixKit.Modules.Publishing.ListingCache do
- On invalidate: clears :persistent_term entry (next read triggers regeneration)
"""

alias PhoenixKit.Modules.Publishing.Constants
alias PhoenixKit.Modules.Publishing.DBStorage

@timestamp_modes Constants.timestamp_modes()
alias PhoenixKit.Modules.Publishing.LanguageHelpers
alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub
alias PhoenixKit.Settings
Expand Down Expand Up @@ -554,7 +557,7 @@ defmodule PhoenixKit.Modules.Publishing.ListingCache do
def find_post_by_mode(group_slug, post) do
mode = Map.get(post, :mode)

if mode in ["timestamp", :timestamp] do
if mode in @timestamp_modes do
date = post[:date]
time = post[:time]

Expand Down
142 changes: 103 additions & 39 deletions lib/modules/publishing/posts.ex
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,28 @@ defmodule PhoenixKit.Modules.Publishing.Posts do
DBStorage.list_posts_with_metadata(group_slug)
end

@doc "Lists posts filtered by status (e.g. 'trashed', 'published')."
@spec list_posts_by_status(String.t(), String.t()) :: [map()]
def list_posts_by_status(group_slug, status) do
DBStorage.list_posts_with_metadata(group_slug, status)
end

@doc "Lists raw DB post records for a group, optionally filtered by status."
@spec list_raw_posts(String.t(), String.t() | nil) :: [struct()]
def list_raw_posts(group_slug, status \\ nil) do
if status,
do: DBStorage.list_posts(group_slug, status),
else: DBStorage.list_posts(group_slug)
end

@doc "Counts primary language migration status from a list of posts."
@spec count_primary_language_status(list(), String.t()) :: map() | nil
def count_primary_language_status([], _primary), do: nil

def count_primary_language_status(posts, primary_language) do
DBStorage.count_primary_language_status_from_posts(posts, primary_language)
end

@doc """
Creates a new post for the given publishing group using the current timestamp.
"""
Expand Down Expand Up @@ -169,16 +191,48 @@ defmodule PhoenixKit.Modules.Publishing.Posts do
result = update_post_in_db(group_slug, post, params, audit_meta)

with {:ok, updated_post} <- result do
if Shared.should_regenerate_cache?(updated_post) do
ListingCache.regenerate(group_slug)
end

ListingCache.regenerate(group_slug)
PublishingPubSub.broadcast_post_updated(group_slug, updated_post)
end

result
end

@doc """
Changes a post's status by UUID.

Reads the post, resolves primary language, updates status via `update_post`,
invalidates render cache, and broadcasts the change.

Returns `{:ok, updated_post}` or `{:error, reason}`.
"""
@spec change_post_status(String.t(), String.t(), String.t(), keyword()) ::
{:ok, map()} | {:error, term()}
def change_post_status(group_slug, post_uuid, new_status, opts \\ []) do
case read_post_by_uuid(post_uuid) do
{:ok, post} ->
primary_language = post[:primary_language] || LanguageHelpers.get_primary_language()
is_primary_language = post.language == primary_language

case update_post(group_slug, post, %{"status" => new_status},
scope: opts[:scope],
is_primary_language: is_primary_language
) do
{:ok, updated_post} ->
identifier = updated_post[:uuid] || updated_post.slug
Publishing.Renderer.invalidate_cache(group_slug, identifier, updated_post.language)
PublishingPubSub.broadcast_post_status_changed(group_slug, updated_post)
{:ok, updated_post}

{:error, _} = err ->
err
end

{:error, _} = err ->
err
end
end

@doc """
Restores a trashed post by UUID, setting its status back to "draft".

Expand All @@ -196,6 +250,8 @@ defmodule PhoenixKit.Modules.Publishing.Posts do
{:ok, _} ->
StaleFixer.reconcile_post_status(db_post)
ListingCache.regenerate(group_slug)
broadcast_id = db_post.slug || db_post.uuid
PublishingPubSub.broadcast_post_updated(group_slug, %{slug: broadcast_id})
{:ok, post_uuid}

{:error, reason} ->
Expand Down Expand Up @@ -300,10 +356,17 @@ defmodule PhoenixKit.Modules.Publishing.Posts do
end

defp create_post_in_db(group_slug, opts) do
scope = Shared.fetch_option(opts, :scope)
group = DBStorage.get_group_by_slug(group_slug)
unless group, do: throw({:error, :group_not_found})
case DBStorage.get_group_by_slug(group_slug) do
nil ->
{:error, :group_not_found}

group ->
do_create_post_in_db(group_slug, group, opts)
end
end

defp do_create_post_in_db(group_slug, group, opts) do
scope = Shared.fetch_option(opts, :scope)
mode = Publishing.get_group_mode(group_slug)
primary_language = LanguageHelpers.get_primary_language()
now = UtilsDate.utc_now()
Expand Down Expand Up @@ -415,10 +478,6 @@ defmodule PhoenixKit.Modules.Publishing.Posts do
end
end
end
catch
{:error, reason} ->
Logger.warning("[Publishing] create_post failed for #{group_slug}: #{inspect(reason)}")
{:error, reason}
end

defp read_post_from_db(group_slug, identifier, language, version) do
Expand Down Expand Up @@ -610,37 +669,42 @@ defmodule PhoenixKit.Modules.Publishing.Posts do
content = Map.get(params, "content", post[:content] || "")
new_title = resolve_post_title(params, post, content)

# Title is required for primary language when publishing (drafts can be untitled)
if language == db_post.primary_language and new_status == "published" and
new_title in ["", Constants.default_title()] do
throw({:post_update_failed, :title_required})
with :ok <- validate_title_for_publish(db_post, language, new_status, new_title),
old_db_status = db_post.status,
:ok <- update_post_level_fields(db_post, new_status, params, audit_meta),
:ok <-
upsert_post_content(version, language, new_title, content, new_status, params, post) do
maybe_propagate_status(version, language, db_post, new_status, old_db_status)
read_updated_post(db_post, group_slug, final_slug, language, version_number)
end
else
{:error, :not_found}
end
end

# Capture old status from DB before updating (editor assigns may already reflect new status)
old_db_status = db_post.status
@default_title Constants.default_title()

update_post_level_fields!(db_post, new_status, params, audit_meta)
upsert_post_content(version, language, new_title, content, new_status, params, post)
maybe_propagate_status(version, language, db_post, new_status, old_db_status)
defp validate_title_for_publish(db_post, language, "published", title)
when title in ["", @default_title] do
if language == db_post.primary_language,
do: {:error, :title_required},
else: :ok
end

if db_post.mode == "timestamp" do
DBStorage.read_post_by_datetime(
group_slug,
db_post.post_date,
db_post.post_time,
language,
version_number
)
else
DBStorage.read_post(group_slug, final_slug, language, version_number)
end
defp validate_title_for_publish(_db_post, _language, _status, _title), do: :ok

defp read_updated_post(db_post, group_slug, final_slug, language, version_number) do
if db_post.mode == "timestamp" do
DBStorage.read_post_by_datetime(
group_slug,
db_post.post_date,
db_post.post_time,
language,
version_number
)
else
{:error, :not_found}
DBStorage.read_post(group_slug, final_slug, language, version_number)
end
catch
{:post_update_failed, reason} ->
Logger.warning("[Publishing] update_post failed for #{group_slug}: #{inspect(reason)}")
{:error, reason}
end

defp resolve_post_title(params, post, content) do
Expand All @@ -653,7 +717,7 @@ defmodule PhoenixKit.Modules.Publishing.Posts do
Constants.default_title()
end

defp update_post_level_fields!(db_post, new_status, params, audit_meta) do
defp update_post_level_fields(db_post, new_status, params, audit_meta) do
update_attrs =
%{
status: new_status,
Expand All @@ -664,7 +728,7 @@ defmodule PhoenixKit.Modules.Publishing.Posts do

case DBStorage.update_post(db_post, update_attrs) do
{:ok, _} -> :ok
{:error, reason} -> throw({:post_update_failed, reason})
{:error, reason} -> {:error, reason}
end
end

Expand Down Expand Up @@ -692,7 +756,7 @@ defmodule PhoenixKit.Modules.Publishing.Posts do
data: build_content_data(params, post, existing_data)
}) do
{:ok, _} -> :ok
{:error, reason} -> throw({:post_update_failed, reason})
{:error, reason} -> {:error, reason}
end
end

Expand Down
10 changes: 10 additions & 0 deletions lib/modules/publishing/publishing.ex
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ defmodule PhoenixKit.Modules.Publishing do

alias PhoenixKit.Dashboard.Tab
alias PhoenixKit.Modules.Languages
alias PhoenixKit.Modules.Publishing.DBStorage
alias PhoenixKit.Modules.Publishing.LanguageHelpers
alias PhoenixKit.Modules.Publishing.SlugHelpers
# ============================================================================
Expand Down Expand Up @@ -48,6 +49,7 @@ defmodule PhoenixKit.Modules.Publishing do
defdelegate generate_unique_slug(group_slug, title, preferred_slug), to: SlugHelpers
defdelegate generate_unique_slug(group_slug, title, preferred_slug, opts), to: SlugHelpers
defdelegate validate_url_slug(group_slug, url_slug, language, exclude), to: SlugHelpers
defdelegate clear_url_slug_from_post(group_slug, post_slug, url_slug), to: DBStorage

# ============================================================================
# Cache Delegates
Expand All @@ -71,6 +73,7 @@ defmodule PhoenixKit.Modules.Publishing do
alias PhoenixKit.Modules.Publishing.Groups

defdelegate list_groups(), to: Groups
defdelegate list_groups(status), to: Groups
defdelegate get_group(slug), to: Groups
defdelegate add_group(name, opts \\ []), to: Groups
defdelegate remove_group(slug), to: Groups
Expand All @@ -91,14 +94,19 @@ defmodule PhoenixKit.Modules.Publishing do
alias PhoenixKit.Modules.Publishing.Posts

defdelegate list_posts(group_slug, preferred_language \\ nil), to: Posts
defdelegate list_posts_by_status(group_slug, status), to: Posts
defdelegate list_raw_posts(group_slug, status \\ nil), to: Posts
defdelegate count_primary_language_status(posts, primary_language), to: Posts
defdelegate create_post(group_slug, opts \\ %{}), to: Posts
defdelegate read_post(group_slug, identifier, language \\ nil, version \\ nil), to: Posts
defdelegate read_post_by_uuid(post_uuid, language \\ nil, version \\ nil), to: Posts
defdelegate update_post(group_slug, post, params, opts \\ %{}), to: Posts
defdelegate change_post_status(group_slug, post_uuid, new_status, opts \\ []), to: Posts
defdelegate trash_post(group_slug, post_uuid), to: Posts
defdelegate restore_post(group_slug, post_uuid), to: Posts
defdelegate count_posts_on_date(group_slug, date), to: Posts
defdelegate list_times_on_date(group_slug, date), to: Posts
defdelegate read_post_by_datetime(group_slug, date, time), to: DBStorage
defdelegate find_by_url_slug(group_slug, language, url_slug), to: Posts
defdelegate find_by_previous_url_slug(group_slug, language, url_slug), to: Posts
defdelegate extract_slug_version_and_language(group_slug, identifier), to: Posts
Expand Down Expand Up @@ -164,6 +172,8 @@ defmodule PhoenixKit.Modules.Publishing do
defdelegate delete_language(group_slug, post_uuid, language_code, version \\ nil),
to: TranslationManager

defdelegate clear_translation(group_slug, post_uuid, language_code), to: TranslationManager

defdelegate set_translation_status(group_slug, post_identifier, version, language, status),
to: TranslationManager

Expand Down
Loading
Loading