cover/Elixir.WalletRisk.RiskProfileStore.html

1 defmodule WalletRisk.RiskProfileStore do
2 @moduledoc """
3 ETS-backed GenServer for RiskProfile persistence.
4
5 Tables:
6 - `:wallet_risk_profiles` — keyed by profile_id.
7 - `:wallet_risk_profile_user_idx` — set: {user_id, profile_id} (one profile per user).
8 """
9
10 use GenServer
11
12 alias WalletRisk.RiskProfile
13
14 @table :wallet_risk_profiles
15 @user_idx :wallet_risk_profile_user_idx
16
17 2 def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
18
19 @spec store(RiskProfile.t()) :: :ok | {:error, :duplicate_profile_id}
20 5 def store(%RiskProfile{} = p), do: GenServer.call(__MODULE__, {:store, p})
21
22 @spec get(profile_id :: String.t()) :: {:ok, RiskProfile.t()} | {:error, :not_found}
23
:-(
def get(id), do: GenServer.call(__MODULE__, {:get, id})
24
25 @spec get_by_user(user_id :: String.t()) :: {:ok, RiskProfile.t()} | {:error, :not_found}
26 9 def get_by_user(uid), do: GenServer.call(__MODULE__, {:get_by_user, uid})
27
28 @spec upsert_for_user(user_id :: String.t(), update_fn :: (RiskProfile.t() -> RiskProfile.t()), opts :: keyword()) ::
29 {:ok, RiskProfile.t()}
30
:-(
def upsert_for_user(user_id, update_fn, opts \\ []) do
31 17 GenServer.call(__MODULE__, {:upsert_for_user, user_id, update_fn, opts})
32 end
33
34 @spec update(RiskProfile.t()) :: :ok | {:error, :not_found}
35
:-(
def update(%RiskProfile{} = p), do: GenServer.call(__MODULE__, {:update, p})
36
37 @spec list_all() :: [RiskProfile.t()]
38
:-(
def list_all, do: GenServer.call(__MODULE__, :list_all)
39
40 @spec reset() :: :ok
41 32 def reset, do: GenServer.call(__MODULE__, :reset)
42
43 @impl true
44 def init(_opts) do
45 2 :ets.new(@table, [:set, :protected, :named_table])
46 2 :ets.new(@user_idx, [:set, :protected, :named_table])
47 {:ok, %{}}
48 end
49
50 @impl true
51 def handle_call({:store, p}, _from, state) do
52 5 if :ets.member(@table, p.profile_id) do
53
:-(
{:reply, {:error, :duplicate_profile_id}, state}
54 else
55 5 :ets.insert(@table, {p.profile_id, p})
56 5 :ets.insert(@user_idx, {p.user_id, p.profile_id})
57 5 {:reply, :ok, state}
58 end
59 end
60
61 @impl true
62 def handle_call({:get, id}, _from, state) do
63
:-(
result =
64 case :ets.lookup(@table, id) do
65
:-(
[{_, p}] -> {:ok, p}
66
:-(
[] -> {:error, :not_found}
67 end
68
:-(
{:reply, result, state}
69 end
70
71 @impl true
72 def handle_call({:get_by_user, uid}, _from, state) do
73 9 result =
74 case :ets.lookup(@user_idx, uid) do
75 [{_, pid}] ->
76 3 case :ets.lookup(@table, pid) do
77 3 [{_, p}] -> {:ok, p}
78
:-(
[] -> {:error, :not_found}
79 end
80 6 [] -> {:error, :not_found}
81 end
82 9 {:reply, result, state}
83 end
84
85 @impl true
86 def handle_call({:upsert_for_user, user_id, update_fn, opts}, _from, state) do
87 17 profile =
88 case :ets.lookup(@user_idx, user_id) do
89 [{_, pid}] ->
90 8 case :ets.lookup(@table, pid) do
91 8 [{_, p}] -> p
92
:-(
[] -> WalletRisk.RiskProfile.new(user_id, opts)
93 end
94 9 [] -> WalletRisk.RiskProfile.new(user_id, opts)
95 end
96
97 17 updated = update_fn.(profile)
98 15 :ets.insert(@table, {updated.profile_id, updated})
99 15 :ets.insert(@user_idx, {user_id, updated.profile_id})
100 15 {:reply, {:ok, updated}, state}
101 end
102
103 @impl true
104 def handle_call({:update, p}, _from, state) do
105
:-(
result =
106
:-(
case :ets.lookup(@table, p.profile_id) do
107 [{_, _}] ->
108
:-(
:ets.insert(@table, {p.profile_id, p})
109 :ok
110
:-(
[] ->
111 {:error, :not_found}
112 end
113
:-(
{:reply, result, state}
114 end
115
116 @impl true
117 def handle_call(:list_all, _from, state) do
118
:-(
items = :ets.tab2list(@table) |> Enum.map(fn {_, p} -> p end)
119
:-(
{:reply, items, state}
120 end
121
122 @impl true
123 def handle_call(:reset, _from, state) do
124 29 :ets.delete_all_objects(@table)
125 29 :ets.delete_all_objects(@user_idx)
126 29 {:reply, :ok, state}
127 end
128 end
Line Hits Source