add page url slug management functions

Context functions for custom page URLs:
- list_pages_with_custom_urls/0 for admin overview
- get_published_page_by_effective_url/1 for routing lookups
- update_page_url_slug/2 with automatic redirect creation

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
jamey 2026-04-01 00:35:24 +01:00
parent f56e04390c
commit 1004865013

View File

@ -90,6 +90,28 @@ defmodule Berrypod.Pages do
list_system_pages() ++ list_custom_pages()
end
@doc "Lists pages that have a custom URL slug set. Used by Routes cache."
def list_pages_with_custom_urls do
from(p in Page, where: not is_nil(p.url_slug))
|> Repo.all()
end
@doc "Gets a published page by its effective URL (url_slug or slug)."
def get_published_page_by_effective_url(url) do
# First check for custom url_slug match
case Repo.one(from p in Page, where: p.url_slug == ^url and p.published == true) do
nil ->
# Fall back to slug match for custom pages
Repo.one(
from p in Page,
where: p.slug == ^url and p.type == "custom" and p.published == true
)
page ->
page
end
end
# ── Write ─────────────────────────────────────────────────────────
@doc """
@ -272,7 +294,91 @@ defmodule Berrypod.Pages do
meta_description: page.meta_description,
show_in_nav: page.show_in_nav,
nav_label: page.nav_label,
nav_position: page.nav_position
nav_position: page.nav_position,
url_slug: page.url_slug
}
end
# ── URL customisation ────────────────────────────────────────────
@doc """
Updates a page's custom URL slug. Creates a redirect from the old
effective URL to the new one, and invalidates the R module cache.
Pass nil or empty string to clear the custom URL.
"""
def update_page_url_slug(%Page{} = page, url_slug) do
old_effective_url = Page.effective_url(page)
# Normalise empty string to nil
url_slug = if url_slug == "", do: nil, else: url_slug
changeset_fn =
if Page.system_slug?(page.slug),
do: &Page.system_changeset/2,
else: &Page.custom_changeset/2
result =
page
|> changeset_fn.(%{url_slug: url_slug})
|> Repo.update()
case result do
{:ok, updated} ->
new_effective_url = Page.effective_url(updated)
# Create redirect if URL changed
if old_effective_url != new_effective_url do
# Delete any stale redirect FROM the new URL (it's now a live page)
delete_stale_redirect("/#{new_effective_url}")
# Create redirect from old URL to new URL
Berrypod.Redirects.create_auto(%{
from_path: "/#{old_effective_url}",
to_path: "/#{new_effective_url}",
source: "auto_slug_change"
})
end
# Invalidate caches synchronously to avoid race conditions
PageCache.invalidate(page.slug)
BerrypodWeb.R.invalidate_all_sync()
{:ok, updated}
error ->
error
end
end
def update_page_url_slug(slug, url_slug) when is_binary(slug) do
case get_page_struct(slug) do
nil ->
# Page doesn't exist in DB - create it from defaults if it's a system page
if Page.system_slug?(slug) do
defaults = Defaults.for_slug(slug)
case save_page(slug, defaults) do
{:ok, page} -> update_page_url_slug(page, url_slug)
error -> error
end
else
{:error, :not_found}
end
page ->
update_page_url_slug(page, url_slug)
end
end
# When a URL becomes a live page, delete any redirect from that URL
defp delete_stale_redirect(path) do
case Repo.one(from r in Berrypod.Redirects.Redirect, where: r.from_path == ^path) do
%Berrypod.Redirects.Redirect{} = redirect ->
Berrypod.Redirects.delete_redirect(redirect)
nil ->
:ok
end
end
end