PhoenixKit.Settings.History (phoenix_kit v2.17.0)

Copy Markdown View Source

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?".

Summary

Functions

The activity action a settings change is logged under.

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

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.

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

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

The value key had at instant.

Functions

action()

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

The activity action a settings change is logged under.

list(key, opts \\ [])

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

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

lock_current(key)

@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(entry)

@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(before, written, opts \\ [])

@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(key, instant)

@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.