cover/Elixir.WalletSettlement.SettlementStore.html

1 defmodule WalletSettlement.SettlementStore do
2 @moduledoc """
3 ETS-backed GenServer for settlement batch persistence.
4
5 Tables:
6 - `:wallet_settlement_batches` — batches keyed by batch_id.
7 - `:wallet_settlement_batch_idx` — bag index: {status, batch_id}.
8 """
9 use GenServer
10
11 alias WalletSettlement.SettlementBatch
12
13 @batches_table :wallet_settlement_batches
14 @idx_table :wallet_settlement_batch_idx
15
16
:-(
def start_link(opts), do: GenServer.start_link(__MODULE__, opts, name: __MODULE__)
17
18 @doc "Stores a new batch. Returns `:ok` or `{:error, :duplicate_batch_id}`."
19 @spec store(SettlementBatch.t()) :: :ok | {:error, :duplicate_batch_id}
20 7 def store(%SettlementBatch{} = batch), do: GenServer.call(__MODULE__, {:store, batch})
21
22 @doc "Retrieves a batch by batch_id."
23 @spec get(String.t()) :: {:ok, SettlementBatch.t()} | {:error, :not_found}
24 11 def get(batch_id), do: GenServer.call(__MODULE__, {:get, batch_id})
25
26 @doc "Updates an existing batch."
27 @spec update(SettlementBatch.t()) :: :ok | {:error, :not_found}
28 8 def update(%SettlementBatch{} = batch), do: GenServer.call(__MODULE__, {:update, batch})
29
30 @doc "Lists all batches with a given status."
31 @spec list_by_status(atom()) :: [SettlementBatch.t()]
32
:-(
def list_by_status(status), do: GenServer.call(__MODULE__, {:list_by_status, status})
33
34 @doc "Lists all batches."
35 @spec list_all() :: [SettlementBatch.t()]
36
:-(
def list_all, do: GenServer.call(__MODULE__, :list_all)
37
38 @doc "Resets all store state. Test use only."
39 9 def reset, do: GenServer.call(__MODULE__, :reset)
40
41 @impl true
42 def init(_opts) do
43
:-(
:ets.new(@batches_table, [:set, :protected, :named_table])
44
:-(
:ets.new(@idx_table, [:bag, :protected, :named_table])
45 {:ok, %{}}
46 end
47
48 @impl true
49 def handle_call({:store, batch}, _from, state) do
50 7 if :ets.member(@batches_table, batch.batch_id) do
51
:-(
{:reply, {:error, :duplicate_batch_id}, state}
52 else
53 7 :ets.insert(@batches_table, {batch.batch_id, batch})
54 7 :ets.insert(@idx_table, {batch.status, batch.batch_id})
55 7 {:reply, :ok, state}
56 end
57 end
58
59 @impl true
60 def handle_call({:get, batch_id}, _from, state) do
61 11 result = case :ets.lookup(@batches_table, batch_id) do
62 7 [{_, batch}] -> {:ok, batch}
63 4 [] -> {:error, :not_found}
64 end
65 11 {:reply, result, state}
66 end
67
68 @impl true
69 def handle_call({:update, batch}, _from, state) do
70 8 result = case :ets.lookup(@batches_table, batch.batch_id) do
71 [{_, old}] ->
72 8 :ets.delete_object(@idx_table, {old.status, old.batch_id})
73 8 :ets.insert(@batches_table, {batch.batch_id, batch})
74 8 :ets.insert(@idx_table, {batch.status, batch.batch_id})
75 :ok
76
:-(
[] -> {:error, :not_found}
77 end
78 8 {:reply, result, state}
79 end
80
81 @impl true
82 def handle_call({:list_by_status, status}, _from, state) do
83
:-(
ids = :ets.lookup(@idx_table, status) |> Enum.map(fn {_, id} -> id end)
84
:-(
batches = Enum.flat_map(ids, fn id ->
85
:-(
case :ets.lookup(@batches_table, id) do
86
:-(
[{_, b}] -> [b]
87
:-(
[] -> []
88 end
89 end)
90
:-(
{:reply, batches, state}
91 end
92
93 @impl true
94 def handle_call(:list_all, _from, state) do
95
:-(
batches = :ets.tab2list(@batches_table) |> Enum.map(fn {_, b} -> b end)
96
:-(
{:reply, batches, state}
97 end
98
99 @impl true
100 def handle_call(:reset, _from, state) do
101 9 :ets.delete_all_objects(@batches_table)
102 9 :ets.delete_all_objects(@idx_table)
103 9 {:reply, :ok, state}
104 end
105 end
Line Hits Source