defmodule DaProductApp.GlobalSettings do @moduledoc """ Caches global settings from `global_table` in ShukriaMms database. - Loads settings at startup - Periodically refreshes (default 10 minutes) - Exposes `get_all/0`, `get/1` and `refresh/0` API Settings are stored in a public ETS table (`read_concurrency: true`) so that reader processes (`get/1`, `get_all/0`) fetch values concurrently and lock-free, without serializing through the GenServer. The GenServer is the sole writer: it loads/refreshes the data and writes it into ETS. The cache is a flat `%{string_key => value}` map so `get_all/0` stays backward compatible with existing consumers (e.g. `TransactionRules.merge_settings_with_global/2`) that expect scalar values. `global_table` rows are `{id, config_key, config_value, group, created_at, updated_at}` - there is no separate "extended value" column; any list-style config (e.g. `POS_ENTRY_BLOCKED`) stores its comma-separated value directly in `config_value`. """ use GenServer require Logger alias DaProductApp.Repos.ShukriaMmsRepo import Ecto.Query @name __MODULE__ @table __MODULE__ # Single key under which the full settings map is stored in ETS. @settings_key :settings @refresh_ms Application.get_env(:da_product_app, :global_settings_refresh_ms, :timer.minutes(10)) # Public API def start_link(_opts) do GenServer.start_link(__MODULE__, %{}, name: @name) end def get_all do case :ets.lookup(@table, @settings_key) do [{@settings_key, map}] -> map _ -> %{} end rescue ArgumentError -> # Table not yet created (e.g. process not started) - fail safe to empty. %{} end def get(key) do Map.get(get_all(), to_string(key)) end def refresh do GenServer.cast(@name, :refresh) end # Callbacks @impl true def init(_args) do :ets.new(@table, [:named_table, :set, :public, read_concurrency: true]) store(load_once(%{})) if is_integer(@refresh_ms) and @refresh_ms > 0 do :timer.send_interval(@refresh_ms, :refresh) end {:ok, %{}} end @impl true def handle_cast(:refresh, state) do store(load_once(get_all())) {:noreply, state} end @impl true def handle_info(:refresh, state) do store(load_once(get_all())) {:noreply, state} end defp store(map) do :ets.insert(@table, {@settings_key, map}) map end defp load_once(current_state) do query = from(g in "global_table", select: {g.config_key, g.config_value}) case ShukriaMmsRepo.all(query) do rows when is_list(rows) -> Enum.reduce(rows, %{}, fn {k, v}, acc -> Map.put(acc, to_string(k), v) end) _ -> Logger.error("GlobalSettings: unexpected response loading settings, keeping last-known state") current_state end rescue e -> Logger.error("GlobalSettings load error: #{inspect(e)} - keeping last-known state") current_state end end