Packages

phoenix_kit

2.43.0
2.60.3 2.60.2 2.60.1 2.60.0 2.59.0 2.58.0 2.57.1 2.57.0 2.56.1 2.56.0 2.55.1 2.55.0 2.54.2 2.54.1 2.54.0 2.53.0 2.52.2 2.52.1 2.52.0 2.51.0 2.50.0 2.49.1 2.49.0 2.48.0 2.47.0 2.46.0 2.45.0 2.44.0 2.43.1 2.43.0 2.42.1 2.42.0 2.41.6 2.41.4 2.41.3 2.41.2 2.41.1 2.41.0 2.40.1 2.40.0 2.39.0 2.38.1 2.38.0 2.37.5 2.37.4 2.37.3 2.37.2 2.37.1 2.37.0 2.36.1 2.36.0 2.35.0 2.34.0 2.33.0 2.32.1 2.32.0 2.31.1 2.31.0 2.30.0 2.29.1 2.29.0 2.28.2 2.28.1 2.28.0 2.27.2 2.27.1 2.27.0 2.26.1 2.26.0 2.25.0 2.24.0 2.23.3 2.23.2 2.23.1 2.23.0 2.22.24 2.22.23 2.22.22 2.22.21 2.22.20 2.22.19 2.22.18 2.22.17 2.22.16 2.22.15 2.22.14 2.22.13 2.22.12 2.22.11 2.22.10 2.22.9 2.22.8 2.22.7 2.22.6 2.22.5 2.22.4 2.22.3 2.22.2 2.22.1 2.22.0 2.21.5 2.21.4 2.21.3 2.21.2 2.21.1 2.21.0 2.20.0 2.19.0 2.18.1 2.18.0 2.17.0 2.16.0 2.15.1 2.15.0 2.14.2 2.14.1 2.14.0 2.13.19 2.13.18 2.13.17 2.13.16 2.13.15 2.13.13 2.13.12 2.13.11 2.13.10 2.13.9 2.13.8 2.13.7 2.13.6 2.13.5 2.13.4 2.13.3 2.13.2 2.13.1 2.13.0 2.12.1 2.12.0 2.11.0 2.10.0 2.9.0 2.8.1 2.8.0 2.7.0 2.6.0 2.5.0 2.4.0 2.3.0 2.2.0 2.1.0 2.0.1 2.0.0 1.7.236 1.7.235 1.7.234 1.7.233 1.7.232 1.7.231 1.7.230 1.7.229 1.7.228 1.7.227 1.7.226 1.7.225 1.7.224 1.7.223 1.7.222 1.7.221 1.7.220 1.7.219 1.7.218 1.7.217 1.7.216 1.7.215 1.7.214 1.7.213 1.7.212 1.7.211 1.7.210 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
phoenix_kit lib modules sitemap cache.ex
Raw

lib/modules/sitemap/cache.ex

defmodule PhoenixKit.Modules.Sitemap.Cache do
  @moduledoc """
  Caching layer for sitemap generation.

  Manages ETS cache for entries, per-module XML, and index XML.
  When invalidated, clears ETS cache and deletes sitemap files.

  ## Cache Key Scheme

  - `:index_xml` - The sitemapindex XML content
  - `{:module_xml, "sitemap-shop"}` - Per-module XML content
  - `{:module_entries, :shop}` - Per-module collected entries
  - `:entries` - All collected entries (legacy)
  - `:parts` - Sitemap index parts (legacy)
  """

  alias PhoenixKit.Modules.Sitemap
  alias PhoenixKit.Modules.Sitemap.FileStorage

  @table_name :sitemap_cache

  @doc """
  Initializes the ETS cache table.

  Safe to call multiple times - returns :ok if table already exists.
  """
  @spec init() :: :ok
  def init do
    unless table_exists?() do
      :ets.new(@table_name, [
        :named_table,
        :public,
        :set,
        read_concurrency: true,
        write_concurrency: false
      ])
    end

    :ok
  end

  @doc """
  Retrieves a cached value by key.
  """
  @spec get(term()) :: {:ok, any()} | :error
  def get(key) do
    init()

    case :ets.lookup(@table_name, key) do
      [{^key, value}] -> {:ok, value}
      [] -> :error
    end
  end

  @doc """
  Stores a value in cache with the given key.
  """
  @spec put(term(), any()) :: :ok
  def put(key, value) do
    init()
    :ets.insert(@table_name, {key, value})
    :ok
  end

  @doc """
  Checks if a cached value exists for the given key.
  """
  @spec has?(term()) :: boolean()
  def has?(key) do
    init()

    case :ets.lookup(@table_name, key) do
      [{^key, _}] -> true
      [] -> false
    end
  end

  @doc """
  Deletes a specific cache entry.
  """
  @spec delete(term()) :: :ok
  def delete(key) do
    if table_exists?() do
      :ets.delete(@table_name, key)
    end

    :ok
  end

  # ── Per-module cache operations ────────────────────────────────────

  @doc """
  Caches XML content for a specific module.
  """
  @spec put_module(String.t(), String.t()) :: :ok
  def put_module(filename, xml) when is_binary(filename) and is_binary(xml) do
    put({:module_xml, filename}, xml)
  end

  @doc """
  Gets cached XML content for a specific module.
  """
  @spec get_module(String.t()) :: {:ok, String.t()} | :error
  def get_module(filename) when is_binary(filename) do
    get({:module_xml, filename})
  end

  @doc """
  Invalidates all cache keys for a specific source module.
  """
  @spec invalidate_module(String.t()) :: :ok
  def invalidate_module(filename) when is_binary(filename) do
    delete({:module_xml, filename})
    :ok
  end

  # ── Full invalidation ──────────────────────────────────────────────

  @doc """
  Clears all cached data and deletes all sitemap files.

  Should be called when sitemap content changes.
  """
  @spec invalidate() :: :ok
  def invalidate do
    # Delete sitemap files (index + all module files)
    FileStorage.delete()
    FileStorage.delete_all_modules()

    # Domain mode inherits the delete-on-invalidate freshness guarantee:
    # every generated per-host set goes too (current AND stale hosts).
    Enum.each(FileStorage.list_domain_hosts(), &FileStorage.delete_domain_files/1)

    # Clear generation stats
    Sitemap.clear_generation_stats()

    # Clear all ETS entries
    if table_exists?() do
      :ets.delete_all_objects(@table_name)
    end

    :ok
  end

  @doc """
  Alias for `invalidate/0`.
  """
  @spec invalidate_all() :: :ok
  def invalidate_all, do: invalidate()

  @doc """
  Returns cache statistics.
  """
  @spec stats() :: map()
  def stats do
    if table_exists?() do
      info = :ets.info(@table_name)

      %{
        size: Keyword.get(info, :size, 0),
        memory: Keyword.get(info, :memory, 0)
      }
    else
      %{size: 0, memory: 0}
    end
  end

  # Private helpers

  defp table_exists? do
    :ets.whereis(@table_name) != :undefined
  end
end