Packages
phoenix_kit
1.7.78
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/phoenix_kit/install/router_integration.ex
defmodule PhoenixKit.Install.RouterIntegration do
@moduledoc """
Handles router integration for PhoenixKit installation.
This module provides functionality to:
- Find and validate Phoenix router modules
- Add PhoenixKit imports and routes to routers
- Generate demo page routes
- Handle router integration warnings and notices
"""
use PhoenixKit.Install.IgniterCompat
alias Igniter.Code.{Common, Function}
alias Igniter.Libs.Phoenix, as: IgniterPhoenix
alias Igniter.Project.Application
alias Igniter.Project.Module, as: IgniterModule
alias PhoenixKit.Install.IgniterHelpers
@doc """
Adds PhoenixKit integration to the Phoenix router.
## Parameters
- `igniter` - The igniter context
- `custom_router_path` - Custom path to router file (optional)
## Returns
Updated igniter with router integration or warnings if router not found.
"""
def add_router_integration(igniter, custom_router_path) do
case find_router(igniter, custom_router_path) do
{igniter, nil} ->
warning = create_router_not_found_warning(custom_router_path)
Igniter.add_warning(igniter, warning)
{igniter, router_module} ->
add_phoenix_kit_routes_to_router(igniter, router_module)
end
end
@doc """
Verifies and fixes the position of phoenix_kit_routes() in the router.
During updates, checks if phoenix_kit_routes() is positioned AFTER catch-all routes
(/:param or /*path patterns) and automatically moves it BEFORE them.
## Parameters
- `igniter` - The igniter context
- `custom_router_path` - Custom path to router file (optional)
## Returns
Updated igniter with router position fixed or unchanged if already correct.
"""
def verify_and_fix_router_position(igniter, custom_router_path) do
case find_router(igniter, custom_router_path) do
{igniter, nil} ->
# No router found, nothing to fix
igniter
{igniter, router_module} ->
fix_phoenix_kit_routes_position(igniter, router_module)
end
end
# Fix phoenix_kit_routes() position if it's after catch-all routes
defp fix_phoenix_kit_routes_position(igniter, router_module) do
{_igniter, source, zipper} = IgniterModule.find_module!(igniter, router_module)
# First check if phoenix_kit_routes() exists in router
case Function.move_to_function_call(zipper, :phoenix_kit_routes, 0) do
{:ok, _pk_zipper} ->
# Routes exist, check if they need to be moved
router_code = Rewrite.Source.get(source, :content)
if needs_position_fix?(router_code) do
move_routes_before_catch_all(igniter, router_module)
else
# Position is already correct
igniter
end
:error ->
# phoenix_kit_routes() not found, nothing to fix
igniter
end
end
# Check if phoenix_kit_routes() comes AFTER catch-all routes (needs fixing)
defp needs_position_fix?(router_code) when is_binary(router_code) do
# Find positions of phoenix_kit_routes() and catch-all patterns
pk_routes_match = Regex.run(~r/phoenix_kit_routes\(\)/, router_code, return: :index)
catch_all_match = Regex.run(~r/(live|get)\s+["\']\/:[a-z_]+/, router_code, return: :index)
case {pk_routes_match, catch_all_match} do
{[{pk_pos, _}], [{catch_all_pos, _}]} ->
# If phoenix_kit_routes() position is AFTER catch-all, needs fix
pk_pos > catch_all_pos
_ ->
# No catch-all routes or no phoenix_kit_routes(), no fix needed
false
end
end
# Move phoenix_kit_routes() before catch-all routes
defp move_routes_before_catch_all(igniter, router_module) do
IO.puts("🔄 Moving phoenix_kit_routes() before catch-all routes for proper route matching...")
IgniterModule.find_and_update_module!(igniter, router_module, fn zipper ->
# Step 1: Find and remove existing phoenix_kit_routes() call
case Function.move_to_function_call(zipper, :phoenix_kit_routes, 0) do
{:ok, pk_zipper} ->
# Remove the phoenix_kit_routes() node
zipper_after_removal = Sourceror.Zipper.remove(pk_zipper)
# Step 2: Find catch-all route and insert phoenix_kit_routes() before it
case find_catch_all_insertion_point(zipper_after_removal) do
{:ok, insertion_zipper} ->
{:ok, Common.add_code(insertion_zipper, "phoenix_kit_routes()", placement: :before)}
:error ->
# Fallback: re-add at the end (shouldn't happen, but safety)
{:ok,
Common.add_code(zipper_after_removal, "phoenix_kit_routes()", placement: :after)}
end
:error ->
# phoenix_kit_routes() not found, nothing to do
{:ok, zipper}
end
end)
end
# Find router using IgniterPhoenix
defp find_router(igniter, nil) do
# Check if this is the PhoenixKit library itself (not a real Phoenix app)
case Application.app_name(igniter) do
:phoenix_kit ->
# This is the PhoenixKit library itself, skip router integration
{igniter, nil}
_app_name ->
router_module = IgniterHelpers.get_parent_app_module_web_router(igniter)
case IgniterModule.module_exists(igniter, router_module) do
{true, igniter} ->
{igniter, router_module}
{false, igniter} ->
# Fallback to Igniter's router selection
IgniterPhoenix.select_router(
igniter,
"Which router should be used for PhoenixKit routes?"
)
end
end
end
defp find_router(igniter, custom_path) do
if File.exists?(custom_path) do
handle_existing_router_file(igniter, custom_path)
else
Igniter.add_warning(igniter, "Router file not found at #{custom_path}")
{igniter, nil}
end
end
# Handle extraction and verification of router module from existing file
defp handle_existing_router_file(igniter, custom_path) do
case extract_module_from_router_file(custom_path) do
{:ok, module} ->
verify_router_module_exists(igniter, module, custom_path)
:error ->
Igniter.add_warning(igniter, "Could not determine module name from #{custom_path}")
{igniter, nil}
end
end
# Verify the extracted router module exists in the project
defp verify_router_module_exists(igniter, module, custom_path) do
case IgniterModule.module_exists(igniter, module) do
{true, igniter} ->
{igniter, module}
{false, igniter} ->
Igniter.add_warning(
igniter,
"Module #{inspect(module)} extracted from #{custom_path} does not exist"
)
{igniter, nil}
end
end
# Extract module name from router file content
defp extract_module_from_router_file(path) do
case File.read(path) do
{:ok, content} ->
case Regex.run(~r/defmodule\s+([A-Za-z0-9_.]+)/, content) do
[_, module_name] -> {:ok, Module.concat([module_name])}
_ -> :error
end
_ ->
:error
end
end
# Add PhoenixKit routes to router using proper Igniter API
defp add_phoenix_kit_routes_to_router(igniter, router_module) do
# Check if PhoenixKit routes already exist
{_igniter, _source, zipper} = IgniterModule.find_module!(igniter, router_module)
case Function.move_to_function_call(zipper, :phoenix_kit_routes, 0) do
{:ok, _} ->
# Routes already exist, add notice
Igniter.add_notice(
igniter,
"PhoenixKit routes already exist in router #{inspect(router_module)}, skipping."
)
:error ->
# Add import and routes call to router module
igniter
|> add_import_to_router_module(router_module)
|> add_routes_call_to_router_module(router_module)
end
end
# Add import PhoenixKitWeb.Integration to router
defp add_import_to_router_module(igniter, router_module) do
IgniterModule.find_and_update_module!(igniter, router_module, fn zipper ->
handle_import_addition(igniter, zipper)
end)
end
# Handle the addition of import statement to router
defp handle_import_addition(igniter, zipper) do
if import_already_exists?(zipper) do
{:ok, zipper}
else
add_import_after_use_statement(igniter, zipper)
end
end
# Check if PhoenixKitWeb.Integration import already exists
defp import_already_exists?(zipper) do
case Function.move_to_function_call(zipper, :import, 1, &check_import_argument/1) do
{:ok, _} -> true
:error -> false
end
end
# Check if import argument matches PhoenixKitWeb.Integration
defp check_import_argument(call_zipper) do
case Function.move_to_nth_argument(call_zipper, 0) do
{:ok, arg_zipper} -> Common.nodes_equal?(arg_zipper, PhoenixKitWeb.Integration)
:error -> false
end
end
# Add import statement after use statement
defp add_import_after_use_statement(igniter, zipper) do
case IgniterPhoenix.move_to_router_use(igniter, zipper) do
{:ok, use_zipper} ->
import_code = "import PhoenixKitWeb.Integration"
{:ok, Common.add_code(use_zipper, import_code, placement: :after)}
:error ->
{:warning,
"Could not add import PhoenixKitWeb.Integration to router. Please add manually."}
end
end
# Add phoenix_kit_routes() call to router with smart positioning
# Inserts BEFORE catch-all routes like /:param or /*path to avoid conflicts
defp add_routes_call_to_router_module(igniter, router_module) do
# Get router source to analyze for catch-all routes
{_igniter, source, _zipper} = IgniterModule.find_module!(igniter, router_module)
# Check if router has catch-all routes
router_code = Rewrite.Source.get(source, :content)
has_catch_all = has_catch_all_routes?(router_code)
IgniterModule.find_and_update_module!(igniter, router_module, fn zipper ->
app_web_module_name = IgniterHelpers.get_parent_app_module_web_string(igniter)
routes_code = generate_routes_code(app_web_module_name)
if has_catch_all do
# Try to insert before catch-all routes
case find_catch_all_insertion_point(zipper) do
{:ok, insertion_zipper} ->
IO.puts(
"📍 Inserting phoenix_kit_routes() before catch-all routes for proper route matching"
)
{:ok, Common.add_code(insertion_zipper, routes_code, placement: :before)}
:error ->
# Fallback: add at end
{:ok, Common.add_code(zipper, routes_code, placement: :after)}
end
else
# No catch-all routes, add at end (current behavior)
{:ok, Common.add_code(zipper, routes_code, placement: :after)}
end
end)
end
# Check if router code contains catch-all routes
defp has_catch_all_routes?(code) when is_binary(code) do
# Patterns that indicate catch-all routes:
# - live "/:param"
# - get "/:param"
# - live "/:param/*path"
# - scope "/" with live "/:param"
catch_all_patterns = [
# live "/:entity_name"
~r/live\s+["\']\/:[a-z_]+["\']/,
# get "/:param"
~r/get\s+["\']\/:[a-z_]+["\']/,
# live "/:entity/*path"
~r/live\s+["\']\/:[a-z_]+\/\*[a-z_]+["\']/,
# get "/:entity/*path"
~r/get\s+["\']\/:[a-z_]+\/\*[a-z_]+["\']/
]
Enum.any?(catch_all_patterns, &Regex.match?(&1, code))
end
# Find a good insertion point before catch-all routes
# Returns {:ok, zipper} positioned at a scope or route before catch-all, or :error
defp find_catch_all_insertion_point(zipper) do
# Try to find a scope call - use :any to match any arity
case Function.move_to_function_call(zipper, :scope, :any) do
{:ok, scope_zipper} ->
# Check if this scope contains catch-all routes
if scope_has_catch_all?(scope_zipper) do
{:ok, scope_zipper}
else
# Try to find another scope or fall back to first live/get catch-all
find_catch_all_route_fallback(zipper)
end
:error ->
# No scope found, try to find first live/get with catch-all pattern
find_catch_all_route_fallback(zipper)
end
end
# Check if a scope call contains catch-all routes
defp scope_has_catch_all?(call_zipper) do
# Get the scope's source code to check for catch-all patterns
node = Sourceror.Zipper.node(call_zipper)
code = Macro.to_string(node)
has_catch_all_routes?(code)
end
# Fallback: find first live or get route with catch-all pattern
defp find_catch_all_route_fallback(zipper) do
# Look for live routes with any arity
case Function.move_to_function_call(zipper, :live, :any) do
{:ok, live_zipper} ->
if catch_all_route?(live_zipper) do
{:ok, live_zipper}
else
# Try get routes
try_get_catch_all(zipper)
end
:error ->
# Try get routes
try_get_catch_all(zipper)
end
end
# Try to find a get route with catch-all pattern
defp try_get_catch_all(zipper) do
case Function.move_to_function_call(zipper, :get, :any) do
{:ok, get_zipper} ->
if catch_all_route?(get_zipper), do: {:ok, get_zipper}, else: :error
:error ->
:error
end
end
# Check if a route call is a catch-all (/:param pattern)
defp catch_all_route?(call_zipper) do
case Function.move_to_nth_argument(call_zipper, 0) do
{:ok, arg_zipper} ->
node = Sourceror.Zipper.node(arg_zipper)
case node do
path when is_binary(path) ->
# Check if path starts with /:
String.match?(path, ~r/^\/:[a-z_]+/)
_ ->
false
end
:error ->
false
end
end
# Generate the routes code with demo pages
defp generate_routes_code(app_web_module_name) do
"""
# PhoenixKit Demo Pages - Test Authentication Levels
scope "/" do
pipe_through :browser
live_session :phoenix_kit_demo_current_scope,
on_mount: [{PhoenixKitWeb.Users.Auth, :phoenix_kit_mount_current_scope}] do
live "/test-current-user", #{app_web_module_name}.PhoenixKitLive.TestRequireAuthLive, :index
end
live_session :phoenix_kit_demo_redirect_if_auth_scope,
on_mount: [{PhoenixKitWeb.Users.Auth, :phoenix_kit_redirect_if_authenticated_scope}] do
live "/test-redirect-if-auth", #{app_web_module_name}.PhoenixKitLive.TestRedirectIfAuthLive, :index
end
live_session :phoenix_kit_demo_ensure_auth_scope,
on_mount: [{PhoenixKitWeb.Users.Auth, :phoenix_kit_ensure_authenticated_scope}] do
live "/test-ensure-auth", #{app_web_module_name}.PhoenixKitLive.TestEnsureAuthLive, :index
end
end
phoenix_kit_routes()
"""
end
# Create comprehensive warning when router is not found
defp create_router_not_found_warning(nil) do
"""
🚨 Router Detection Failed
PhoenixKit could not automatically detect your Phoenix router.
📋 MANUAL SETUP REQUIRED:
1. Open your main router file (usually lib/your_app_web/router.ex)
2. Add the following lines to your router module:
defmodule YourAppWeb.Router do
use YourAppWeb, :router
# Add this import
import PhoenixKitWeb.Integration
# Your existing pipelines and scopes...
# Add this line at the end, before the final 'end'
phoenix_kit_routes()
end
3. The routes will be available at:
• {prefix}/register - User registration
• {prefix}/login - User login
• {prefix}/reset_password - Password reset
Note: {prefix} is your configured PhoenixKit URL prefix (default: /phoenix_kit)
• And other authentication routes
📖 Common router locations:
• lib/my_app_web/router.ex
• lib/my_app/router.ex
• apps/my_app_web/lib/my_app_web/router.ex (umbrella apps)
⚠️ Note: You may see a compiler warning about "unused import PhoenixKitWeb.Integration".
This is normal behavior for Elixir macros and can be safely ignored.
The phoenix_kit_routes() macro will expand correctly.
💡 Need help? Check the PhoenixKit documentation or create an issue on GitHub.
"""
end
defp create_router_not_found_warning(custom_path) do
"""
🚨 Router Not Found at Custom Path
PhoenixKit could not find a router at the specified path: #{custom_path}
📋 TROUBLESHOOTING STEPS:
1. Verify the path exists and contains a valid Phoenix router
2. Check file permissions (file must be readable)
3. Ensure the file contains a proper Phoenix router module:
defmodule YourAppWeb.Router do
use YourAppWeb, :router
# ... router content
end
📋 MANUAL SETUP (if file exists but couldn't be processed):
Add the following to your router at #{custom_path}:
# Add after 'use YourAppWeb, :router'
import PhoenixKitWeb.Integration
# Add before the final 'end'
phoenix_kit_routes()
🔄 ALTERNATIVE: Let PhoenixKit auto-detect your router:
Run the installer without --router-path option:
mix phoenix_kit.install
⚠️ Note: You may see a compiler warning about "unused import PhoenixKitWeb.Integration".
This is normal for macros and can be safely ignored.
💡 Need help? Check the PhoenixKit documentation or create an issue on GitHub.
"""
end
end