From 6f4a7a2f9ac291dd3a0ae3908867045703d02f8e Mon Sep 17 00:00:00 2001 From: timujeen Date: Tue, 17 Mar 2026 14:18:46 +0000 Subject: [PATCH 1/3] Fix phoenix_kit.status showing V01 instead of actual migration version Repo was not properly started in --no-start context: status task and runtime version detection both failed to start the Repo process with database config, causing all queries to fail silently and fall back to version 0/1. --- lib/mix/tasks/phoenix_kit.status.ex | 6 +++- lib/phoenix_kit/migrations/postgres.ex | 46 ++++++++++++++++++++------ 2 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/mix/tasks/phoenix_kit.status.ex b/lib/mix/tasks/phoenix_kit.status.ex index 32ca0b360..bfb1243db 100644 --- a/lib/mix/tasks/phoenix_kit.status.ex +++ b/lib/mix/tasks/phoenix_kit.status.ex @@ -532,11 +532,15 @@ defmodule Mix.Tasks.PhoenixKit.Status do # Ensure repo is properly started for database operations. # Since we use --no-start, the repo may not be running yet. + # We start repo with its application config so it gets the connection params. defp ensure_repo_started(repo) do if repo_available?(repo) do :ok else - case repo.start_link([]) do + parent_app = Mix.Project.config()[:app] + config = Application.get_env(parent_app, repo, []) + + case repo.start_link(config) do {:ok, _pid} -> :ok {:error, {:already_started, _pid}} -> :ok {:error, reason} -> {:error, "Failed to start #{inspect(repo)}: #{inspect(reason)}"} diff --git a/lib/phoenix_kit/migrations/postgres.ex b/lib/phoenix_kit/migrations/postgres.ex index 33d32387a..86d9b9747 100644 --- a/lib/phoenix_kit/migrations/postgres.ex +++ b/lib/phoenix_kit/migrations/postgres.ex @@ -1097,22 +1097,46 @@ defmodule PhoenixKit.Migrations.Postgres do defp ensure_repo_loaded?(_), do: false - # Ensure repo is properly started for database operations - # Note: For Mix tasks, the application should already be started + # Ensure repo is properly started for database operations. + # In --no-start context, Repo process may not be running yet. defp ensure_repo_started(repo) do - # Try Mix.Ecto.ensure_repo if available - if Code.ensure_loaded?(Mix.Ecto) do - Mix.Ecto.ensure_repo(repo, []) + if Process.whereis(repo) != nil do :ok else - # Basic check if repo is available - if Code.ensure_loaded?(repo) && function_exported?(repo, :__adapter__, 0) do - :ok - else - {:error, "Repository #{inspect(repo)} is not available"} - end + start_repo_with_config(repo) end rescue error -> {:error, "Failed to start repo: #{inspect(error)}"} end + + defp start_repo_with_config(repo) do + # Try Mix.Ecto.ensure_repo first (handles config resolution) + if Code.ensure_loaded?(Mix.Ecto) do + Mix.Ecto.ensure_repo(repo, []) + + # ensure_repo loads but doesn't start — start the process + if Process.whereis(repo) == nil do + do_start_repo(repo) + else + :ok + end + else + do_start_repo(repo) + end + end + + defp do_start_repo(repo) do + # Get config from parent app's application env + app = + if Code.ensure_loaded?(Mix) and function_exported?(Mix.Project, :config, 0), + do: Mix.Project.config()[:app] + + config = if app, do: Application.get_env(app, repo, []), else: [] + + case repo.start_link(config) do + {:ok, _pid} -> :ok + {:error, {:already_started, _pid}} -> :ok + {:error, reason} -> {:error, "Failed to start #{inspect(repo)}: #{inspect(reason)}"} + end + end end From 54b9b8994e7568b44a952ca9972a71a8f14dce72 Mon Sep 17 00:00:00 2001 From: timujeen Date: Tue, 17 Mar 2026 21:52:54 +0000 Subject: [PATCH 2/3] Add lastmod to sitemap router-discovered and static entries Router-discovered pages now use the beam file modification time of the actual LiveView module (extracted from route metadata) as an approximate lastmod date. Static and custom entries use the current date of sitemap generation. In dev mode, parent app modules may lack beam files (in-memory compilation), so lastmod gracefully falls back to nil. --- .../sitemap/sources/router_discovery.ex | 39 ++++++++++++++++++- lib/modules/sitemap/sources/static.ex | 4 +- 2 files changed, 40 insertions(+), 3 deletions(-) diff --git a/lib/modules/sitemap/sources/router_discovery.ex b/lib/modules/sitemap/sources/router_discovery.ex index 9d144b869..9f02561a9 100644 --- a/lib/modules/sitemap/sources/router_discovery.ex +++ b/lib/modules/sitemap/sources/router_discovery.ex @@ -322,7 +322,7 @@ defmodule PhoenixKit.Modules.Sitemap.Sources.RouterDiscovery do UrlEntry.new(%{ loc: url, - lastmod: nil, + lastmod: module_lastmod(route), changefreq: "weekly", priority: 0.5, title: title, @@ -331,6 +331,43 @@ defmodule PhoenixKit.Modules.Sitemap.Sources.RouterDiscovery do }) end + # Approximate lastmod from the beam file modification time of the route's LiveView module. + # Falls back to the plug module if no LiveView metadata is found. + defp module_lastmod(route) do + module = extract_liveview_module(route) || route.plug + beam_file_mtime(module) + rescue + _ -> nil + end + + defp extract_liveview_module(route) do + case route.metadata do + %{phoenix_live_view: {module, _, _, _}} when is_atom(module) -> module + %{phoenix_live_view: {module, _, _}} when is_atom(module) -> module + _ -> nil + end + rescue + _ -> nil + end + + defp beam_file_mtime(module) when is_atom(module) do + case :code.which(module) do + beam_path when is_list(beam_path) -> + case File.stat(List.to_string(beam_path)) do + {:ok, %{mtime: mtime}} -> + NaiveDateTime.from_erl!(mtime) |> DateTime.from_naive!("Etc/UTC") + + _ -> + nil + end + + _ -> + nil + end + end + + defp beam_file_mtime(_), do: nil + defp build_url(path, nil) do base = Settings.get_setting("site_url", "") normalized_base = String.trim_trailing(base, "/") diff --git a/lib/modules/sitemap/sources/static.ex b/lib/modules/sitemap/sources/static.ex index 765da3374..636032418 100644 --- a/lib/modules/sitemap/sources/static.ex +++ b/lib/modules/sitemap/sources/static.ex @@ -199,7 +199,7 @@ defmodule PhoenixKit.Modules.Sitemap.Sources.Static do UrlEntry.new(%{ loc: url, - lastmod: nil, + lastmod: Date.utc_today(), changefreq: Map.get(config, "changefreq", "weekly"), priority: Map.get(config, "priority", 0.5), title: Map.get(config, "title", path), @@ -224,7 +224,7 @@ defmodule PhoenixKit.Modules.Sitemap.Sources.Static do UrlEntry.new(%{ loc: url, - lastmod: nil, + lastmod: Date.utc_today(), changefreq: Map.get(config, "changefreq", "weekly"), priority: Map.get(config, "priority", 0.5), title: Map.get(config, "title", path), From 893b47a2e0dfc36b47ff6220fa8aff2ef73b85fa Mon Sep 17 00:00:00 2001 From: timujeen Date: Tue, 17 Mar 2026 22:10:56 +0000 Subject: [PATCH 3/3] Reduce cyclomatic complexity of update_meta handle_event Extract post update logic into update_post_from_form/3 to bring cyclomatic complexity from 13 to within the 12 limit. --- lib/modules/publishing/web/editor.ex | 54 ++++++++++++++-------------- 1 file changed, 28 insertions(+), 26 deletions(-) diff --git a/lib/modules/publishing/web/editor.ex b/lib/modules/publishing/web/editor.ex index de7b29999..ca06b96f2 100644 --- a/lib/modules/publishing/web/editor.ex +++ b/lib/modules/publishing/web/editor.ex @@ -527,33 +527,10 @@ defmodule PhoenixKit.Modules.Publishing.Web.Editor do ) has_changes = Forms.dirty?(socket_with_slug.assigns.post, new_form, socket.assigns.content) - language = Helpers.editor_language(socket.assigns) - new_status = new_form["status"] - - # Update both metadata.status and language_statuses for the current language - # This ensures the language switcher reflects the user's selection immediately - current_language_statuses = Map.get(socket.assigns.post, :language_statuses, %{}) - updated_language_statuses = Map.put(current_language_statuses, language, new_status) - - # Update post with current form values for accurate public URL - form_slug = new_form["slug"] - form_url_slug = new_form["url_slug"] - - updated_post = - socket.assigns.post - |> Map.put(:metadata, Map.merge(socket.assigns.post.metadata, %{status: new_status})) - |> Map.put(:language_statuses, updated_language_statuses) - |> then(fn p -> - if form_slug && form_slug != "", do: Map.put(p, :slug, form_slug), else: p - end) - |> then(fn p -> - if form_url_slug && form_url_slug != "", - do: Map.put(p, :url_slug, form_url_slug), - else: p - end) - - public_url = Helpers.build_public_url(updated_post, language) + + {updated_post, public_url} = + update_post_from_form(socket.assigns.post, new_form, language) socket = socket_with_slug @@ -1052,6 +1029,31 @@ defmodule PhoenixKit.Modules.Publishing.Web.Editor do handle_event("attempt_cancel", %{}, socket) end + # Update post struct with current form values for accurate public URL and status display + defp update_post_from_form(post, form, language) do + new_status = form["status"] + current_language_statuses = Map.get(post, :language_statuses, %{}) + updated_language_statuses = Map.put(current_language_statuses, language, new_status) + + form_slug = form["slug"] + form_url_slug = form["url_slug"] + + updated_post = + post + |> Map.put(:metadata, Map.merge(post.metadata, %{status: new_status})) + |> Map.put(:language_statuses, updated_language_statuses) + |> then(fn p -> + if form_slug && form_slug != "", do: Map.put(p, :slug, form_slug), else: p + end) + |> then(fn p -> + if form_url_slug && form_url_slug != "", + do: Map.put(p, :url_slug, form_url_slug), + else: p + end) + + {updated_post, Helpers.build_public_url(updated_post, language)} + end + # ============================================================================ # Handle Info - Autosave # ============================================================================