PhoenixKitStaff.Web.Helpers (PhoenixKitStaff v0.2.0)

Copy Markdown View Source

Cross-LV helpers shared by the staff admin LiveViews.

log_operation_error/3 — failure-side audit rows

Staff's documented architectural choice is that PhoenixKitStaff.Activity is success-only at the call site (see CLAUDE.md "Activity logging"). The post-Apr pipeline expects every user-driven mutation to leave an audit row on BOTH :ok AND :error branches so a DB outage / FK violation / constraint failure can't silently erase admin clicks from the activity feed.

The catalogue module's Batch 4 (canonical reference at phoenix_kit_catalogue/lib/phoenix_kit_catalogue/web/helpers.ex) resolves the tension by writing the failure-side row at the LiveView layer, not in the context. Same intent here:

  • One edit point in this helper instead of ~10 LV mutation sites
  • Same action atom the success path would have used (e.g. staff.person_deleted)
  • metadata.db_pending: true so audit-feed readers can distinguish attempted-but-failed from completed actions
  • PII-safe metadata: changeset reasons land error-key field names only (no values), atom reasons land the atom string, other shapes get error_kind: "other"

Helper only fires from handle_event {:error, _} branches — validate cycles never reach it (they're handled by the form's assign_form/2 cycle, no audit row needed for keystrokes).

Summary

Functions

Writes a failure-side activity row for a destructive/mutating operation.

Functions

log_operation_error(action, socket, opts)

@spec log_operation_error(String.t(), Phoenix.LiveView.Socket.t(), keyword()) ::
  :ok | :activity_unavailable | {:ok, struct()} | {:error, any()}

Writes a failure-side activity row for a destructive/mutating operation.

Required opts

  • :resource_type — string, e.g. "staff_person" / "department"
  • :reason — the {:error, reason} value from the context call

Optional opts

  • :resource_uuid — uuid of the record the operation targeted. Optional because failed CREATE submissions have no uuid yet — in that case the audit row records intent without a target.
  • :target_uuid — second-party uuid for membership operations (the user being added/removed, etc.)
  • :metadata — extra metadata (must be PII-safe). Merged UNDER the helper's own db_pending / error_kind / error_keys / error_atom keys — caller-supplied collisions on those keys are ignored so the audit-feed contract stays stable.

Returns the underlying Activity.log/2 return value (:ok, {:ok, _entry}, {:error, _}, :activity_unavailable); never raises.