defmodule ShootingEventPhx.Domain.Settings do @moduledoc false import Ecto.Query alias ShootingEventPhx.Repo alias ShootingEventPhx.Domain.AppSetting @setting_view_proof_in_view "view_proof_in_view" @setting_live_active_view "live_active_view" @setting_live_show_group_live "live_show_group_live" @setting_live_group_display_mode "live_group_display_mode" @setting_live_group_fixed_code "live_group_fixed_code" @setting_live_show_prelim_tie "live_show_prelim_tie" @setting_live_show_prelim_overall "live_show_prelim_overall" @setting_live_show_final_groups "live_show_final_groups" @setting_live_final_display_mode "live_final_display_mode" @setting_live_final_fixed_group "live_final_fixed_group" @setting_live_show_final_tie "live_show_final_tie" @setting_live_show_podium "live_show_podium" @setting_live_rotation_interval "live_rotation_interval_sec" @setting_live_rotation_players "live_rotation_player_count" @allowed_live_views MapSet.new([ "group_live", "prelim_tie", "prelim_overall", "final_groups", "final_tie", "podium" ]) def default_live_mode do %{ "activeView" => "group_live", "showGroupLive" => true, "groupDisplayMode" => "rotate", "groupFixedCode" => "", "showPrelimTie" => true, "showPrelimOverall" => true, "showFinalGroups" => true, "finalDisplayMode" => "rotate", "finalFixedGroup" => 1, "showFinalTie" => true, "showPodium" => true, "rotationIntervalSec" => 5, "rotationPlayerCount" => 12 } end def read_settings do all = Repo.all(from s in AppSetting, select: {s.key, s.value}) |> Map.new() live = default_live_mode() live = live |> Map.put("showGroupLive", setting_bool(Map.get(all, @setting_live_show_group_live, "1"))) |> Map.put( "activeView", normalize_live_active_view( Map.get(all, @setting_live_active_view, live["activeView"]), live["activeView"] ) ) |> Map.put( "groupDisplayMode", normalize_display_mode( Map.get(all, @setting_live_group_display_mode, live["groupDisplayMode"]), live["groupDisplayMode"] ) ) |> Map.put( "groupFixedCode", String.upcase(String.trim(Map.get(all, @setting_live_group_fixed_code, ""))) ) |> Map.put("showPrelimTie", setting_bool(Map.get(all, @setting_live_show_prelim_tie, "1"))) |> Map.put( "showPrelimOverall", setting_bool(Map.get(all, @setting_live_show_prelim_overall, "1")) ) |> Map.put( "showFinalGroups", setting_bool(Map.get(all, @setting_live_show_final_groups, "1")) ) |> Map.put( "finalDisplayMode", normalize_display_mode( Map.get(all, @setting_live_final_display_mode, live["finalDisplayMode"]), live["finalDisplayMode"] ) ) |> Map.put( "finalFixedGroup", normalize_final_fixed_group(Map.get(all, @setting_live_final_fixed_group, "1"), 1) ) |> Map.put("showFinalTie", setting_bool(Map.get(all, @setting_live_show_final_tie, "1"))) |> Map.put("showPodium", setting_bool(Map.get(all, @setting_live_show_podium, "1"))) |> Map.put( "rotationIntervalSec", normalize_rotation_interval(Map.get(all, @setting_live_rotation_interval, "5"), 5) ) |> Map.put( "rotationPlayerCount", normalize_rotation_player_count(Map.get(all, @setting_live_rotation_players, "12"), 12) ) %{ "viewProofInView" => setting_bool(Map.get(all, @setting_view_proof_in_view, "0")), "liveMode" => live } end def update_settings(%{} = req) do has_update = Map.has_key?(req, "viewProofInView") or Map.has_key?(req, :viewProofInView) or Map.has_key?(req, "liveMode") or Map.has_key?(req, :liveMode) if not has_update do {:error, "at least one settings field is required"} else Repo.transaction(fn -> req |> get_value("viewProofInView") |> maybe_upsert(@setting_view_proof_in_view) live_patch = get_value(req, "liveMode") if is_map(live_patch), do: persist_live_patch(live_patch) end) |> case do {:ok, _} -> :ok {:error, %Ecto.ConstraintError{} = e} -> {:error, Exception.message(e)} {:error, reason} when is_binary(reason) -> {:error, reason} {:error, reason} -> {:error, inspect(reason)} end end end defp persist_live_patch(patch) do patch |> get_value("activeView") |> maybe_upsert(@setting_live_active_view, fn v -> normalize_live_active_view(v, "group_live") end) patch |> get_value("showGroupLive") |> maybe_upsert(@setting_live_show_group_live) patch |> get_value("groupDisplayMode") |> maybe_upsert(@setting_live_group_display_mode, fn v -> normalize_display_mode(v, "rotate") end) patch |> get_value("groupFixedCode") |> maybe_upsert(@setting_live_group_fixed_code, fn v -> v |> to_string() |> String.trim() |> String.upcase() end) patch |> get_value("showPrelimTie") |> maybe_upsert(@setting_live_show_prelim_tie) patch |> get_value("showPrelimOverall") |> maybe_upsert(@setting_live_show_prelim_overall) patch |> get_value("showFinalGroups") |> maybe_upsert(@setting_live_show_final_groups) patch |> get_value("finalDisplayMode") |> maybe_upsert(@setting_live_final_display_mode, fn v -> normalize_display_mode(v, "rotate") end) patch |> get_value("finalFixedGroup") |> maybe_upsert(@setting_live_final_fixed_group, fn v -> normalize_final_fixed_group(v, 1) end) patch |> get_value("showFinalTie") |> maybe_upsert(@setting_live_show_final_tie) patch |> get_value("showPodium") |> maybe_upsert(@setting_live_show_podium) patch |> get_value("rotationIntervalSec") |> maybe_upsert(@setting_live_rotation_interval, fn v -> normalize_rotation_interval(v, 5) end) patch |> get_value("rotationPlayerCount") |> maybe_upsert(@setting_live_rotation_players, fn v -> normalize_rotation_player_count(v, 12) end) end defp maybe_upsert(nil, _key), do: :ok defp maybe_upsert(value, key) do normalized = setting_string(value) Repo.insert!( %AppSetting{key: key, value: normalized}, on_conflict: [set: [value: normalized, updated_at: DateTime.utc_now()]], conflict_target: :key ) end defp maybe_upsert(nil, _key, _fun), do: :ok defp maybe_upsert(value, key, normalize_fun) do normalized = value |> normalize_fun.() |> setting_string() maybe_upsert(normalized, key) end defp get_value(map, key) when is_map(map) do cond do Map.has_key?(map, key) -> Map.get(map, key) Map.has_key?(map, String.to_atom(key)) -> Map.get(map, String.to_atom(key)) true -> nil end end defp setting_bool(value) do case value |> to_string() |> String.trim() |> String.downcase() do "1" -> true "true" -> true "yes" -> true "on" -> true _ -> false end end defp setting_string(v) when is_boolean(v), do: if(v, do: "1", else: "0") defp setting_string(v) when is_integer(v), do: Integer.to_string(v) defp setting_string(v), do: v |> to_string() defp normalize_display_mode(value, fallback) do case value |> to_string() |> String.trim() |> String.downcase() do "fixed" -> "fixed" "rotate" -> "rotate" _ -> if(String.downcase(to_string(fallback)) == "fixed", do: "fixed", else: "rotate") end end defp normalize_final_fixed_group(value, fallback) do parsed = case Integer.parse(to_string(value)) do {v, _} -> v _ -> fallback end if parsed == 2, do: 2, else: 1 end defp normalize_rotation_interval(value, fallback) do parsed = case Integer.parse(to_string(value)) do {v, _} -> v _ -> fallback end parsed |> max(3) |> min(30) end defp normalize_rotation_player_count(value, fallback) do parsed = case Integer.parse(to_string(value)) do {v, _} -> v _ -> fallback end parsed |> max(3) |> min(40) end defp normalize_live_active_view(value, fallback) do normalized = value |> to_string() |> String.trim() |> String.downcase() cond do MapSet.member?(@allowed_live_views, normalized) -> normalized MapSet.member?(@allowed_live_views, to_string(fallback)) -> fallback true -> "group_live" end end end