Packages

phoenix_kit

1.7.38
1.7.207 1.7.206 1.7.205 1.7.204 1.7.203 1.7.202 1.7.201 1.7.200 1.7.199 1.7.198 1.7.197 1.7.196 1.7.194 1.7.193 1.7.192 1.7.191 1.7.190 1.7.189 1.7.187 1.7.186 1.7.185 1.7.184 1.7.183 1.7.182 1.7.181 1.7.180 1.7.179 1.7.178 1.7.177 1.7.176 1.7.175 1.7.174 1.7.173 1.7.172 1.7.171 1.7.170 1.7.169 1.7.168 1.7.167 1.7.166 1.7.165 1.7.164 1.7.162 1.7.161 1.7.160 1.7.159 1.7.157 1.7.156 1.7.155 1.7.154 1.7.153 1.7.152 1.7.151 1.7.150 1.7.149 1.7.146 1.7.145 1.7.144 1.7.143 1.7.138 1.7.133 1.7.132 1.7.131 1.7.130 1.7.128 1.7.126 1.7.125 1.7.121 1.7.120 1.7.119 1.7.118 1.7.117 1.7.116 1.7.115 1.7.114 1.7.113 1.7.112 1.7.111 1.7.110 1.7.109 1.7.108 1.7.107 1.7.106 1.7.105 1.7.104 1.7.103 1.7.102 1.7.101 1.7.100 1.7.99 1.7.98 1.7.97 1.7.96 1.7.95 1.7.94 1.7.93 1.7.92 1.7.91 1.7.90 1.7.89 1.7.88 1.7.87 1.7.86 1.7.85 1.7.84 1.7.83 1.7.82 1.7.81 1.7.80 1.7.79 1.7.78 1.7.77 1.7.76 1.7.75 1.7.74 1.7.71 1.7.70 1.7.69 1.7.66 1.7.65 1.7.64 1.7.63 1.7.62 1.7.61 1.7.59 1.7.58 1.7.57 1.7.56 1.7.55 1.7.54 1.7.53 1.7.52 1.7.51 1.7.49 1.7.44 1.7.43 1.7.42 1.7.41 1.7.39 1.7.38 1.7.37 1.7.36 1.7.34 1.7.33 1.7.31 1.7.30 1.7.29 1.7.28 1.7.27 1.7.26 1.7.25 1.7.24 1.7.23 1.7.22 1.7.21 1.7.20 1.7.19 1.7.18 1.7.17 1.7.16 1.7.15 1.7.14 1.7.13 1.7.12 1.7.11 1.7.10 1.7.9 1.7.8 1.7.7 1.7.6 1.7.5 1.7.4 1.7.3 1.7.2 1.7.1 1.7.0 1.6.20 1.6.19 1.6.18 1.6.17 1.6.16 1.6.15 1.6.14 1.6.13 1.6.12 1.6.11 1.6.10 1.6.9 1.6.8 1.6.7 1.6.6 1.6.5 1.6.4 1.6.3 1.5.2 1.5.1 1.5.0 1.4.9 1.4.8 1.4.7 1.4.6 1.4.5 1.4.4 1.4.3 1.4.2 1.4.1 1.4.0 1.3.2 1.3.1 1.3.0 1.2.10 1.2.9 1.2.8 1.2.7 1.2.5 1.2.4 1.2.2 1.2.1 1.2.0 1.1.0 1.0.0

A foundation for building Elixir Phoenix apps — SaaS, social networks, ERP systems, marketplaces, and more

Current section

Files

Jump to
phoenix_kit lib modules publishing web controller.ex
Raw

lib/modules/publishing/web/controller.ex

defmodule PhoenixKit.Modules.Publishing.Web.Controller do
@moduledoc """
Public blog post display controller.
Handles public-facing routes for viewing published blog posts with multi-language support.
URL patterns:
/:language/:blog_slug/:post_slug - Slug mode post
/:language/:blog_slug/:date/:time - Timestamp mode post
/:language/:blog_slug - Blog listing
## Architecture
This controller delegates to specialized submodules:
- `Routing` - URL path parsing and segment building
- `Language` - Language detection and resolution
- `SlugResolution` - URL slug resolution and redirects
- `PostFetching` - Post retrieval from cache/filesystem
- `Listing` - Blog listing rendering and pagination
- `PostRendering` - Post rendering and version handling
- `Translations` - Translation link building
- `Fallback` - 404 handling and smart fallback chain
"""
use PhoenixKitWeb, :controller
use Gettext, backend: PhoenixKitWeb.Gettext
alias PhoenixKit.Modules.Publishing
alias PhoenixKit.Modules.Publishing.Web.Controller.Fallback
alias PhoenixKit.Modules.Publishing.Web.Controller.Language
alias PhoenixKit.Modules.Publishing.Web.Controller.Listing
alias PhoenixKit.Modules.Publishing.Web.Controller.PostRendering
alias PhoenixKit.Modules.Publishing.Web.Controller.Routing
alias PhoenixKit.Settings
# ============================================================================
# Main Entry Points
# ============================================================================
@doc """
Displays a blog post, blog listing, or all blogs overview.
Path parsing determines which action to take:
- [] -> Invalid request (no blog specified)
- [blog_slug] -> Blog listing
- [blog_slug, post_slug] -> Slug mode post
- [blog_slug, date] -> Date-only timestamp (resolves to single post or first post)
- [blog_slug, date, time] -> Timestamp mode post
"""
def show(conn, %{"language" => language_param} = params) do
# Detect if 'language' param is actually a language code or a blog slug
# This allows the same route to work for both single and multi-language setups
{language, adjusted_params} = Language.detect_language_or_blog(language_param, params)
conn = assign(conn, :current_language, language)
if Publishing.enabled?() and public_enabled?() do
case Routing.build_segments(adjusted_params) do
[] ->
handle_not_found(conn, :invalid_path)
segments ->
handle_parsed_path(conn, Routing.parse_path(segments), language)
end
else
handle_not_found(conn, :module_disabled)
end
end
# Fallback for routes without language parameter
# This handles the non-localized route where :blog might actually be a language code
def show(conn, params) do
if Publishing.enabled?() and public_enabled?() do
# Check if the first segment (blog) is actually a language with content
case Language.detect_language_in_blog_param(params) do
{:language_detected, language, adjusted_params} ->
# First segment was a language code with content - use localized logic
conn = assign(conn, :current_language, language)
handle_localized_request(conn, language, adjusted_params)
:not_a_language ->
# First segment is a blog slug - use default language
language = Language.get_default_language()
conn = assign(conn, :current_language, language)
handle_non_localized_request(conn, language, params)
end
else
handle_not_found(conn, :module_disabled)
end
end
# ============================================================================
# Request Handlers
# ============================================================================
# Handles request after language has been detected
defp handle_localized_request(conn, language, params) do
case Routing.build_segments(params) do
[] ->
handle_not_found(conn, :invalid_path)
segments ->
handle_parsed_path(conn, Routing.parse_path(segments), language)
end
end
# Handles non-localized request with default language
defp handle_non_localized_request(conn, language, params) do
case Routing.build_segments(params) do
[] ->
handle_not_found(conn, :invalid_path)
segments ->
handle_parsed_path(conn, Routing.parse_path(segments), language)
end
end
# Dispatches to appropriate handler based on parsed path
defp handle_parsed_path(conn, parsed_path, language) do
case parsed_path do
{:listing, blog_slug} ->
handle_blog_listing(conn, blog_slug, language)
{:slug_post, blog_slug, post_slug} ->
handle_post(conn, blog_slug, {:slug, post_slug}, language)
{:timestamp_post, blog_slug, date, time} ->
handle_post(conn, blog_slug, {:timestamp, date, time}, language)
{:date_only_post, blog_slug, date} ->
handle_date_only_url(conn, blog_slug, date, language)
{:versioned_post, blog_slug, post_slug, version} ->
handle_versioned_post(conn, blog_slug, post_slug, version, language)
{:error, reason} ->
handle_not_found(conn, reason)
end
end
# ============================================================================
# Blog Listing Handler
# ============================================================================
defp handle_blog_listing(conn, blog_slug, language) do
case Listing.render_blog_listing(conn, blog_slug, language, conn.params) do
{:ok, assigns} ->
conn
|> assign(:page_title, assigns.page_title)
|> assign(:blog, assigns.blog)
|> assign(:posts, assigns.posts)
|> assign(:current_language, assigns.current_language)
|> assign(:translations, assigns.translations)
|> assign(:page, assigns.page)
|> assign(:per_page, assigns.per_page)
|> assign(:total_count, assigns.total_count)
|> assign(:total_pages, assigns.total_pages)
|> assign(:breadcrumbs, assigns.breadcrumbs)
|> render(:index)
{:redirect, url} ->
redirect(conn, to: url)
{:error, reason} ->
handle_not_found(conn, reason)
end
end
# ============================================================================
# Post Handlers
# ============================================================================
defp handle_post(conn, blog_slug, identifier, language) do
case PostRendering.render_post(conn, blog_slug, identifier, language) do
{:ok, assigns} ->
conn
|> assign(:page_title, assigns.page_title)
|> assign(:blog_slug, assigns.blog_slug)
|> assign(:post, assigns.post)
|> assign(:html_content, assigns.html_content)
|> assign(:current_language, assigns.current_language)
|> assign(:translations, assigns.translations)
|> assign(:breadcrumbs, assigns.breadcrumbs)
|> assign(:version_dropdown, assigns.version_dropdown)
|> render(:show)
{:redirect, url} ->
redirect(conn, to: url)
{:redirect_301, url} ->
conn
|> put_status(301)
|> redirect(to: url)
{:error, reason} ->
handle_not_found(conn, reason)
end
end
defp handle_versioned_post(conn, blog_slug, post_slug, version, language) do
case PostRendering.render_versioned_post(conn, blog_slug, post_slug, version, language) do
{:ok, assigns} ->
conn
|> assign(:page_title, assigns.page_title)
|> assign(:blog_slug, assigns.blog_slug)
|> assign(:post, assigns.post)
|> assign(:html_content, assigns.html_content)
|> assign(:current_language, assigns.current_language)
|> assign(:translations, assigns.translations)
|> assign(:breadcrumbs, assigns.breadcrumbs)
|> assign(:canonical_url, assigns.canonical_url)
|> assign(:is_versioned_view, assigns.is_versioned_view)
|> assign(:is_live_version, assigns.is_live_version)
|> assign(:version, assigns.version)
|> assign(:version_dropdown, assigns.version_dropdown)
|> render(:show)
{:error, reason} ->
handle_not_found(conn, reason)
end
end
defp handle_date_only_url(conn, blog_slug, date, language) do
case PostRendering.handle_date_only_url(conn, blog_slug, date, language) do
{:ok, assigns} ->
conn
|> assign(:page_title, assigns.page_title)
|> assign(:blog_slug, assigns.blog_slug)
|> assign(:post, assigns.post)
|> assign(:html_content, assigns.html_content)
|> assign(:current_language, assigns.current_language)
|> assign(:translations, assigns.translations)
|> assign(:breadcrumbs, assigns.breadcrumbs)
|> assign(:version_dropdown, assigns.version_dropdown)
|> render(:show)
{:redirect, url} ->
redirect(conn, to: url)
{:redirect_301, url} ->
conn
|> put_status(301)
|> redirect(to: url)
{:error, reason} ->
handle_not_found(conn, reason)
end
end
# ============================================================================
# Error Handling
# ============================================================================
defp handle_not_found(conn, reason) do
case Fallback.handle_not_found(conn, reason) do
{:redirect_with_flash, path, message} ->
conn
|> put_flash(:info, message)
|> redirect(to: path)
{:render_404} ->
conn
|> put_status(:not_found)
|> put_view(html: PhoenixKitWeb.ErrorHTML)
|> render(:"404")
end
end
# ============================================================================
# Configuration Helpers
# ============================================================================
defp public_enabled? do
# Check new key first, fallback to legacy
case Settings.get_setting("publishing_public_enabled") do
nil -> Settings.get_boolean_setting("blogging_public_enabled", true)
"true" -> true
"false" -> false
_ -> true
end
end
end