Packages

phoenix_kit

1.7.3
1.7.208 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 phoenix_kit sitemap sources static.ex
Raw

lib/phoenix_kit/sitemap/sources/static.ex

defmodule PhoenixKit.Sitemap.Sources.Static do
@moduledoc """
Static routes source for sitemap generation.
Collects configurable static routes for the sitemap. Routes are configured
through Settings and resolved via RouteResolver - NO hardcoded fallbacks.
## Settings
- `sitemap_static_routes` - JSON array of route configurations
- `sitemap_custom_urls` - JSON array of custom URL entries
## Route Configuration Format
Each route in `sitemap_static_routes` can have:
%{
"plug" => "PhoenixKitWeb.Users.Registration", # Module to resolve via RouteResolver
"path" => "/custom/path", # OR explicit path (overrides plug)
"priority" => 0.7, # Sitemap priority (0.0-1.0)
"changefreq" => "monthly", # Change frequency
"title" => "Register", # Display title
"category" => "Authentication", # Category for grouping
"prefixed" => true # Use PhoenixKit URL prefix
}
## Custom URL Format
Each entry in `sitemap_custom_urls`:
%{
"path" => "/about-us",
"priority" => 0.8,
"changefreq" => "monthly",
"title" => "About Us",
"category" => "Company"
}
## Default Configuration
By default, includes:
- Homepage (/) - Priority: 0.9, daily
- Registration page - Priority: 0.7, monthly (if route exists)
- Login page - Priority: 0.7, monthly (if route exists)
## No Hardcoded Fallbacks
If RouteResolver cannot find a route and no explicit path is configured,
the route is skipped. This ensures sitemap only contains valid URLs.
"""
@behaviour PhoenixKit.Sitemap.Sources.Source
alias PhoenixKit.Settings
alias PhoenixKit.Sitemap.RouteResolver
alias PhoenixKit.Sitemap.UrlEntry
alias PhoenixKit.Utils.Routes
@default_static_routes [
%{
"path" => "/",
"priority" => 0.9,
"changefreq" => "daily",
"title" => "Home",
"category" => "Main",
"prefixed" => false
},
%{
"plug" => "PhoenixKitWeb.Users.Registration",
"priority" => 0.7,
"changefreq" => "monthly",
"title" => "Register",
"category" => "Authentication",
"prefixed" => true
},
%{
"plug" => "PhoenixKitWeb.Users.Login",
"priority" => 0.7,
"changefreq" => "monthly",
"title" => "Login",
"category" => "Authentication",
"prefixed" => true
}
]
@impl true
def source_name, do: :static
@impl true
def enabled?, do: true
@impl true
def collect(opts \\ []) do
base_url = Keyword.get(opts, :base_url)
language = Keyword.get(opts, :language)
is_default = Keyword.get(opts, :is_default_language, true)
static_entries = collect_static_routes(base_url, language, is_default)
custom_entries = collect_custom_urls(base_url, language, is_default)
(static_entries ++ custom_entries)
|> Enum.reject(&is_nil/1)
rescue
error ->
require Logger
Logger.warning("Static routes sitemap source failed to collect: #{inspect(error)}")
[]
end
defp collect_static_routes(base_url, language, is_default) do
get_static_routes_config()
|> Enum.map(&build_static_entry(&1, base_url, language, is_default))
end
defp collect_custom_urls(base_url, language, is_default) do
get_custom_urls_config()
|> Enum.map(&build_custom_entry(&1, base_url, language, is_default))
end
defp get_static_routes_config do
case Settings.get_setting("sitemap_static_routes") do
nil ->
@default_static_routes
json_string when is_binary(json_string) ->
case Jason.decode(json_string) do
{:ok, routes} when is_list(routes) -> routes
_ -> @default_static_routes
end
routes when is_list(routes) ->
routes
_ ->
@default_static_routes
end
end
defp get_custom_urls_config do
case Settings.get_setting("sitemap_custom_urls") do
nil ->
[]
json_string when is_binary(json_string) ->
case Jason.decode(json_string) do
{:ok, urls} when is_list(urls) -> urls
_ -> []
end
urls when is_list(urls) ->
urls
_ ->
[]
end
end
defp build_static_entry(config, base_url, language, is_default) do
path = resolve_path(config)
if path do
prefixed = Map.get(config, "prefixed", false)
# Canonical path without language prefix (for hreflang grouping)
canonical_path = if prefixed, do: Routes.path(path), else: path
localized_path = build_path_with_language(canonical_path, language, is_default)
url = build_url_from_localized_path(localized_path, base_url, prefixed)
UrlEntry.new(%{
loc: url,
lastmod: nil,
changefreq: Map.get(config, "changefreq", "weekly"),
priority: Map.get(config, "priority", 0.5),
title: Map.get(config, "title", path),
category: Map.get(config, "category", "Static"),
source: :static,
canonical_path: canonical_path
})
else
# Route not found and no explicit path - skip
nil
end
end
defp build_custom_entry(config, base_url, language, is_default) do
path = Map.get(config, "path")
if path do
# Canonical path without language prefix (for hreflang grouping)
canonical_path = path
localized_path = build_path_with_language(path, language, is_default)
url = build_url_from_localized_path(localized_path, base_url, false)
UrlEntry.new(%{
loc: url,
lastmod: nil,
changefreq: Map.get(config, "changefreq", "weekly"),
priority: Map.get(config, "priority", 0.5),
title: Map.get(config, "title", path),
category: Map.get(config, "category", "Custom"),
source: :static,
canonical_path: canonical_path
})
else
nil
end
end
# Resolve path from config: explicit path OR via RouteResolver
defp resolve_path(%{"path" => path}) when is_binary(path) and path != "" do
path
end
defp resolve_path(%{"plug" => plug_string}) when is_binary(plug_string) do
# Try to resolve module via RouteResolver
module = String.to_existing_atom("Elixir." <> plug_string)
RouteResolver.find_route(module)
rescue
# Module doesn't exist - return nil (no hardcoded fallback!)
_ -> nil
end
defp resolve_path(_), do: nil
# Build URL from already localized path
defp build_url_from_localized_path(path, nil, _prefixed) do
base = Settings.get_setting("site_url", "")
normalized_base = String.trim_trailing(base, "/")
"#{normalized_base}#{path}"
end
defp build_url_from_localized_path(path, base_url, _prefixed) when is_binary(base_url) do
normalized_base = String.trim_trailing(base_url, "/")
"#{normalized_base}#{path}"
end
# Add language prefix to path if not default language
defp build_path_with_language(path, language, is_default) do
if language && !is_default do
"/#{extract_base(language)}#{path}"
else
path
end
end
# Extract base language code (e.g., "en" from "en-US")
defp extract_base(code) when is_binary(code) do
code |> String.split("-") |> List.first() |> String.downcase()
end
defp extract_base(_), do: "en"
end