Stage 1 of custom CMS pages. Adds type/published/meta/nav fields to pages schema, splits changeset into system vs custom (with slug format validation and reserved path exclusion), adds create/update/delete functions with auto-redirect on slug change, and warms custom pages in ETS cache. 62 pages tests, 1426 total. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
100 lines
1.9 KiB
Elixir
100 lines
1.9 KiB
Elixir
defmodule Berrypod.Pages.PageCache do
|
|
@moduledoc """
|
|
GenServer that maintains an ETS table for caching page definitions.
|
|
|
|
Same pattern as `Berrypod.Theme.CSSCache`. Pages are cached by slug
|
|
for O(1) lookups. Invalidated on save, warmed on startup.
|
|
"""
|
|
|
|
use GenServer
|
|
|
|
@table_name :page_cache
|
|
|
|
## Client API
|
|
|
|
def start_link(opts) do
|
|
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
|
|
end
|
|
|
|
@doc "Gets a cached page by slug. Returns `{:ok, page_data}` or `:miss`."
|
|
def get(slug) do
|
|
case :ets.lookup(@table_name, slug) do
|
|
[{^slug, page_data}] -> {:ok, page_data}
|
|
[] -> :miss
|
|
end
|
|
end
|
|
|
|
@doc "Caches a page definition by slug."
|
|
def put(slug, page_data) do
|
|
:ets.insert(@table_name, {slug, page_data})
|
|
:ok
|
|
end
|
|
|
|
@doc "Invalidates a single cached page."
|
|
def invalidate(slug) do
|
|
:ets.delete(@table_name, slug)
|
|
:ok
|
|
rescue
|
|
ArgumentError -> :ok
|
|
end
|
|
|
|
@doc "Invalidates all cached pages."
|
|
def invalidate_all do
|
|
:ets.delete_all_objects(@table_name)
|
|
:ok
|
|
rescue
|
|
ArgumentError -> :ok
|
|
end
|
|
|
|
@doc "Warms the cache by loading all pages from the DB."
|
|
def warm do
|
|
alias Berrypod.Pages
|
|
alias Berrypod.Pages.Page
|
|
|
|
for slug <- Page.system_slugs() do
|
|
page = Pages.get_page_from_db(slug)
|
|
put(slug, page)
|
|
end
|
|
|
|
import Ecto.Query
|
|
|
|
custom_slugs =
|
|
Berrypod.Repo.all(from p in Page, where: p.type == "custom", select: p.slug)
|
|
|
|
for slug <- custom_slugs do
|
|
case Pages.get_page_from_db(slug) do
|
|
nil -> :ok
|
|
page -> put(slug, page)
|
|
end
|
|
end
|
|
|
|
:ok
|
|
end
|
|
|
|
## Server callbacks
|
|
|
|
@impl true
|
|
def init(_opts) do
|
|
:ets.new(@table_name, [
|
|
:set,
|
|
:public,
|
|
:named_table,
|
|
read_concurrency: true,
|
|
write_concurrency: false
|
|
])
|
|
|
|
{:ok, %{}, {:continue, :warm}}
|
|
end
|
|
|
|
@impl true
|
|
def handle_continue(:warm, state) do
|
|
try do
|
|
warm()
|
|
rescue
|
|
_ -> :ok
|
|
end
|
|
|
|
{:noreply, state}
|
|
end
|
|
end
|