Packages
Projects module for PhoenixKit — projects, reusable tasks, assignments, and dependencies.
Current section
Files
Jump to
Current section
Files
lib/phoenix_kit_projects/activity.ex
defmodule PhoenixKitProjects.Activity do
@moduledoc "Activity logging wrapper for the Projects module."
require Logger
@module "projects"
@doc "Logs a projects activity entry via `PhoenixKit.Activity`. Swallows errors so it never crashes the caller."
@spec log(binary(), keyword()) :: term()
def log(action, opts) when is_binary(action) and is_list(opts) do
if Code.ensure_loaded?(PhoenixKit.Activity) do
PhoenixKit.Activity.log(%{
action: action,
module: @module,
mode: Keyword.get(opts, :mode, "manual"),
actor_uuid: Keyword.get(opts, :actor_uuid),
resource_type: Keyword.get(opts, :resource_type),
resource_uuid: Keyword.get(opts, :resource_uuid),
target_uuid: Keyword.get(opts, :target_uuid),
metadata: Keyword.get(opts, :metadata, %{})
})
else
:activity_unavailable
end
rescue
Postgrex.Error ->
:ok
DBConnection.OwnershipError ->
:ok
e ->
Logger.warning("[Projects] Activity logging error: #{Exception.message(e)}")
{:error, e}
catch
:exit, _reason -> :ok
end
@doc """
Logs a user-driven mutation that did NOT land cleanly — the success
path would have called `log/2` with the same action + opts; this
variant tags the metadata with `db_pending: true` so audit-feed
readers can distinguish attempted-but-failed actions from completed
ones. Per the post-Apr 2026 pipeline standard
(publishing-Batch-3 / catalogue-Batch-4 precedent): a Drive/DB
outage must NOT erase admin clicks from the activity feed.
Identical signature to `log/2`. Same rescue/catch shape.
"""
@spec log_failed(binary(), keyword()) :: term()
def log_failed(action, opts) when is_binary(action) and is_list(opts) do
metadata = Keyword.get(opts, :metadata, %{}) |> Map.put("db_pending", true)
log(action, Keyword.put(opts, :metadata, metadata))
end
@doc "Extracts `user.uuid` from the LiveView socket assigns."
@spec actor_uuid(Phoenix.LiveView.Socket.t()) :: binary() | nil
def actor_uuid(socket) do
case socket.assigns[:phoenix_kit_current_user] do
%{uuid: uuid} -> uuid
_ -> nil
end
end
end