Packages
phoenix_kit
1.7.21
1.7.209
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
Current section
Files
lib/modules/sitemap/sources/router_discovery.ex
defmodule PhoenixKit.Modules.Sitemap.Sources.RouterDiscovery do
@moduledoc """
Router Discovery source for sitemap generation.
Automatically scans all GET routes from the parent application's router
and includes them in the sitemap. Routes can be filtered using exclude
patterns and include-only patterns.
## Settings
- `sitemap_router_discovery_enabled` - Enable/disable auto-discovery (default: true)
- `sitemap_router_discovery_exclude_patterns` - JSON array of regex patterns to exclude
- `sitemap_router_discovery_include_only` - JSON array of regex patterns for whitelist mode
- `sitemap_protected_pipelines` - JSON array of pipeline names that require authentication
## Default Exclusions
By default, the following patterns are excluded:
- `^/admin` - Admin routes
- `^/api` - API endpoints
- `^/phoenix_kit` - PhoenixKit admin routes
- `^/dev` - Development routes
- `:[a-z_]+` - Routes with parameters
- `\\*` - Wildcard routes
Additionally, routes using authentication pipelines are automatically excluded:
- `:phoenix_kit_require_authenticated` - Routes requiring user authentication
- `:phoenix_kit_admin_only` - Routes requiring admin/owner role
- `:authenticated` - Common name for authentication pipeline
- `:require_authenticated` - Alternative authentication pipeline name
- `:admin` - Common admin pipeline name
- `:admin_only` - Alternative admin pipeline name
Custom pipelines can be added via `sitemap_protected_pipelines` setting.
LiveView routes using authentication `on_mount` hooks are also excluded:
- `{PhoenixKitWeb.Users.Auth, :phoenix_kit_ensure_authenticated_scope}` - Ensures user is authenticated
- `{PhoenixKitWeb.Users.Auth, :phoenix_kit_redirect_if_authenticated_scope}` - Redirects if already authenticated
## Examples
# Enable auto-discovery (default)
Settings.update_boolean_setting("sitemap_router_discovery_enabled", true)
# Custom exclude patterns
Settings.update_setting("sitemap_router_discovery_exclude_patterns",
Jason.encode!(["^/admin", "^/api", "^/private"]))
# Whitelist mode - only include specific paths
Settings.update_setting("sitemap_router_discovery_include_only",
Jason.encode!(["^/products", "^/categories"]))
# Custom protected pipelines (add to defaults)
Settings.update_setting("sitemap_protected_pipelines",
Jason.encode!(["my_auth_pipeline", "member_only"]))
## Sitemap Properties
- Priority: 0.5 (default for discovered routes)
- Change frequency: weekly
- Category: "Routes"
"""
@behaviour PhoenixKit.Modules.Sitemap.Sources.Source
require Logger
alias PhoenixKit.Modules.Sitemap.RouteResolver
alias PhoenixKit.Modules.Sitemap.UrlEntry
alias PhoenixKit.Settings
@default_exclude_patterns [
"^/admin",
"^/api",
"^/phoenix_kit",
"^/dev",
":[a-z_]+",
"\\*"
]
# Default pipelines that require authentication - routes using these should not appear in sitemap
# Can be extended via Settings: sitemap_protected_pipelines
@default_protected_pipelines [
:phoenix_kit_require_authenticated,
:phoenix_kit_admin_only,
:authenticated,
:require_authenticated,
:admin,
:admin_only
]
# Default on_mount hooks that require authentication (for LiveView routes)
# Format: {Module, hook_name} - matches against on_mount id tuples
@default_protected_on_mount_hooks [
{PhoenixKitWeb.Users.Auth, :phoenix_kit_ensure_authenticated_scope},
{PhoenixKitWeb.Users.Auth, :phoenix_kit_redirect_if_authenticated_scope}
]
@impl true
def source_name, do: :router_discovery
@impl true
def enabled? do
Settings.get_boolean_setting("sitemap_router_discovery_enabled", true)
end
@impl true
def collect(opts \\ []) do
if enabled?() do
do_collect(opts)
else
[]
end
rescue
error ->
Logger.warning("RouterDiscovery source failed: #{inspect(error)}")
[]
end
defp do_collect(opts) do
base_url = Keyword.get(opts, :base_url)
exclude_patterns = get_exclude_patterns()
include_only = get_include_only_patterns()
RouteResolver.get_routes()
|> Enum.filter(&valid_for_sitemap?(&1, exclude_patterns, include_only))
|> Enum.map(&build_entry(&1, base_url))
|> Enum.uniq_by(& &1.loc)
end
defp valid_for_sitemap?(route, exclude_patterns, include_only) do
get_route?(route) and
not excluded?(route.path, exclude_patterns) and
included?(route.path, include_only) and
not protected_route?(route) and
not protected_liveview_route?(route)
end
# Check if route uses authentication pipelines
# Uses Phoenix.Router.route_info/4 to get pipe_through info (not available in __routes__)
defp protected_route?(route) do
route_pipelines = get_route_pipelines(route.path)
protected_pipelines = get_protected_pipelines()
Enum.any?(protected_pipelines, &(&1 in route_pipelines))
end
# Get pipelines for a route using Phoenix.Router.route_info/4
defp get_route_pipelines(path) do
case RouteResolver.get_router() do
nil ->
[]
router ->
case Phoenix.Router.route_info(router, "GET", path, "localhost") do
%{pipe_through: pipelines} when is_list(pipelines) -> pipelines
_ -> []
end
end
rescue
_ -> []
end
# Check if LiveView route uses authentication on_mount hooks
defp protected_liveview_route?(route) do
on_mount_hooks = get_route_on_mount_hooks(route.path)
protected_hooks = @default_protected_on_mount_hooks
Enum.any?(protected_hooks, &(&1 in on_mount_hooks))
end
# Get on_mount hook IDs for a LiveView route
defp get_route_on_mount_hooks(path) do
case RouteResolver.get_router() do
nil ->
[]
router ->
case Phoenix.Router.route_info(router, "GET", path, "localhost") do
%{phoenix_live_view: {_module, _action, _opts, %{extra: %{on_mount: hooks}}}}
when is_list(hooks) ->
Enum.map(hooks, & &1.id)
_ ->
[]
end
end
rescue
_ -> []
end
defp get_protected_pipelines do
custom_pipelines = get_custom_protected_pipelines()
@default_protected_pipelines ++ custom_pipelines
end
defp get_custom_protected_pipelines do
case Settings.get_setting("sitemap_protected_pipelines") do
nil ->
[]
json_string when is_binary(json_string) ->
case Jason.decode(json_string) do
{:ok, pipelines} when is_list(pipelines) ->
Enum.map(pipelines, &safe_to_atom/1)
_ ->
[]
end
pipelines when is_list(pipelines) ->
Enum.map(pipelines, &safe_to_atom/1)
_ ->
[]
end
end
defp safe_to_atom(value) when is_atom(value), do: value
defp safe_to_atom(value) when is_binary(value), do: String.to_atom(value)
defp get_route?(route) do
route.verb == :get
end
defp excluded?(path, patterns) do
Enum.any?(patterns, fn pattern ->
case Regex.compile(pattern) do
{:ok, regex} -> Regex.match?(regex, path)
_ -> false
end
end)
end
defp included?(_path, []) do
# Empty include_only = include all
true
end
defp included?(path, patterns) do
Enum.any?(patterns, fn pattern ->
case Regex.compile(pattern) do
{:ok, regex} -> Regex.match?(regex, path)
_ -> false
end
end)
end
defp get_exclude_patterns do
case Settings.get_setting("sitemap_router_discovery_exclude_patterns") do
nil ->
@default_exclude_patterns
json_string when is_binary(json_string) ->
case Jason.decode(json_string) do
{:ok, patterns} when is_list(patterns) -> patterns
_ -> @default_exclude_patterns
end
patterns when is_list(patterns) ->
patterns
_ ->
@default_exclude_patterns
end
end
defp get_include_only_patterns do
case Settings.get_setting("sitemap_router_discovery_include_only") do
nil ->
[]
json_string when is_binary(json_string) ->
case Jason.decode(json_string) do
{:ok, patterns} when is_list(patterns) -> patterns
_ -> []
end
patterns when is_list(patterns) ->
patterns
_ ->
[]
end
end
defp build_entry(route, base_url) do
url = build_url(route.path, base_url)
title = extract_title(route)
UrlEntry.new(%{
loc: url,
lastmod: nil,
changefreq: "weekly",
priority: 0.5,
title: title,
category: "Routes",
source: :router_discovery
})
end
defp build_url(path, nil) do
base = Settings.get_setting("site_url", "")
normalized_base = String.trim_trailing(base, "/")
"#{normalized_base}#{path}"
end
defp build_url(path, base_url) when is_binary(base_url) do
normalized_base = String.trim_trailing(base_url, "/")
"#{normalized_base}#{path}"
end
defp extract_title(route) do
# Try to extract meaningful title from plug module name
plug_name =
route.plug
|> to_string()
|> String.replace("Elixir.", "")
|> String.split(".")
|> List.last()
# Convert CamelCase to Title Case
plug_name
|> String.replace(~r/([A-Z])/, " \\1")
|> String.trim()
end
end