Files
Beam/lib/forum/log_store.ex
2026-06-10 11:51:56 -05:00

62 lines
1.5 KiB
Elixir

defmodule Forum.LogStore do
@moduledoc """
Persists request and hosting logs to a JSON file.
"""
use GenServer
@max_entries 1_000
def start_link(opts) do
GenServer.start_link(__MODULE__, opts, name: __MODULE__)
end
def add(entry) when is_map(entry) do
GenServer.cast(__MODULE__, {:add, entry})
end
def list(limit \\ 200) do
GenServer.call(__MODULE__, {:list, limit})
end
@impl true
def init(_opts) do
path = Path.expand("priv/logs/requests.json", File.cwd!())
File.mkdir_p!(Path.dirname(path))
{:ok, %{path: path, entries: read_entries(path)}}
end
@impl true
def handle_cast({:add, entry}, state) do
entry =
entry
|> Map.put_new("id", System.unique_integer([:positive, :monotonic]))
|> Map.put_new("time", DateTime.utc_now() |> DateTime.to_iso8601())
entries = [entry | state.entries] |> Enum.take(@max_entries)
write_entries!(state.path, entries)
{:noreply, %{state | entries: entries}}
end
@impl true
def handle_call({:list, limit}, _from, state) do
{:reply, Enum.take(state.entries, limit), state}
end
defp read_entries(path) do
with {:ok, raw} <- File.read(path),
{:ok, entries} when is_list(entries) <- Jason.decode(raw) do
entries
else
_ -> []
end
end
defp write_entries!(path, entries) do
tmp_path = path <> ".tmp"
File.write!(tmp_path, Jason.encode!(entries, pretty: true))
File.rename!(tmp_path, path)
end
end