diff --git a/lib/modules/posts/web/edit.ex b/lib/modules/posts/web/edit.ex index 4cd805864..96d5bff63 100644 --- a/lib/modules/posts/web/edit.ex +++ b/lib/modules/posts/web/edit.ex @@ -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) diff --git a/lib/modules/publishing/db_storage.ex b/lib/modules/publishing/db_storage.ex index 5c1116334..45c94d93e 100644 --- a/lib/modules/publishing/db_storage.ex +++ b/lib/modules/publishing/db_storage.ex @@ -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)." diff --git a/lib/modules/publishing/groups.ex b/lib/modules/publishing/groups.ex index 4f4b8c27a..4a14065a3 100644 --- a/lib/modules/publishing/groups.ex +++ b/lib/modules/publishing/groups.ex @@ -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. @@ -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) diff --git a/lib/modules/publishing/language_helpers.ex b/lib/modules/publishing/language_helpers.ex index a2226da18..2248d64f7 100644 --- a/lib/modules/publishing/language_helpers.ex +++ b/lib/modules/publishing/language_helpers.ex @@ -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 diff --git a/lib/modules/publishing/listing_cache.ex b/lib/modules/publishing/listing_cache.ex index be493e59e..770dd1ebc 100644 --- a/lib/modules/publishing/listing_cache.ex +++ b/lib/modules/publishing/listing_cache.ex @@ -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 @@ -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] diff --git a/lib/modules/publishing/posts.ex b/lib/modules/publishing/posts.ex index a02e91a83..6b8b58b8a 100644 --- a/lib/modules/publishing/posts.ex +++ b/lib/modules/publishing/posts.ex @@ -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. """ @@ -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". @@ -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} -> @@ -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() @@ -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 @@ -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 @@ -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, @@ -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 @@ -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 diff --git a/lib/modules/publishing/publishing.ex b/lib/modules/publishing/publishing.ex index c23378dfe..fdcf15ef2 100644 --- a/lib/modules/publishing/publishing.ex +++ b/lib/modules/publishing/publishing.ex @@ -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 # ============================================================================ @@ -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 @@ -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 @@ -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 @@ -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 diff --git a/lib/modules/publishing/pubsub.ex b/lib/modules/publishing/pubsub.ex index 8cca35d80..493e6d6d3 100644 --- a/lib/modules/publishing/pubsub.ex +++ b/lib/modules/publishing/pubsub.ex @@ -353,16 +353,6 @@ defmodule PhoenixKit.Modules.Publishing.PubSub do Manager.broadcast(cache_topic(group_slug), {:cache_changed, group_slug}) end - @doc """ - Broadcasts detailed cache operation info. - """ - def broadcast_cache_operation(group_slug, operation, metadata \\ %{}) do - Manager.broadcast( - cache_topic(group_slug), - {:cache_operation, group_slug, operation, metadata} - ) - end - # ============================================================================ # AI Translation Progress # ============================================================================ @@ -466,50 +456,6 @@ defmodule PhoenixKit.Modules.Publishing.PubSub do ) end - # ============================================================================ - # Bulk Operations Progress - # ============================================================================ - - @doc """ - Returns the topic for bulk operation progress. - """ - def bulk_operation_topic(group_slug) do - "#{@topic_prefix}:#{group_slug}:bulk_operations" - end - - @doc """ - Subscribes to bulk operation progress for a group. - """ - def subscribe_to_bulk_operations(group_slug) do - Manager.subscribe(bulk_operation_topic(group_slug)) - end - - @doc """ - Broadcasts bulk operation progress. - """ - def broadcast_bulk_operation_progress( - group_slug, - operation_id, - operation_type, - completed, - total - ) do - Manager.broadcast( - bulk_operation_topic(group_slug), - {:bulk_operation_progress, operation_id, operation_type, completed, total} - ) - end - - @doc """ - Broadcasts bulk operation completion. - """ - def broadcast_bulk_operation_completed(group_slug, operation_id, operation_type, results) do - Manager.broadcast( - bulk_operation_topic(group_slug), - {:bulk_operation_completed, operation_id, operation_type, results} - ) - end - # ============================================================================ # Form Key Helpers # ============================================================================ @@ -571,26 +517,6 @@ defmodule PhoenixKit.Modules.Publishing.PubSub do # Primary Language Migration Progress # ============================================================================ - @doc """ - Broadcasts that primary language migration has started. - """ - def broadcast_primary_language_migration_started(group_slug, total_count) do - Manager.broadcast( - posts_topic(group_slug), - {:primary_language_migration_started, group_slug, total_count} - ) - end - - @doc """ - Broadcasts primary language migration progress. - """ - def broadcast_primary_language_migration_progress(group_slug, current, total) do - Manager.broadcast( - posts_topic(group_slug), - {:primary_language_migration_progress, group_slug, current, total} - ) - end - @doc """ Broadcasts that primary language migration has completed. """ diff --git a/lib/modules/publishing/shared.ex b/lib/modules/publishing/shared.ex index 1c22a43dc..e65e3a9f1 100644 --- a/lib/modules/publishing/shared.ex +++ b/lib/modules/publishing/shared.ex @@ -224,11 +224,4 @@ defmodule PhoenixKit.Modules.Publishing.Shared do def resolve_db_version(db_post, version_number), do: DBStorage.get_version(db_post.uuid, version_number) - - # ============================================================================ - # Cache Regeneration Check - # ============================================================================ - - @doc false - def should_regenerate_cache?(_), do: true end diff --git a/lib/modules/publishing/stale_fixer.ex b/lib/modules/publishing/stale_fixer.ex index b37b89afb..ec8bdff51 100644 --- a/lib/modules/publishing/stale_fixer.ex +++ b/lib/modules/publishing/stale_fixer.ex @@ -9,6 +9,8 @@ defmodule PhoenixKit.Modules.Publishing.StaleFixer do require Logger + import Ecto.Query, only: [from: 2] + alias PhoenixKit.Modules.Languages alias PhoenixKit.Modules.Publishing.DBStorage alias PhoenixKit.Modules.Publishing.LanguageHelpers @@ -209,9 +211,12 @@ defmodule PhoenixKit.Modules.Publishing.StaleFixer do if effective_mode == "slug" and (is_nil(post.slug) or post.slug == "") do # Post switched to slug mode but has no slug — generate from title or date - slug = generate_slug_for_post(post, ctx) + base_slug = generate_slug_for_post(post, ctx) + + if base_slug != "" do + # Ensure uniqueness by checking if slug exists in the group + slug = ensure_unique_slug(post.group_uuid, base_slug, post.uuid) - if slug != "" do Logger.info( "[Publishing] Generating slug for post #{post.uuid}: #{inspect(slug)} (mode changed to slug)" ) @@ -229,6 +234,24 @@ defmodule PhoenixKit.Modules.Publishing.StaleFixer do end end + # Ensures a slug is unique within a group by appending a UUID suffix if needed + defp ensure_unique_slug(group_uuid, slug, post_uuid) do + conflict = + from(p in PublishingPost, + where: p.group_uuid == ^group_uuid and p.slug == ^slug and p.uuid != ^post_uuid, + select: p.uuid, + limit: 1 + ) + |> PhoenixKit.RepoHelper.repo().one() + + if conflict do + suffix = String.slice(post_uuid || "", 0, 8) + "#{slug}-#{suffix}" + else + slug + end + end + defp generate_slug_for_post(post, ctx) do # Try to get title from the latest version's primary content title = diff --git a/lib/modules/publishing/translation_manager.ex b/lib/modules/publishing/translation_manager.ex index a50d83aa6..a4b679419 100644 --- a/lib/modules/publishing/translation_manager.ex +++ b/lib/modules/publishing/translation_manager.ex @@ -122,9 +122,7 @@ defmodule PhoenixKit.Modules.Publishing.TranslationManager do result = add_language_to_db(group_slug, post_uuid, language_code, version) with {:ok, new_post} <- result do - if Shared.should_regenerate_cache?(new_post) do - ListingCache.regenerate(group_slug) - end + ListingCache.regenerate(group_slug) broadcast_id = new_post.slug || new_post.uuid @@ -190,6 +188,45 @@ defmodule PhoenixKit.Modules.Publishing.TranslationManager do {:error, :not_found} end + @doc """ + Hard-deletes a language's content row from a post. + + Unlike `delete_language` (which archives), this permanently removes the content. + Refuses to delete the last remaining language. + """ + @spec clear_translation(String.t(), String.t(), String.t()) :: :ok | {:error, term()} + def clear_translation(group_slug, post_uuid, language_code) do + with db_post when not is_nil(db_post) <- DBStorage.get_post_by_uuid(post_uuid, [:group]), + db_version when not is_nil(db_version) <- Shared.resolve_db_version(db_post, nil), + content when not is_nil(content) <- + DBStorage.get_content(db_version.uuid, language_code), + :ok <- validate_not_last_content(db_version, language_code) do + repo = PhoenixKit.RepoHelper.repo() + + case repo.delete(content) do + {:ok, _} -> + broadcast_id = db_post.slug || db_post.uuid + ListingCache.regenerate(group_slug) + PublishingPubSub.broadcast_translation_deleted(group_slug, broadcast_id, language_code) + :ok + + {:error, reason} -> + {:error, reason} + end + else + nil -> {:error, :not_found} + {:error, _} = err -> err + end + end + + defp validate_not_last_content(db_version, language_code) do + remaining = + DBStorage.list_contents(db_version.uuid) + |> Enum.reject(&(&1.language == language_code)) + + if remaining == [], do: {:error, :last_language}, else: :ok + end + @doc """ Deletes a specific language translation from a post. @@ -203,14 +240,9 @@ defmodule PhoenixKit.Modules.Publishing.TranslationManager do def delete_language(group_slug, post_uuid, language_code, version \\ nil) do with db_post when not is_nil(db_post) <- DBStorage.get_post_by_uuid(post_uuid, [:group]), db_version when not is_nil(db_version) <- Shared.resolve_db_version(db_post, version), - content when not is_nil(content) <- DBStorage.get_content(db_version.uuid, language_code) do - # Don't delete the last active language - active = - DBStorage.list_contents(db_version.uuid) - |> Enum.reject(&(&1.status == "archived")) - - if length(active) <= 1, do: throw({:error, :last_language}) - + content when not is_nil(content) <- + DBStorage.get_content(db_version.uuid, language_code), + :ok <- validate_not_last_language(db_version) do case DBStorage.update_content(content, %{status: "archived"}) do {:ok, _} -> broadcast_id = db_post.slug || db_post.uuid @@ -223,14 +255,16 @@ defmodule PhoenixKit.Modules.Publishing.TranslationManager do end else nil -> {:error, :not_found} + {:error, _} = err -> err end - catch - {:error, reason} = err -> - Logger.warning( - "[Publishing] delete_language failed for #{group_slug}/#{post_uuid}/#{language_code}: #{inspect(reason)}" - ) + end + + defp validate_not_last_language(db_version) do + active = + DBStorage.list_contents(db_version.uuid) + |> Enum.reject(&(&1.status == "archived")) - err + if length(active) <= 1, do: {:error, :last_language}, else: :ok end @doc """ @@ -259,10 +293,13 @@ defmodule PhoenixKit.Modules.Publishing.TranslationManager do with db_post when not is_nil(db_post) <- db_post, db_version when not is_nil(db_version) <- DBStorage.get_version(db_post.uuid, version), - content when not is_nil(content) <- DBStorage.get_content(db_version.uuid, language) do + content when not is_nil(content) <- DBStorage.get_content(db_version.uuid, language), + :ok <- validate_translation_status_change(db_post, db_version, language, status) do case DBStorage.update_content(content, %{status: status}) do {:ok, _} -> - if status == "published", do: ListingCache.regenerate(group_slug) + ListingCache.regenerate(group_slug) + broadcast_id = db_post.slug || db_post.uuid + PublishingPubSub.broadcast_post_updated(group_slug, %{slug: broadcast_id}) :ok {:error, reason} -> @@ -270,6 +307,7 @@ defmodule PhoenixKit.Modules.Publishing.TranslationManager do end else nil -> {:error, :not_found} + {:error, _} = error -> error end end @@ -277,6 +315,27 @@ defmodule PhoenixKit.Modules.Publishing.TranslationManager do {:error, :invalid_status} end + # Prevents publishing a translation when the primary language content isn't published. + # This avoids the contradiction where set_translation_status allows publishing but + # fix_translation_status_consistency (stale fixer) silently reverts it. + defp validate_translation_status_change(_db_post, _db_version, _language, status) + when status != "published", + do: :ok + + defp validate_translation_status_change(db_post, db_version, language, "published") do + if language == db_post.primary_language do + :ok + else + case DBStorage.get_content(db_version.uuid, db_post.primary_language) do + nil -> + {:error, :primary_not_published} + + primary -> + if primary.status == "published", do: :ok, else: {:error, :primary_not_published} + end + end + end + @doc """ Enqueues an Oban job to translate a post to all enabled languages using AI. diff --git a/lib/modules/publishing/versions.ex b/lib/modules/publishing/versions.ex index b9efab466..12a5c06e6 100644 --- a/lib/modules/publishing/versions.ex +++ b/lib/modules/publishing/versions.ex @@ -142,30 +142,31 @@ defmodule PhoenixKit.Modules.Publishing.Versions do """ @spec publish_version(String.t(), String.t(), integer(), keyword()) :: :ok | {:error, any()} def publish_version(group_slug, post_uuid, version, opts \\ []) do - db_post = DBStorage.get_post_by_uuid(post_uuid, [:group]) - unless db_post, do: throw({:error, :not_found}) - if db_post.status == "trashed", do: throw({:error, :post_trashed}) + case DBStorage.get_post_by_uuid(post_uuid, [:group]) do + nil -> + {:error, :not_found} + + %{status: "trashed"} -> + {:error, :post_trashed} + + db_post -> + do_publish_version(group_slug, db_post, version, opts) + end + end - # Wrap the entire publish operation in a transaction for atomicity + defp do_publish_version(group_slug, db_post, version, opts) do repo = PhoenixKit.RepoHelper.repo() tx_result = repo.transaction(fn -> - # Validate target version exists versions = DBStorage.list_versions(db_post.uuid) unless Enum.any?(versions, &(&1.version_number == version)) do repo.rollback(:version_not_found) end - # Validate primary language content has a title before publishing validate_primary_title!(repo, db_post, version) - - # Set target version to published, archive previously-published versions - # Also update content status to match so public rendering works correctly update_version_statuses!(repo, versions, version) - - # Update post status and published_at update_post_published!(repo, db_post) end) @@ -188,13 +189,6 @@ defmodule PhoenixKit.Modules.Publishing.Versions do {:error, reason} -> {:error, reason} end - catch - {:error, reason} = err -> - Logger.warning( - "[Publishing] publish_version failed for #{group_slug}/#{post_uuid}/v#{version}: #{inspect(reason)}" - ) - - err end @doc """ @@ -235,15 +229,8 @@ defmodule PhoenixKit.Modules.Publishing.Versions do @spec delete_version(String.t(), String.t(), integer()) :: :ok | {:error, term()} def delete_version(group_slug, post_uuid, version) do with db_post when not is_nil(db_post) <- DBStorage.get_post_by_uuid(post_uuid, [:group]), - db_version when not is_nil(db_version) <- DBStorage.get_version(db_post.uuid, version) do - if db_version.status == "published", do: throw({:error, :cannot_delete_live}) - - active = - DBStorage.list_versions(db_post.uuid) - |> Enum.reject(&(&1.status == "archived")) - - if length(active) <= 1, do: throw({:error, :last_version}) - + db_version when not is_nil(db_version) <- DBStorage.get_version(db_post.uuid, version), + :ok <- validate_version_deletable(db_post, db_version) do broadcast_id = db_post.slug || db_post.uuid case DBStorage.update_version(db_version, %{status: "archived"}) do @@ -258,14 +245,21 @@ defmodule PhoenixKit.Modules.Publishing.Versions do end else nil -> {:error, :not_found} + {:error, _} = err -> err end - catch - {:error, reason} = err -> - Logger.warning( - "[Publishing] delete_version failed for #{group_slug}/#{post_uuid}/v#{version}: #{inspect(reason)}" - ) + end - err + defp validate_version_deletable(db_post, db_version) do + cond do + db_version.status == "published" -> + {:error, :cannot_delete_live} + + length(Enum.reject(DBStorage.list_versions(db_post.uuid), &(&1.status == "archived"))) <= 1 -> + {:error, :last_version} + + true -> + :ok + end end @doc false @@ -285,9 +279,13 @@ defmodule PhoenixKit.Modules.Publishing.Versions do # =========================================================================== defp create_version_in_db(group_slug, post_uuid, source_version, _params, opts) do - db_post = DBStorage.get_post_by_uuid(post_uuid, [:group]) - unless db_post, do: throw({:error, :post_not_found}) + case DBStorage.get_post_by_uuid(post_uuid, [:group]) do + nil -> {:error, :post_not_found} + db_post -> do_create_version(group_slug, post_uuid, db_post, source_version, opts) + end + end + defp do_create_version(group_slug, post_uuid, db_post, source_version, opts) do scope = Shared.fetch_option(opts, :scope) created_by_uuid = Shared.resolve_scope_user_uuids(scope) @@ -321,6 +319,7 @@ defmodule PhoenixKit.Modules.Publishing.Versions do case Shared.read_back_post(group_slug, post_uuid, db_post, nil, db_version.version_number) do {:ok, post} -> broadcast_id = db_post.slug || db_post.uuid + ListingCache.regenerate(group_slug) broadcast_version_created(group_slug, broadcast_id, post) {:ok, post} @@ -328,13 +327,6 @@ defmodule PhoenixKit.Modules.Publishing.Versions do err end end - catch - {:error, reason} -> - Logger.warning( - "[Publishing] create_version failed for #{group_slug}/#{post_uuid}: #{inspect(reason)}" - ) - - {:error, reason} end defp update_version_statuses!(repo, versions, target_version) do diff --git a/lib/modules/publishing/web/controller.ex b/lib/modules/publishing/web/controller.ex index 5677ba685..d33f3e6cf 100644 --- a/lib/modules/publishing/web/controller.ex +++ b/lib/modules/publishing/web/controller.ex @@ -144,12 +144,12 @@ defmodule PhoenixKit.Modules.Publishing.Web.Controller do defp extract_group_slug({_, group_slug, _, _}), do: group_slug defp extract_group_slug(_), do: nil - defp group_trashed?(group_slug) do - alias PhoenixKit.Modules.Publishing.DBStorage + defp group_trashed?(nil), do: false - case DBStorage.get_group_by_slug(group_slug) do - nil -> false - group -> group.status == "trashed" + defp group_trashed?(group_slug) do + case Publishing.get_group(group_slug) do + {:ok, group} -> group["status"] == "trashed" + {:error, _} -> false end end diff --git a/lib/modules/publishing/web/controller/fallback.ex b/lib/modules/publishing/web/controller/fallback.ex index 3bda18d49..26a9defd8 100644 --- a/lib/modules/publishing/web/controller/fallback.ex +++ b/lib/modules/publishing/web/controller/fallback.ex @@ -12,7 +12,6 @@ defmodule PhoenixKit.Modules.Publishing.Web.Controller.Fallback do use Gettext, backend: PhoenixKitWeb.Gettext alias PhoenixKit.Modules.Publishing - alias PhoenixKit.Modules.Publishing.DBStorage alias PhoenixKit.Modules.Publishing.Web.Controller.Language alias PhoenixKit.Modules.Publishing.Web.Controller.Listing alias PhoenixKit.Modules.Publishing.Web.HTML, as: PublishingHTML @@ -160,7 +159,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Controller.Fallback do @max_fallback_languages 5 defp try_other_languages(group_slug, post_slug, post, requested_language, default_lang) do - available = post.available_languages || [] + available = post.available_languages # Build priority list: default first, then others (excluding already-tried language) languages_to_try = @@ -173,7 +172,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Controller.Fallback do # Finds a post by its slug using a direct DB query defp find_post_by_slug(group_slug, post_slug) do - case DBStorage.read_post(group_slug, post_slug) do + case Publishing.read_post(group_slug, post_slug) do {:ok, post} -> {:ok, post} {:error, _} -> :not_found end @@ -338,8 +337,8 @@ defmodule PhoenixKit.Modules.Publishing.Web.Controller.Fallback do parsed_time = parse_time(time) if parsed_date && parsed_time do - case DBStorage.read_post_by_datetime(group_slug, parsed_date, parsed_time) do - {:ok, post} -> post.available_languages || [] + case Publishing.read_post_by_datetime(group_slug, parsed_date, parsed_time) do + {:ok, post} -> post.available_languages {:error, _} -> [] end else diff --git a/lib/modules/publishing/web/editor.ex b/lib/modules/publishing/web/editor.ex index 3294384d3..0edfdac8b 100644 --- a/lib/modules/publishing/web/editor.ex +++ b/lib/modules/publishing/web/editor.ex @@ -29,11 +29,8 @@ defmodule PhoenixKit.Modules.Publishing.Web.Editor do alias PhoenixKit.Modules.AI alias PhoenixKit.Modules.Publishing alias PhoenixKit.Modules.Publishing.Constants - alias PhoenixKit.Modules.Publishing.DBStorage - alias PhoenixKit.Modules.Publishing.ListingCache alias PhoenixKit.Modules.Publishing.Metadata alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub - alias PhoenixKit.Modules.Publishing.Shared alias PhoenixKit.Settings alias PhoenixKit.Utils.Routes @@ -670,40 +667,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Editor do language = socket.assigns.current_language post_uuid = post[:uuid] - result = - with db_post when not is_nil(db_post) <- DBStorage.get_post_by_uuid(post_uuid, [:group]), - db_version when not is_nil(db_version) <- Shared.resolve_db_version(db_post, nil), - content when not is_nil(content) <- - DBStorage.get_content(db_version.uuid, language) do - # Don't delete the last language - remaining = - DBStorage.list_contents(db_version.uuid) - |> Enum.reject(&(&1.language == language)) - - if remaining == [] do - {:error, :last_language} - else - repo = PhoenixKit.RepoHelper.repo() - - case repo.delete(content) do - {:ok, _} -> - ListingCache.regenerate(group_slug) - - PublishingPubSub.broadcast_translation_deleted( - group_slug, - db_post.slug || db_post.uuid, - language - ) - - :ok - - {:error, reason} -> - {:error, reason} - end - end - else - nil -> {:error, :not_found} - end + result = Publishing.clear_translation(group_slug, post_uuid, language) case result do :ok -> @@ -1656,7 +1620,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Editor do file_url = Helpers.get_file_url(file_uuid) js_code = - "window.publishingEditorInsertMedia && window.publishingEditorInsertMedia(#{Jason.encode!(file_url || "")}, 'image')" + "window.publishingEditorInsertMedia && window.publishingEditorInsertMedia(#{Jason.encode!(file_url)}, 'image')" { socket diff --git a/lib/modules/publishing/web/editor/persistence.ex b/lib/modules/publishing/web/editor/persistence.ex index e8918b47a..023446f6c 100644 --- a/lib/modules/publishing/web/editor/persistence.ex +++ b/lib/modules/publishing/web/editor/persistence.ex @@ -9,7 +9,6 @@ defmodule PhoenixKit.Modules.Publishing.Web.Editor.Persistence do use Gettext, backend: PhoenixKitWeb.Gettext alias PhoenixKit.Modules.Publishing - alias PhoenixKit.Modules.Publishing.DBStorage alias PhoenixKit.Modules.Publishing.ListingCache alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub alias PhoenixKit.Modules.Publishing.Renderer @@ -75,7 +74,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Editor.Persistence do {:error, :conflicts_with_post_slug} -> # Auto-clear the url_slug from ALL translations of this post cleared_params = Map.put(params, "url_slug", "") - cleared_languages = DBStorage.clear_url_slug_from_post(group_slug, post_slug, url_slug) + cleared_languages = Publishing.clear_url_slug_from_post(group_slug, post_slug, url_slug) notice = if length(cleared_languages) > 1 do @@ -97,7 +96,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Editor.Persistence do {:error, :slug_already_exists} -> # Auto-clear the url_slug from ALL translations of this post cleared_params = Map.put(params, "url_slug", "") - cleared_languages = DBStorage.clear_url_slug_from_post(group_slug, post_slug, url_slug) + cleared_languages = Publishing.clear_url_slug_from_post(group_slug, post_slug, url_slug) notice = if length(cleared_languages) > 1 do diff --git a/lib/modules/publishing/web/index.ex b/lib/modules/publishing/web/index.ex index 85c936b13..db1f65ebd 100644 --- a/lib/modules/publishing/web/index.ex +++ b/lib/modules/publishing/web/index.ex @@ -10,7 +10,6 @@ defmodule PhoenixKit.Modules.Publishing.Web.Index do alias PhoenixKit.Modules.Publishing alias PhoenixKit.Modules.Publishing.Constants - alias PhoenixKit.Modules.Publishing.DBStorage alias PhoenixKit.Modules.Publishing.Web.Editor.Helpers @group_statuses Constants.group_statuses() @@ -73,7 +72,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Index do |> assign(:dashboard_refresh_timer, nil) |> assign(:view_mode, "active") |> assign(:loading, false) - |> assign(:trashed_count, length(DBStorage.list_groups("trashed"))) + |> assign(:trashed_count, length(Publishing.list_groups("trashed"))) {:ok, socket} end @@ -244,7 +243,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Index do view_mode ) - trashed_count = length(DBStorage.list_groups("trashed")) + trashed_count = length(Publishing.list_groups("trashed")) # Resubscribe to any new groups that may have been created Enum.each(groups, fn group -> @@ -262,17 +261,9 @@ defmodule PhoenixKit.Modules.Publishing.Web.Index do defp dashboard_snapshot(_locale, current_user, date_time_settings, view_mode \\ "active") do # Admin side reads from database only - db_groups = DBStorage.list_groups(view_mode) + db_groups = Publishing.list_groups(view_mode) - groups = - Enum.map(db_groups, fn g -> - %{ - "name" => g.name, - "slug" => g.slug, - "mode" => g.mode, - "position" => g.position - } - end) + groups = db_groups insights = Enum.map(db_groups, &build_group_insight(&1, current_user, date_time_settings)) @@ -283,11 +274,13 @@ defmodule PhoenixKit.Modules.Publishing.Web.Index do end defp build_group_insight(db_group, current_user, date_time_settings) do + group_slug = db_group["slug"] + # Use ListingCache when available (sub-microsecond), fall back to DB posts = - case ListingCache.read(db_group.slug) do + case ListingCache.read(group_slug) do {:ok, cached_posts} -> cached_posts - {:error, _} -> DBStorage.list_posts_with_metadata(db_group.slug) + {:error, _} -> Publishing.list_posts(group_slug) end status_counts = Enum.frequencies_by(posts, &Map.get(&1[:metadata] || %{}, :status, "draft")) @@ -304,15 +297,17 @@ defmodule PhoenixKit.Modules.Publishing.Web.Index do global_primary = Publishing.get_primary_language() primary_lang_status = - DBStorage.count_primary_language_status_from_posts(posts, global_primary) + Publishing.count_primary_language_status(posts, global_primary) lang_migration_count = - primary_lang_status.needs_backfill + primary_lang_status.needs_migration + if primary_lang_status, + do: primary_lang_status.needs_backfill + primary_lang_status.needs_migration, + else: 0 %{ - name: db_group.name, - slug: db_group.slug, - mode: db_group.mode, + name: db_group["name"], + slug: group_slug, + mode: db_group["mode"], posts_count: length(posts), published_count: Map.get(status_counts, "published", 0), draft_count: Map.get(status_counts, "draft", 0), diff --git a/lib/modules/publishing/web/index.html.heex b/lib/modules/publishing/web/index.html.heex index 4adec148b..0627b95fb 100644 --- a/lib/modules/publishing/web/index.html.heex +++ b/lib/modules/publishing/web/index.html.heex @@ -208,7 +208,7 @@ <% end %> -
+
<%= if @view_mode == "active" do %> <.link navigate={Routes.path("/admin/publishing/#{insight.slug}")} diff --git a/lib/modules/publishing/web/listing.ex b/lib/modules/publishing/web/listing.ex index 48b49bc99..ae11c89c3 100644 --- a/lib/modules/publishing/web/listing.ex +++ b/lib/modules/publishing/web/listing.ex @@ -8,9 +8,8 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do require Logger alias PhoenixKit.Modules.Publishing - alias PhoenixKit.Modules.Publishing.DBStorage + alias PhoenixKit.Modules.Publishing.LanguageHelpers alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub - alias PhoenixKit.Modules.Publishing.Renderer alias PhoenixKit.Modules.Publishing.StaleFixer alias PhoenixKit.Modules.Publishing.Web.Editor.Helpers alias PhoenixKit.Modules.Publishing.Web.HTML, as: PublishingHTML @@ -76,8 +75,8 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do if connected?(socket) and new_group_slug do Task.Supervisor.start_child(PhoenixKit.TaskSupervisor, fn -> try do - active = DBStorage.list_posts(new_group_slug) - trashed = DBStorage.list_posts(new_group_slug, "trashed") + active = Publishing.list_raw_posts(new_group_slug) + trashed = Publishing.list_raw_posts(new_group_slug, "trashed") Enum.each(active ++ trashed, &StaleFixer.fix_stale_post/1) rescue e -> @@ -249,13 +248,13 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do mode = socket.assigns.post_view_mode # Always load all non-trashed posts for counting - all_posts = DBStorage.list_posts_with_metadata(group_slug) - trashed_count = length(DBStorage.list_posts(group_slug, "trashed")) + all_posts = Publishing.list_posts(group_slug) + trashed_count = length(Publishing.list_raw_posts(group_slug, "trashed")) posts = case mode do "trashed" -> - DBStorage.list_posts_with_metadata(group_slug, "trashed") + Publishing.list_posts_by_status(group_slug, "trashed") status -> Enum.filter(all_posts, fn p -> p[:metadata] && p.metadata.status == status end) @@ -483,14 +482,14 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do socket group_slug -> - all_posts = DBStorage.list_posts_with_metadata(group_slug) - trashed_count = length(DBStorage.list_posts(group_slug, "trashed")) + all_posts = Publishing.list_posts(group_slug) + trashed_count = length(Publishing.list_raw_posts(group_slug, "trashed")) mode = socket.assigns.post_view_mode filtered_posts = case mode do "trashed" -> - DBStorage.list_posts_with_metadata(group_slug, "trashed") + Publishing.list_posts_by_status(group_slug, "trashed") status -> Enum.filter(all_posts, fn p -> p[:metadata] && p.metadata.status == status end) @@ -677,13 +676,13 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do all_posts = case group_slug do nil -> [] - slug -> DBStorage.list_posts_with_metadata(slug) + slug -> Publishing.list_posts(slug) end trashed_count = case group_slug do nil -> 0 - slug -> length(DBStorage.list_posts(slug, "trashed")) + slug -> length(Publishing.list_raw_posts(slug, "trashed")) end status_counts = build_status_counts(all_posts, trashed_count) @@ -722,7 +721,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do defp filter_posts_for_mode(group_slug, mode, all_posts) do case mode do "trashed" -> - DBStorage.list_posts_with_metadata(group_slug, "trashed") + Publishing.list_posts_by_status(group_slug, "trashed") status -> Enum.filter(all_posts, fn p -> p[:metadata] && p.metadata.status == status end) @@ -730,10 +729,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do end defp load_db_groups do - DBStorage.list_groups() - |> Enum.map(fn g -> - %{"name" => g.name, "slug" => g.slug, "mode" => g.mode, "position" => g.position} - end) + Publishing.list_groups() end defp redirect_if_missing(%{assigns: %{current_group: nil}} = socket) do @@ -913,41 +909,18 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do scope = socket.assigns[:phoenix_kit_current_scope] group_slug = socket.assigns.group_slug - case Publishing.read_post_by_uuid(post_uuid) do - {:ok, post} -> - primary_language = post[:primary_language] || Publishing.get_primary_language() - is_primary_language = post.language == primary_language - - case Publishing.update_post(group_slug, post, %{"status" => new_status}, %{ - scope: scope, - is_primary_language: is_primary_language - }) do - {:ok, updated_post} -> - invalidate_post_cache(group_slug, updated_post) - - PublishingPubSub.broadcast_post_status_changed(group_slug, updated_post) - - {:noreply, - socket - |> put_flash(:info, gettext("Status updated to %{status}", status: new_status)) - |> reload_current_view()} - - {:error, _reason} -> - {:noreply, put_flash(socket, :error, gettext("Failed to update status"))} - end + case Publishing.change_post_status(group_slug, post_uuid, new_status, scope: scope) do + {:ok, _updated_post} -> + {:noreply, + socket + |> put_flash(:info, gettext("Status updated to %{status}", status: new_status)) + |> reload_current_view()} {:error, _reason} -> - {:noreply, put_flash(socket, :error, gettext("Post not found"))} + {:noreply, put_flash(socket, :error, gettext("Failed to update status"))} end end - defp invalidate_post_cache(group_slug, post) do - identifier = post[:uuid] || post.slug - - # Invalidate the render cache for this post - Renderer.invalidate_cache(group_slug, identifier, post.language) - end - @doc """ Builds language data for the publishing_language_switcher component. Returns a list of language maps with status, enabled flag, known flag, and metadata. @@ -963,47 +936,13 @@ defmodule PhoenixKit.Modules.Publishing.Web.Listing do _current_locale, primary_language \\ nil ) do - primary_lang = - primary_language || post[:primary_language] || Publishing.get_primary_language() - - all_languages = - Publishing.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 - - defp build_language_entry(lang_code, post, enabled_languages, primary_lang) do - lang_info = Publishing.get_language_info(lang_code) - available = post.available_languages || [] - content_exists = lang_code in available - - # Status comes from the post level (primary language), not per-translation - post_status = post[:metadata] && post.metadata.status - - %{ - code: lang_code, - display_code: Publishing.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: Publishing.language_enabled?(lang_code, enabled_languages), - known: lang_info != nil, - is_primary: lang_code == primary_lang, - uuid: post[:uuid] - } + LanguageHelpers.build_post_languages(post, enabled_languages, primary_language) end defp primary_language_status_from_posts([]), do: nil defp primary_language_status_from_posts(posts) do global_primary = Publishing.get_primary_language() - DBStorage.count_primary_language_status_from_posts(posts, global_primary) + Publishing.count_primary_language_status(posts, global_primary) end end diff --git a/lib/modules/publishing/web/settings.ex b/lib/modules/publishing/web/settings.ex index ec7b5d3f7..12957101b 100644 --- a/lib/modules/publishing/web/settings.ex +++ b/lib/modules/publishing/web/settings.ex @@ -6,7 +6,6 @@ defmodule PhoenixKit.Modules.Publishing.Web.Settings do use Gettext, backend: PhoenixKitWeb.Gettext alias PhoenixKit.Modules.Publishing - alias PhoenixKit.Modules.Publishing.DBStorage alias PhoenixKit.Modules.Publishing.ListingCache alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub alias PhoenixKit.Modules.Publishing.Renderer @@ -184,14 +183,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Settings do end defp db_groups_to_maps do - DBStorage.list_groups() - |> Enum.map(fn g -> - %{ - "name" => g.name, - "slug" => g.slug, - "mode" => g.mode - } - end) + Publishing.list_groups() end defp cache_toggle_message(cache_type, enabled) do @@ -223,7 +215,7 @@ defmodule PhoenixKit.Modules.Publishing.Web.Settings do post_count = case :persistent_term.get(ListingCache.persistent_term_key(group_slug), :not_found) do - :not_found -> length(DBStorage.list_posts(group_slug)) + :not_found -> length(Publishing.list_posts(group_slug)) posts -> length(posts) end diff --git a/lib/modules/publishing/workers/migrate_primary_language_worker.ex b/lib/modules/publishing/workers/migrate_primary_language_worker.ex index ce844b5c9..72865049d 100644 --- a/lib/modules/publishing/workers/migrate_primary_language_worker.ex +++ b/lib/modules/publishing/workers/migrate_primary_language_worker.ex @@ -38,9 +38,6 @@ defmodule PhoenixKit.Modules.Publishing.Workers.MigratePrimaryLanguageWorker do alias PhoenixKit.Modules.Publishing.ListingCache alias PhoenixKit.Modules.Publishing.PubSub, as: PublishingPubSub - # Batch size for progress updates (broadcast every N posts) - @progress_batch_size 5 - @impl Oban.Worker def perform(%Oban.Job{args: args}) do group_slug = Map.fetch!(args, "group_slug") @@ -58,14 +55,10 @@ defmodule PhoenixKit.Modules.Publishing.Workers.MigratePrimaryLanguageWorker do Logger.info("[MigratePrimaryLanguageWorker] No posts need migration for #{group_slug}") :ok else - # Broadcast start - PublishingPubSub.broadcast_primary_language_migration_started(group_slug, total) - - # Process posts with progress updates + # Process posts {success_count, error_count} = posts - |> Enum.with_index(1) - |> Enum.reduce({0, 0}, fn {post, index}, {successes, errors} -> + |> Enum.reduce({0, 0}, fn post, {successes, errors} -> post_uuid = post[:uuid] result = @@ -75,15 +68,6 @@ defmodule PhoenixKit.Modules.Publishing.Workers.MigratePrimaryLanguageWorker do {:error, :no_uuid} end - # Broadcast progress every batch - if rem(index, @progress_batch_size) == 0 or index == total do - PublishingPubSub.broadcast_primary_language_migration_progress( - group_slug, - index, - total - ) - end - case result do :ok -> {successes + 1, errors} {:error, _} -> {successes, errors + 1}