diff --git a/lib/berrypod/application.ex b/lib/berrypod/application.ex index acd86e2..2d235ba 100644 --- a/lib/berrypod/application.ex +++ b/lib/berrypod/application.ex @@ -7,9 +7,10 @@ defmodule Berrypod.Application do @impl true def start(_type, _args) do - # Create ETS table here so the supervisor process owns it (lives forever). - # The Task below only warms it with data from the DB. + # Create ETS tables here so the supervisor process owns them (live forever). + # The GenServers/Tasks below only warm them with data from the DB. Berrypod.Redirects.create_table() + Berrypod.Settings.SettingsCache.create_table() Berrypod.ActivityLog.ObanTelemetryHandler.attach() children = [ diff --git a/lib/berrypod/media.ex b/lib/berrypod/media.ex index f46177f..aa8bebc 100644 --- a/lib/berrypod/media.ex +++ b/lib/berrypod/media.ex @@ -135,7 +135,8 @@ defmodule Berrypod.Media do from i in ImageSchema, where: i.image_type == "logo", order_by: [desc: i.inserted_at], - limit: 1 + limit: 1, + select: struct(i, [:id, :image_type, :is_svg, :svg_content, :source_width]) ) SettingsCache.put_cached(:logo, logo) @@ -165,7 +166,8 @@ defmodule Berrypod.Media do from i in ImageSchema, where: i.image_type == "header", order_by: [desc: i.inserted_at], - limit: 1 + limit: 1, + select: struct(i, [:id, :image_type, :source_width]) ) SettingsCache.put_cached(:header, header) diff --git a/lib/berrypod/products.ex b/lib/berrypod/products.ex index 3cee8e9..d6b5e54 100644 --- a/lib/berrypod/products.ex +++ b/lib/berrypod/products.ex @@ -146,6 +146,14 @@ defmodule Berrypod.Products do [images: {pi_query, image: image_preload_query()}] end + # Skip provider_data (up to 72KB JSON) and description from listing queries — + # neither is used on product cards. Same idea as image_preload_query(). + @listing_fields Product.__schema__(:fields) -- [:provider_data, :description] + + defp listing_select(query) do + from(p in query, select: struct(p, ^@listing_fields)) + end + @doc """ Gets a single visible, active product by slug with full preloads (for detail page). """ @@ -179,6 +187,7 @@ defmodule Berrypod.Products do |> apply_sort(opts[:sort]) |> maybe_limit(opts[:limit]) |> maybe_exclude(opts[:exclude]) + |> listing_select() |> Repo.all() |> Repo.preload(listing_preloads()) end @@ -195,6 +204,7 @@ defmodule Berrypod.Products do |> apply_visible_filters(opts) |> apply_sort(opts[:sort]) |> maybe_exclude(opts[:exclude]) + |> listing_select() |> Berrypod.Pagination.paginate(page: opts[:page], per_page: opts[:per_page] || 24) %{pagination | items: Repo.preload(pagination.items, listing_preloads())} diff --git a/lib/berrypod/redirects.ex b/lib/berrypod/redirects.ex index cda6bc6..47eb716 100644 --- a/lib/berrypod/redirects.ex +++ b/lib/berrypod/redirects.ex @@ -67,16 +67,9 @@ defmodule Berrypod.Redirects do {:ok, %{to_path: to_path, status_code: status_code, id: id}} [] -> - case Repo.one(from r in Redirect, where: r.from_path == ^path) do - nil -> - :not_found - - redirect -> - put_cache(redirect.from_path, redirect.to_path, redirect.status_code, redirect.id) - - {:ok, - %{to_path: redirect.to_path, status_code: redirect.status_code, id: redirect.id}} - end + # Cache is warmed on startup with all redirects and kept in sync on + # create/update/delete, so a miss here means no redirect exists. + :not_found end end diff --git a/lib/berrypod/settings/settings_cache.ex b/lib/berrypod/settings/settings_cache.ex index 21c9cb6..841b2c2 100644 --- a/lib/berrypod/settings/settings_cache.ex +++ b/lib/berrypod/settings/settings_cache.ex @@ -21,6 +21,24 @@ defmodule Berrypod.Settings.SettingsCache do GenServer.start_link(__MODULE__, opts, name: __MODULE__) end + @doc """ + Creates the ETS table. Called from application.ex so the supervisor + process owns the table — it survives GenServer crashes. + """ + def create_table do + if :ets.whereis(@table_name) == :undefined do + :ets.new(@table_name, [ + :set, + :public, + :named_table, + read_concurrency: true, + write_concurrency: false + ]) + end + + @table_name + end + @doc """ Gets a cached setting by key. @@ -131,14 +149,8 @@ defmodule Berrypod.Settings.SettingsCache do @impl true def init(_opts) do - :ets.new(@table_name, [ - :set, - :public, - :named_table, - read_concurrency: true, - write_concurrency: false - ]) - + # Table is created in application.ex so it survives GenServer restarts + create_table() {:ok, %{}, {:continue, :warm}} end