Current section

Files

Jump to
phoenix_kit_publishing lib phoenix_kit_publishing web controller slug_resolution.ex
Raw

lib/phoenix_kit_publishing/web/controller/slug_resolution.ex

defmodule PhoenixKit.Modules.Publishing.Web.Controller.SlugResolution do
@moduledoc """
URL slug resolution for the publishing controller.
Handles resolving URL slugs to internal slugs, including:
- Per-language custom URL slugs
- Previous URL slugs for 301 redirects
- DB-based slug lookups
"""
alias PhoenixKit.Modules.Languages.DialectMapper
alias PhoenixKit.Modules.Publishing
alias PhoenixKit.Modules.Publishing.Web.Controller.Language
alias PhoenixKit.Modules.Publishing.Web.HTML, as: PublishingHTML
# ============================================================================
# URL Slug Resolution
# ============================================================================
@doc """
Resolves URL slug to internal slug using cache/DB.
Returns:
- `{:redirect, url}` for 301 redirect to new URL
- `{:ok, identifier}` for resolved internal slug
- `:passthrough` for direct use
"""
def resolve_url_slug(group_slug, {:slug, url_slug}, language) do
# Resolve base language codes (de, en) to stored dialect codes (de-DE, en-US)
# before DB queries, since content rows store full BCP-47 dialect codes
db_language = resolve_language_for_db(language)
case Publishing.find_by_url_slug(group_slug, db_language, url_slug) do
{:ok, cached_post} ->
internal_slug = cached_post.slug
if internal_slug == url_slug do
# URL slug matches internal slug - no resolution needed
:passthrough
else
# URL slug differs from internal slug - use resolved identifier
{:ok, {:slug, internal_slug}}
end
{:error, :not_found} ->
# Not found in current slugs - check previous slugs for 301 redirect
case Publishing.find_by_previous_url_slug(group_slug, db_language, url_slug) do
{:ok, cached_post} ->
# Found in previous slugs - redirect to current URL
current_url_slug =
Map.get(cached_post[:language_slugs] || %{}, db_language, cached_post.slug)
redirect_url =
build_post_redirect_url(group_slug, cached_post, language, current_url_slug)
{:redirect, redirect_url}
{:error, _} ->
:passthrough
end
end
end
# Non-slug modes pass through directly
def resolve_url_slug(_group_slug, _identifier, _language), do: :passthrough
@doc """
Resolves a URL slug to the internal post slug.
Used by versioned URL handler and other places that need the internal slug.
"""
def resolve_url_slug_to_internal(group_slug, url_slug, language) do
db_language = resolve_language_for_db(language)
case Publishing.find_by_url_slug(group_slug, db_language, url_slug) do
{:ok, cached_post} ->
cached_post.slug || cached_post[:slug]
{:error, _} ->
# Not found in cache/DB - use as-is
url_slug
end
end
# ============================================================================
# Redirect URL Building
# ============================================================================
@doc """
Builds redirect URL for 301 redirects from cached post data.
"""
def build_post_redirect_url(group_slug, cached_post, language, url_slug) do
# Build post struct with minimal fields needed for URL generation
post = %{
slug: cached_post.slug,
url_slug: url_slug,
mode: cached_post.mode,
date: cached_post.date,
time: cached_post.time,
language_slugs: cached_post.language_slugs
}
PublishingHTML.build_post_url(group_slug, post, language)
end
# ============================================================================
# Language Resolution
# ============================================================================
# Resolves a URL language code to the stored dialect code for DB queries.
# Resolve language for DB lookup. If the language is an enabled code, use it directly.
# Only resolve base codes to dialects when the base itself isn't enabled.
defp resolve_language_for_db(language) do
enabled = Language.get_enabled_languages()
cond do
language in enabled ->
language
Language.base_code?(language) ->
Language.find_dialect_for_base(language, enabled) ||
DialectMapper.base_to_dialect(language)
true ->
language
end
end
end