Packages

Event-driven notification system for Ash Framework with multiple transport types

Retired package: Release invalid - Use vulcora organization package

Current section

Files

Jump to
ash_dispatch lib helpers notification_loader.ex
Raw

lib/helpers/notification_loader.ex

defmodule AshDispatch.Helpers.NotificationLoader do
@moduledoc """
Helper for loading and managing notifications.
Automatically discovers the notification resource from AshDispatch.Resources.Notification
and provides common operations.
## Usage
alias AshDispatch.Helpers.NotificationLoader
# Load recent notifications
notifications = NotificationLoader.load_recent(user_id)
#=> [%{id: "...", title: "...", ...}]
# Mark as read
NotificationLoader.mark_as_read(notification_id, actor: user)
# Mark all as read
NotificationLoader.mark_all_as_read(user_id, actor: user)
## Custom Serialization
Apps can provide custom serializers:
NotificationLoader.load_recent(user_id,
serializer: &MyApp.serialize_notification/1
)
"""
require Ash.Query
require Logger
@doc """
Load recent notifications for a user.
Returns serialized notifications ready for JSON/frontend consumption.
## Options
- `:limit` - Number of notifications to load (default: 50)
- `:serializer` - Custom serialization function (default: built-in serializer)
## Examples
# Default serialization
NotificationLoader.load_recent("user-123")
#=> [%{id: "...", title: "...", read: false, ...}]
# Custom limit
NotificationLoader.load_recent("user-123", limit: 10)
# Custom serializer
NotificationLoader.load_recent("user-123",
serializer: &MyApp.serialize_notification/1
)
"""
def load_recent(user_id, opts \\ []) do
limit = Keyword.get(opts, :limit, 50)
serializer = Keyword.get(opts, :serializer)
notification_resource()
|> Ash.Query.filter(user_id == ^user_id)
|> Ash.Query.sort(inserted_at: :desc)
|> Ash.Query.limit(limit)
|> Ash.read(authorize?: false)
|> case do
{:ok, notifications} ->
Enum.map(notifications, fn notification ->
if serializer do
serializer.(notification)
else
serialize(notification)
end
end)
{:error, error} ->
Logger.error("[NotificationLoader] Failed to load notifications: #{inspect(error)}")
[]
_ ->
[]
end
end
@doc """
Serialize a notification for JSON/frontend consumption.
Uses camelCase keys for JavaScript compatibility.
Apps can provide custom serializers via options.
## Examples
NotificationLoader.serialize(notification)
#=> %{id: "...", title: "...", actionLabel: "View", ...}
"""
def serialize(notification) do
%{
id: notification.id,
type: notification.type,
title: notification.title,
message: notification.message,
read: notification.read,
timestamp: notification.inserted_at,
metadata: notification.metadata,
actionLabel: notification.action_label,
actionUrl: notification.action_url
}
end
@doc """
Mark a notification as read.
Uses Ash policies to verify ownership.
## Options
- `:actor` - User performing the action (for authorization)
## Examples
NotificationLoader.mark_as_read(notification_id, actor: current_user)
#=> {:ok, updated_notification}
## Returns
- `{:ok, notification}` - Successfully marked as read
- `{:error, reason}` - Failed (unauthorized, not found, etc.)
"""
def mark_as_read(notification_id, opts \\ []) do
actor = Keyword.get(opts, :actor)
with {:ok, notification} <-
notification_resource()
|> Ash.get(notification_id, actor: actor),
{:ok, updated} <-
notification
|> Ash.Changeset.for_update(:mark_as_read, %{}, actor: actor)
|> Ash.update() do
{:ok, updated}
end
end
@doc """
Mark all notifications as read for a user.
Uses the mark_all_as_read action which properly triggers counter broadcasts.
## Options
- `:actor` - User performing the action (for authorization)
## Examples
NotificationLoader.mark_all_as_read(user_id, actor: current_user)
#=> {:ok, %{marked_count: 5}}
## Returns
- `{:ok, %{marked_count: integer}}` - Successfully marked notifications as read
- `{:error, reason}` - Failed to mark notifications
"""
def mark_all_as_read(user_id, _opts \\ []) do
notification_resource().mark_all_as_read(user_id)
end
# Get configured notification resource or fall back to default
defp notification_resource do
Application.get_env(:ash_dispatch, :notification_resource, AshDispatch.Resources.Notification)
end
end