Packages
phoenix_kit
1.7.21
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/file_storage.ex
defmodule PhoenixKit.Modules.Sitemap.FileStorage do
@moduledoc """
File-based storage for sitemap XML.
Sitemaps are saved to priv/static/sitemap.xml for direct nginx/CDN serving.
This is the primary storage - no ETS caching layer needed.
## Key Features
- **Direct nginx serving** - Files in priv/static/ can be served without Phoenix
- **ETag from mtime** - Use `get_file_stat/0` for cache validation
- **On-demand generation** - First request generates if file missing
## Usage
# Save sitemap XML
FileStorage.save(xml_content)
# Load sitemap XML
case FileStorage.load() do
{:ok, xml} -> send_resp(conn, 200, xml)
:error -> generate_and_serve(conn)
end
# Check if file exists
FileStorage.exists?()
# Get file stats for ETag
{:ok, mtime, size} = FileStorage.get_file_stat()
# Delete to force regeneration
FileStorage.delete()
"""
require Logger
@sitemap_file "sitemap.xml"
@doc """
Saves XML content to the sitemap file.
Creates the storage directory if it doesn't exist.
## Examples
iex> FileStorage.save("<?xml ...")
:ok
"""
@spec save(String.t()) :: :ok | {:error, term()}
def save(xml_content) when is_binary(xml_content) do
path = file_path()
with :ok <- ensure_directory_exists(path),
:ok <- File.write(path, xml_content) do
Logger.debug("FileStorage: Saved sitemap.xml (#{byte_size(xml_content)} bytes)")
:ok
else
{:error, reason} = error ->
Logger.warning("FileStorage: Failed to save sitemap.xml: #{inspect(reason)}")
error
end
end
@doc """
Loads XML content from the sitemap file.
## Examples
iex> FileStorage.load()
{:ok, "<?xml ..."}
iex> FileStorage.load() # when file doesn't exist
:error
"""
@spec load() :: {:ok, String.t()} | :error
def load do
path = file_path()
case File.read(path) do
{:ok, content} ->
Logger.debug("FileStorage: Loaded sitemap.xml from file")
{:ok, content}
{:error, :enoent} ->
:error
{:error, reason} ->
Logger.warning("FileStorage: Failed to read sitemap.xml: #{inspect(reason)}")
:error
end
end
@doc """
Checks if the sitemap file exists.
## Examples
iex> FileStorage.exists?()
true
"""
@spec exists?() :: boolean()
def exists? do
file_path()
|> File.exists?()
end
@doc """
Returns file stats for ETag generation.
Uses mtime and size for cache validation - more reliable than
database-based config values.
## Examples
iex> FileStorage.get_file_stat()
{:ok, {{2025, 1, 15}, {10, 30, 0}}, 12345}
iex> FileStorage.get_file_stat() # file doesn't exist
:error
"""
@spec get_file_stat() :: {:ok, tuple(), non_neg_integer()} | :error
def get_file_stat do
case File.stat(file_path()) do
{:ok, %{mtime: mtime, size: size}} ->
{:ok, mtime, size}
{:error, _} ->
:error
end
end
@doc """
Deletes the sitemap file to force regeneration.
Next request will trigger fresh generation.
## Examples
iex> FileStorage.delete()
:ok
"""
@spec delete() :: :ok
def delete do
case File.rm(file_path()) do
:ok ->
Logger.debug("FileStorage: Deleted sitemap.xml")
:ok
{:error, :enoent} ->
# File doesn't exist - that's fine
:ok
{:error, reason} ->
Logger.warning("FileStorage: Failed to delete sitemap.xml: #{inspect(reason)}")
:ok
end
end
@doc """
Returns the file path for the sitemap.
Path: priv/static/sitemap.xml (allows nginx direct serving)
"""
@spec file_path() :: String.t()
def file_path do
Path.join(storage_dir(), @sitemap_file)
end
@doc """
Returns the storage directory path.
Uses priv/static for nginx/CDN compatibility.
"""
@spec storage_dir() :: String.t()
def storage_dir do
case :code.priv_dir(:phoenix_kit) do
{:error, :bad_name} ->
# Fallback for development/test
"priv/static"
priv_dir ->
Path.join(priv_dir, "static")
end
end
@doc """
Clears the sitemap file.
Alias for `delete/0` for API consistency.
"""
@spec clear_all() :: :ok
def clear_all do
delete()
end
# Legacy API compatibility - style parameter ignored
@doc false
@spec save(String.t(), String.t()) :: :ok | {:error, term()}
def save(_style, xml_content), do: save(xml_content)
@doc false
@spec load(String.t()) :: {:ok, String.t()} | :error
def load(_style), do: load()
@doc false
@spec exists?(String.t()) :: boolean()
def exists?(_style), do: exists?()
@doc false
@spec file_path(String.t()) :: String.t()
def file_path(_style), do: file_path()
# Private helpers
defp ensure_directory_exists(file_path) do
dir = Path.dirname(file_path)
case File.mkdir_p(dir) do
:ok -> :ok
{:error, reason} -> {:error, {:mkdir_failed, reason}}
end
end
end