# `PhoenixKit.Settings.History`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.16.0/lib/phoenix_kit/settings/history.ex#L1)

The history of site settings: what each setting was, and since when.

## Why

A stored instant does not say which regime wrote it. When the `time_zone`
setting moved from an integer offset to an IANA id and several modules
turned out to have added that value to other instants, the rows they had
written could not be repaired: nothing recorded when the setting changed
or what it was before. `phoenix_kit_settings.date_updated` holds the last
change only, and no settings writer logged anything.

## Where it lives

In the activity feed, as `setting.changed` entries that are **permanent**
(the pruner keeps them whatever their age), so there is one record of who
did what, and the admin's Activity page shows settings changes beside
everything else. The entry is inserted inside the settings write's
transaction and published to the feed's subscribers only after the commit
— nobody hears of a change that rolled back.

Every write through `PhoenixKit.Settings` that changes a value records
one entry: `metadata` carries the `key`, the value `from` and `to` (a
JSON setting as its encoded document), the `source` (`"settings"` for the
admin pages, `"system"` otherwise); `actor_uuid` is the person when one
made the change; `resource_uuid` is the setting row. The value before is
read under a row lock inside the write's transaction, so two racing
writers cannot both record the same old value. A write that leaves the
value as it was records nothing. A restricted (secret) setting records
that a change happened — `restricted: true`, both values withheld.

## Reading it

  * `list/2` — the changes to one key, newest first.
  * `value_at/2` — the value a key had at an instant: the answer to "which
    timezone was this site on when that row was written?".

# `action`

```elixir
@spec action() :: String.t()
```

The activity action a settings change is logged under.

# `list`

```elixir
@spec list(
  String.t(),
  keyword()
) :: [PhoenixKit.Activity.Entry.t()]
```

The changes to `key`, newest first. `:limit` (default 100).

# `lock_current`

```elixir
@spec lock_current(String.t()) :: PhoenixKit.Settings.Setting.t() | nil
```

The current row for `key`, locked for the rest of the transaction — the
"before" a writer hands to `record/3`. `nil` when the key does not exist.
Call inside a transaction.

# `publish`

```elixir
@spec publish(PhoenixKit.Activity.Entry.t() | :unchanged) :: :ok
```

Publishes a recorded entry to the feed's subscribers — call after the
transaction that wrote it has committed. `:unchanged` publishes nothing.

# `record`

```elixir
@spec record(
  PhoenixKit.Settings.Setting.t() | nil,
  PhoenixKit.Settings.Setting.t(),
  keyword()
) ::
  {:ok, PhoenixKit.Activity.Entry.t() | :unchanged}
  | {:error, Ecto.Changeset.t()}
```

Records the change a settings write made, or nothing when it changed no
value.

`before` is the row as it was — read under a row lock inside the write's
transaction (`lock_current/1`) — or `nil` when the key did not exist;
`written` the row as stored. Options: `:actor_uuid` (nil for a module or
a migration), `:source` (`"settings"` for the admin pages; default
`"system"`).

Returns `{:ok, %Activity.Entry{}}`, `{:ok, :unchanged}` or
`{:error, changeset}`.

# `value_at`

```elixir
@spec value_at(String.t(), DateTime.t() | NaiveDateTime.t()) :: String.t() | nil
```

The value `key` had at `instant`.

The newest change at or before the instant says what the value became;
with none, the oldest change after it says what the value was before
anything was recorded; with no history at all, the current value — a
setting that was never changed since recording began is ASSUMED to have
always been what it is now. A JSON setting is its encoded document, the
same shape the history holds. A `DateTime` in any zone is the instant it
names, not its wall clock. A restricted key answers `nil` for every
instant: its values are withheld from the history and this must not
become the way around that — and a key that WAS restricted when a change
was recorded answers `nil` for that period even after it stops being
restricted, because the value was never written down.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
