Packages

phoenix_kit

1.7.70
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 post display controller.
Handles public-facing routes for viewing published posts with multi-language support.
URL patterns:
/:language/:group_slug/:post_slug - Slug mode post
/:language/:group_slug/:date/:time - Timestamp mode post
/:language/:group_slug - Group 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/database
- `Listing` - Group 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 post, group listing, or all groups overview.
Path parsing determines which action to take:
- [] -> Invalid request (no group specified)
- [group_slug] -> Group listing
- [group_slug, post_slug] -> Slug mode post
- [group_slug, date] -> Date-only timestamp (resolves to single post or first post)
- [group_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 group slug
# This allows the same route to work for both single and multi-language setups
{language, adjusted_params} = Language.detect_language_or_group(language_param, params)
conn = assign(conn, :current_language, language)
set_gettext_locale(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 :group might actually be a language code
def show(conn, params) do
if Publishing.enabled?() and public_enabled?() do
# Check if the first segment (group) is actually a language with content
case Language.detect_language_in_group_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)
set_gettext_locale(language)
handle_localized_request(conn, language, adjusted_params)
:not_a_language ->
# First segment is a group slug - use default language
language = Language.get_default_language()
conn = assign(conn, :current_language, language)
set_gettext_locale(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, group_slug} ->
handle_group_listing(conn, group_slug, language)
{:slug_post, group_slug, post_slug} ->
handle_post(conn, group_slug, {:slug, post_slug}, language)
{:timestamp_post, group_slug, date, time} ->
handle_post(conn, group_slug, {:timestamp, date, time}, language)
{:date_only_post, group_slug, date} ->
handle_date_only_url(conn, group_slug, date, language)
{:versioned_post, group_slug, post_slug, version} ->
handle_versioned_post(conn, group_slug, post_slug, version, language)
{:error, reason} ->
handle_not_found(conn, reason)
end
end
# ============================================================================
# Group Listing Handler
# ============================================================================
defp handle_group_listing(conn, group_slug, language) do
case Listing.render_group_listing(conn, group_slug, language, conn.params) do
{:ok, assigns} ->
conn
|> assign(:page_title, assigns.page_title)
|> assign(:group, assigns.group)
|> 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, group_slug, identifier, language) do
case PostRendering.render_post(conn, group_slug, identifier, language) do
{:ok, assigns} ->
conn
|> assign(:page_title, assigns.page_title)
|> assign(:group_slug, assigns.group_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, group_slug, post_slug, version, language) do
case PostRendering.render_versioned_post(conn, group_slug, post_slug, version, language) do
{:ok, assigns} ->
conn
|> assign(:page_title, assigns.page_title)
|> assign(:group_slug, assigns.group_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, group_slug, date, language) do
case PostRendering.handle_date_only_url(conn, group_slug, date, language) do
{:ok, assigns} ->
conn
|> assign(:page_title, assigns.page_title)
|> assign(:group_slug, assigns.group_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
defp set_gettext_locale(language) do
Gettext.put_locale(PhoenixKitWeb.Gettext, language)
end
end