Packages
phoenix_kit
1.7.53
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/pages/file_paths.ex
defmodule PhoenixKit.Modules.Pages.FilePaths do
@moduledoc """
Path utilities for Pages module.
Handles path resolution, validation, and conversion operations.
"""
require Logger
alias PhoenixKit.Config
@default_not_found_slug "/404"
@doc """
Gets the root directory path for pages.
Creates the directory if it doesn't exist.
Uses the parent application's directory, not PhoenixKit's dependency directory.
## Examples
iex> PhoenixKit.Modules.Pages.FilePaths.root_path()
"/path/to/app/priv/static/pages"
"""
def root_path do
parent_app = Config.get_parent_app()
path = resolve_pages_path(parent_app)
Logger.debug("Pages root_path: parent_app=#{inspect(parent_app)}, path=#{inspect(path)}")
case File.mkdir_p(path) do
:ok -> path
{:error, reason} -> raise "Failed to create pages directory at #{path}: #{inspect(reason)}"
end
end
@doc """
Normalizes a slug to ensure proper formatting.
- Ensures slug starts with "/"
- Removes trailing "/"
- Returns default slug for empty or invalid inputs
## Examples
iex> PhoenixKit.Modules.Pages.FilePaths.normalize_slug("")
"/404"
iex> PhoenixKit.Modules.Pages.FilePaths.normalize_slug("hello")
"/hello"
iex> PhoenixKit.Modules.Pages.FilePaths.normalize_slug("/hello/")
"/hello"
"""
def normalize_slug(slug) do
slug =
slug
|> String.trim()
|> case do
"" -> @default_not_found_slug
value -> value
end
slug =
if String.starts_with?(slug, "/") do
slug
else
"/" <> slug
end
slug =
slug
|> String.trim_trailing("/")
|> case do
"" -> @default_not_found_slug
"/" -> @default_not_found_slug
value -> value
end
slug
end
@doc """
Converts a slug to a relative file path with .md extension.
## Examples
iex> PhoenixKit.Modules.Pages.FilePaths.slug_to_file_path("/hello")
"/hello.md"
iex> PhoenixKit.Modules.Pages.FilePaths.slug_to_file_path("/hello.md")
"/hello.md"
"""
def slug_to_file_path(slug) do
normalized = normalize_slug(slug)
if String.ends_with?(normalized, ".md") do
normalized
else
normalized <> ".md"
end
end
@doc """
Converts a relative file path to a slug (without .md extension).
## Examples
iex> PhoenixKit.Modules.Pages.FilePaths.file_path_to_slug("/hello.md")
"/hello"
iex> PhoenixKit.Modules.Pages.FilePaths.file_path_to_slug("/hello")
"/hello"
"""
def file_path_to_slug(file_path) do
file_path
|> String.trim_leading("/")
|> Path.rootname(".md")
|> then(fn path -> "/" <> path end)
|> normalize_slug()
end
@doc """
Validates that a path is safe and doesn't attempt directory traversal.
## Examples
iex> PhoenixKit.Modules.Pages.FilePaths.safe_path?("/hello")
true
iex> PhoenixKit.Modules.Pages.FilePaths.safe_path?("/../etc/passwd")
false
"""
def safe_path?(relative_path) do
normalized =
relative_path
|> String.trim_leading("/")
|> String.trim_trailing("/")
not String.contains?(normalized, "..")
end
@doc """
Builds the full absolute path for a relative pages path.
Includes security checks to prevent directory traversal.
## Examples
iex> PhoenixKit.Modules.Pages.FilePaths.build_full_path("/hello.md")
"/app/priv/static/pages/hello.md"
"""
def build_full_path(relative_path) do
# Normalize path (remove leading/trailing slashes)
normalized =
relative_path
|> String.trim_leading("/")
|> String.trim_trailing("/")
# Prevent directory traversal attacks
if String.contains?(normalized, "..") do
raise "Invalid path: directory traversal not allowed"
end
# Build full path
root = root_path()
full_path = Path.join(root, normalized)
# Double-check path is within root directory (security check)
if String.starts_with?(full_path, root) do
full_path
else
raise "Invalid path: attempting to access outside root directory"
end
end
@doc """
Joins path segments safely for pages paths.
Normalizes the result to ensure consistent formatting.
## Examples
iex> PhoenixKit.Modules.Pages.FilePaths.join_path("/blog", "hello.md")
"/blog/hello.md"
iex> PhoenixKit.Modules.Pages.FilePaths.join_path("/blog/", "/hello.md")
"/blog/hello.md"
"""
def join_path(segments) when is_list(segments) do
segments
|> Enum.map(&String.trim/1)
|> Path.join()
|> then(fn path -> "/" <> path end)
|> normalize_path_format()
end
def join_path(segment1, segment2) do
join_path([segment1, segment2])
end
def join_path(segment1, segment2, segment3) do
join_path([segment1, segment2, segment3])
end
# Private helpers
defp resolve_pages_path(parent_app) do
priv_dir = :code.priv_dir(parent_app) |> to_string()
if contains_build_path?(priv_dir) do
project_root = Path.expand("../../../../../", priv_dir)
Path.join(project_root, "priv/static/pages")
else
Path.join(priv_dir, "static/pages")
end
end
defp contains_build_path?(path) do
String.contains?(path, "/_build/") || String.contains?(path, "\\_build\\")
end
defp normalize_path_format(path) do
path
# Replace multiple slashes with single slash
|> String.replace(~r{/+}, "/")
|> String.trim_trailing("/")
|> case do
"" -> "/"
path -> path
end
end
end