PhoenixKit.Notifications.Prefs (phoenix_kit v1.7.102)

Copy Markdown View Source

Per-user notification preferences.

Preferences live inside the user's existing custom_fields JSONB column under the "notification_preferences" key — a flat %{type_key => boolean} map. Unset keys default to the type's own :default flag via PhoenixKit.Notifications.Types.default_for/1, so behaviour before a user has opted in anywhere is unchanged.

The filter function user_wants?/2 is called once per notification fan-out from PhoenixKit.Notifications.maybe_create_from_activity/1. It's designed to fail open: any lookup error, unknown action, or malformed prefs map returns true so the system never silently drops a notification due to a bad row.

Summary

Functions

Returns the user's raw preference map.

Saves a preference map into the user's custom_fields.

Answers "would this user want a notification for this action?"

Functions

get(uuid)

@spec get(PhoenixKit.Users.Auth.User.t() | String.t()) :: %{
  optional(String.t()) => boolean()
}

Returns the user's raw preference map.

Accepts either a loaded %User{} (zero DB work) or a UUID (one lookup). Missing preferences return %{}.

update(user, prefs)

@spec update(PhoenixKit.Users.Auth.User.t(), %{optional(String.t()) => boolean()}) ::
  {:ok, PhoenixKit.Users.Auth.User.t()} | {:error, Ecto.Changeset.t()}

Saves a preference map into the user's custom_fields.

prefs is the full map of %{type_key => boolean} — callers should include entries for every rendered toggle, since the storage is a replace, not a merge-at-the-key level. Other custom-field keys are preserved (we merge at the custom_fields level, not inside it).

user_wants?(user_uuid, action)

@spec user_wants?(String.t(), String.t()) :: boolean()

Answers "would this user want a notification for this action?"

Returns true on any ambiguity so new actions, unknown prefs, or a failing user lookup never cause a silent drop.

Order of checks:

  1. Unknown action (no registered type) → true (fail open)
  2. Prefs map has the type key set to true or false → that value
  3. No entry → the type's :default from Types.default_for/1
  4. Any raise → logged as a warning and returns true