# `PhoenixKit.Activity`
[🔗](https://github.com/BeamLabEU/phoenix_kit/blob/v2.14.2/lib/phoenix_kit/activity/activity.ex#L1)

Activity feed for tracking business-level actions across the platform.

Provides a simple API for logging and querying activities. Any module can call
`Activity.log/1` to record an action. The admin dashboard shows a real-time
activity stream.

## Usage

    PhoenixKit.Activity.log(%{
      action: "post.created",
      actor_uuid: user.uuid,
      resource_type: "post",
      resource_uuid: post.uuid,
      metadata: %{"title" => post.title}
    })

## Action naming convention

Use dotted format: `resource.verb` — e.g., "post.created", "comment.liked",
"user.registered", "password.changed", "role.assigned".

# `action_badge_color`

Returns a CSS badge class based on the action verb.

# `count`

Counts activities matching the given filters.

# `full_log_access?`

```elixir
@spec full_log_access?(PhoenixKit.Users.Auth.Scope.t() | nil) :: boolean()
```

Whether `scope` may read the WHOLE activity log — every user's actions —
rather than only its own.

Administrators qualify: the Admin or Owner role, or any `"*"` superadmin
role. Everyone else (a custom role that merely holds `dashboard`) is scoped
to their own actions. The activity LiveViews share this as the single gate
for both the list and the single-entry page.

# `get_entry`

Gets a single activity entry by UUID with preloaded associations.

# `get_entry!`

Gets a single activity entry by UUID. Raises if not found.

# `humanize_metadata_value`

Renders an activity-metadata VALUE as human-readable text, tolerant of the
shapes host apps store.

The admin feed and detail page display arbitrary per-module metadata, so this
must never raise `Protocol.UndefinedError` (`String.Chars`/`to_string` on a
Map): a field-change diff carries a nested `%{"from" => _, "to" => _}` map,
which is rendered as `"1 → 2"`; any other map is rendered as
`"key: value, ..."`. Legacy entries whose scalar was serialised via `inspect`
(`"Decimal.new(\"1\")"`) are unwrapped to their inner value so old rows read
cleanly instead of leaking Elixir syntax.

# `list`

Lists activities with filtering and pagination.

## Options

- `:action` — filter by action string (exact match or prefix with "post.*")
- `:actor_uuid` — filter by who performed the action
- `:resource_type` — filter by resource type
- `:resource_uuid` — filter by the specific resource's UUID (scope to one resource)
- `:target_uuid` — filter by who was affected
- `:since` — filter activities after this datetime
- `:until` — filter activities before this datetime
- `:page` — page number (default: 1)
- `:per_page` — items per page (default: 50)
- `:preload` — associations to preload (default: [:actor])

# `list_action_types`

Returns distinct action types that have been logged.

# `list_for_user`

Lists activities for a specific user (as actor).

# `list_modes`

Returns distinct modes that have been logged.

# `list_modules`

Returns distinct modules that have been logged.

# `list_resource_types`

Returns distinct resource types that have been logged.

# `log`

Logs an activity.

## Required fields

- `:action` — dotted action string (e.g., "post.created")

## Optional fields

- `:actor_uuid` — who performed the action
- `:resource_type` — type of resource acted on
- `:resource_uuid` — UUID of the resource
- `:target_uuid` — who was affected (e.g., follow target)
- `:metadata` — map of additional context

Returns `{:ok, entry}` or `{:error, changeset}`. Failures are logged but never crash.

# `log_user_change`

Logs a user change with automatic from/to diff extraction from a changeset.

Extracts changed fields from the changeset and builds `field_from` / `field_to`
metadata pairs. Skips logging if nothing actually changed.

## Options

- `:actor_uuid` — who performed the action (default: the user's own UUID)
- `:target_uuid` — who was affected (default: nil)
- `:mode` — "auto" or "manual" (default: "auto")
- `:actor_role` — "user" or "admin" (default: "user")
- `:extra_metadata` — additional metadata to merge in (default: %{})
- `:skip_fields` — fields to exclude from diff (default: [:custom_fields])

# `mode_badge_color`

Returns a CSS badge class based on the mode.

# `own_entry?`

```elixir
@spec own_entry?(PhoenixKit.Users.Auth.Scope.t() | nil, PhoenixKit.Activity.Entry.t()) ::
  boolean()
```

Whether `entry` is `scope`'s OWN activity.

"Own" is defined by AUTHORSHIP: the scope's user is the entry's `actor_uuid`
(the account that performed the action). A record where the user is only the
`target_uuid` — someone else acted on or for them — is NOT their own and stays
hidden from a non-administrator.

This is the single definition the audit log enforces in two places, and they
must not drift: the list pins `actor_uuid` to the user (`Activity.Index`) and
the single-entry page gates on this predicate (`Activity.Show`). If "own" ever
needs to include target-side records, change it HERE and switch the list's
filter in lock-step — do not fork the rule per view.

# `prune`

Deletes activities older than the given number of days.

# `pubsub_topic`

PubSub topic for activity events.

# `recent`

Returns the N most recent activities.

# `resolve_resource_users`

Resolves resource info for entries where `resource_type` is "user".

Returns a map of `resource_uuid => %{email: ..., first_name: ..., last_name: ...}`.
Batch-queries to avoid N+1.

# `retention_days`

Returns the configured retention period in days.

---

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