cover/Elixir.DaProductApp.Settings.PlatformSetting.html

1 defmodule DaProductApp.Settings.PlatformSetting do
2 @moduledoc """
3 Schema for platform settings with dynamic configuration management.
4
5 This table stores all platform configuration that can be changed
6 without code deployment, supporting the gradual migration strategy.
7 """
8 use Ecto.Schema
9 import Ecto.Changeset
10
11 @value_types ~w(string number boolean json)
12
13
:-(
schema "platform_settings" do
14 field :category, :string
15 field :key, :string
16 field :value, :string
17 field :value_type, :string, default: "string"
18 field :description, :string
19 field :is_encrypted, :boolean, default: false
20 field :is_active, :boolean, default: true
21
22 timestamps(type: :utc_datetime)
23 end
24
25 @required_fields ~w(category key value)a
26 @optional_fields ~w(value_type description is_encrypted is_active)a
27
28 def changeset(setting, attrs) do
29 setting
30 |> cast(attrs, @required_fields ++ @optional_fields)
31 |> validate_required(@required_fields)
32 |> validate_inclusion(:value_type, @value_types)
33 |> validate_length(:category, max: 50)
34 |> validate_length(:key, max: 100)
35 |> unique_constraint([:category, :key], name: :platform_settings_category_key_index)
36
:-(
|> validate_value_format()
37 end
38
39 @doc """
40 Parse the stored value according to its type.
41 """
42
:-(
def parse_value(%__MODULE__{value: value, value_type: "string"}), do: value
43 def parse_value(%__MODULE__{value: value, value_type: "number"}) do
44
:-(
case Float.parse(value) do
45
:-(
{num, ""} -> num
46
:-(
_ -> {:error, :invalid_number}
47 end
48 end
49 def parse_value(%__MODULE__{value: value, value_type: "boolean"}) do
50
:-(
case value do
51
:-(
"true" -> true
52
:-(
"false" -> false
53
:-(
_ -> {:error, :invalid_boolean}
54 end
55 end
56 def parse_value(%__MODULE__{value: value, value_type: "json"}) do
57
:-(
case Jason.decode(value) do
58
:-(
{:ok, parsed} -> parsed
59
:-(
{:error, _} -> {:error, :invalid_json}
60 end
61 end
62
63 @doc """
64 Encode a value for storage based on its type.
65 """
66
:-(
def encode_value(value, "string") when is_binary(value), do: value
67
:-(
def encode_value(value, "number") when is_number(value), do: to_string(value)
68
:-(
def encode_value(value, "boolean") when is_boolean(value), do: to_string(value)
69
:-(
def encode_value(value, "json"), do: Jason.encode!(value)
70
:-(
def encode_value(_, _), do: {:error, :invalid_value_type_combination}
71
72 defp validate_value_format(changeset) do
73
:-(
value = get_field(changeset, :value)
74
:-(
value_type = get_field(changeset, :value_type)
75
76
:-(
case {value, value_type} do
77
:-(
{nil, _} -> changeset
78 {val, "number"} ->
79
:-(
case Float.parse(val) do
80
:-(
{_, ""} -> changeset
81
:-(
_ -> add_error(changeset, :value, "must be a valid number")
82 end
83 {val, "boolean"} ->
84
:-(
if val in ["true", "false"] do
85
:-(
changeset
86 else
87
:-(
add_error(changeset, :value, "must be 'true' or 'false'")
88 end
89 {val, "json"} ->
90
:-(
case Jason.decode(val) do
91
:-(
{:ok, _} -> changeset
92
:-(
{:error, _} -> add_error(changeset, :value, "must be valid JSON")
93 end
94
:-(
_ -> changeset
95 end
96 end
97 end
Line Hits Source