berrypod/lib/simpleshop_theme/settings.ex
Jamey Greenwood a401365943 feat: add Settings and Media contexts with theme settings schema
- Create settings table for site-wide key-value configuration
- Create images table for BLOB storage of logo/header images
- Add Setting schema with JSON/string/integer/boolean support
- Add ThemeSettings embedded schema with all theme options
- Add Settings context with get/put/update operations
- Add Media context for image uploads and retrieval
- Add Image schema with SVG detection and storage
- Add 9 curated theme presets (gallery, studio, boutique, etc.)
- Add comprehensive tests for Settings and Media contexts
- Add seeds with default Studio preset
- All tests passing (29 tests, 0 failures)
2025-12-30 21:35:52 +00:00

134 lines
3.3 KiB
Elixir

defmodule SimpleshopTheme.Settings do
@moduledoc """
The Settings context for managing site-wide configuration.
"""
import Ecto.Query, warn: false
alias SimpleshopTheme.Repo
alias SimpleshopTheme.Settings.{Setting, ThemeSettings}
@doc """
Gets a setting by key with an optional default value.
## Examples
iex> get_setting("site_name", "My Shop")
"My Shop"
"""
def get_setting(key, default \\ nil) do
case Repo.get_by(Setting, key: key) do
nil -> default
setting -> decode_value(setting)
end
end
@doc """
Sets a setting value by key.
## Examples
iex> put_setting("site_name", "My Awesome Shop")
{:ok, %Setting{}}
"""
def put_setting(key, value, value_type \\ "string") do
encoded_value = encode_value(value, value_type)
%Setting{key: key}
|> Setting.changeset(%{key: key, value: encoded_value, value_type: value_type})
|> Repo.insert(
on_conflict: {:replace, [:value, :value_type, :updated_at]},
conflict_target: :key
)
end
@doc """
Gets the theme settings as a ThemeSettings struct.
## Examples
iex> get_theme_settings()
%ThemeSettings{mood: "neutral", typography: "clean", ...}
"""
def get_theme_settings do
case get_setting("theme_settings") do
nil ->
# Return defaults
%ThemeSettings{}
settings_map when is_map(settings_map) ->
settings_map
|> atomize_keys()
|> then(&struct(ThemeSettings, &1))
end
end
@doc """
Updates the theme settings.
## Examples
iex> update_theme_settings(%{mood: "dark", typography: "modern"})
{:ok, %ThemeSettings{}}
"""
def update_theme_settings(attrs) when is_map(attrs) do
current = get_theme_settings()
changeset = ThemeSettings.changeset(current, attrs)
if changeset.valid? do
settings = Ecto.Changeset.apply_changes(changeset)
json = Jason.encode!(settings)
put_setting("theme_settings", json, "json")
{:ok, settings}
else
{:error, changeset}
end
end
@doc """
Applies a preset to theme settings.
## Examples
iex> apply_preset(:gallery)
{:ok, %ThemeSettings{}}
"""
def apply_preset(preset_name) when is_atom(preset_name) do
preset = SimpleshopTheme.Theme.Presets.get(preset_name)
if preset do
update_theme_settings(preset)
else
{:error, :preset_not_found}
end
end
# Private helpers
defp decode_value(%Setting{value: value, value_type: "json"}), do: Jason.decode!(value)
defp decode_value(%Setting{value: value, value_type: "integer"}), do: String.to_integer(value)
defp decode_value(%Setting{value: value, value_type: "boolean"}),
do: value == "true"
defp decode_value(%Setting{value: value, value_type: "string"}), do: value
defp encode_value(value, "json") when is_binary(value), do: value
defp encode_value(value, "json"), do: Jason.encode!(value)
defp encode_value(value, "integer") when is_integer(value), do: Integer.to_string(value)
defp encode_value(value, "boolean") when is_boolean(value), do: Atom.to_string(value)
defp encode_value(value, "string") when is_binary(value), do: value
defp atomize_keys(map) when is_map(map) do
Map.new(map, fn
{key, value} when is_binary(key) -> {String.to_atom(key), value}
{key, value} -> {key, value}
end)
end
end