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 resources manual_trigger base.ex
Raw

lib/resources/manual_trigger/base.ex

defmodule AshDispatch.Resources.ManualTrigger.Base do
@moduledoc """
Provides the base DSL for ManualTrigger resources.
This module exports a `__using__` macro that consuming apps can use to create
their own ManualTrigger resource with a specific domain.
## Usage
defmodule MyApp.Deliveries.ManualTrigger do
use AshDispatch.Resources.ManualTrigger.Base,
domain: MyApp.Deliveries
end
Then add to your domain:
resources do
resource MyApp.Deliveries.ManualTrigger do
define :list_manual_trigger_events, action: :list_events, args: [:user_id]
define :preview_manual_trigger, action: :preview
define :trigger_manual_event, action: :trigger
end
end
"""
defmacro __using__(opts) do
domain = Keyword.fetch!(opts, :domain)
quote do
use Ash.Resource,
domain: unquote(domain),
data_layer: Ash.DataLayer.Simple,
extensions: [AshTypescript.Resource],
validate_domain_inclusion?: false
alias AshDispatch.Resources.ManualTrigger.Helpers
resource do
require_primary_key? false
end
typescript do
type_name("ManualTrigger")
end
actions do
defaults []
read :list_events do
description "List all available email events with their metadata, optionally filtered by user state"
argument :user_id, :string do
allow_nil? true
description "Optional user ID to filter events based on user state"
end
prepare fn query, _context ->
# TODO: Make user loading configurable via DSL
# For now, we list all events without user-based filtering
_user_id = Ash.Query.get_argument(query, :user_id)
events = Helpers.list_available_events(nil)
records =
Enum.map(events, fn event_data ->
struct(__MODULE__, event_data)
end)
Ash.DataLayer.Simple.set_data(query, records)
end
end
read :get_user_preference do
description "Get user's email preference status for a specific event"
argument :user_id, :string do
allow_nil? false
description "The user ID to check preferences for"
end
argument :event_id, :string do
allow_nil? false
description "The event ID to check"
end
prepare fn query, _context ->
user_id = Ash.Query.get_argument(query, :user_id)
event_id = Ash.Query.get_argument(query, :event_id)
case Helpers.get_user_preference_for_event(user_id, event_id) do
{:ok, preference_data} ->
record = struct(__MODULE__, Map.merge(preference_data, %{event_id: event_id}))
Ash.DataLayer.Simple.set_data(query, [record])
{:error, reason} ->
Ash.Query.add_error(query, reason)
end
end
end
read :preview do
description "Preview the email content that would be sent"
argument :event_id, :string do
allow_nil? false
end
argument :context_data, :map do
default %{}
end
argument :recipient_email, :ci_string
argument :audience, :atom, constraints: [one_of: [:user, :admin]]
argument :transport, :atom, constraints: [one_of: [:email, :in_app]]
prepare fn query, context ->
event_id = Ash.Query.get_argument(query, :event_id)
context_data = Ash.Query.get_argument(query, :context_data)
recipient_email = Ash.Query.get_argument(query, :recipient_email)
audience = Ash.Query.get_argument(query, :audience)
transport = Ash.Query.get_argument(query, :transport)
channel_filter = Helpers.build_channel_filter(audience, transport)
case Helpers.preview_trigger(
event_id,
context_data,
channel_filter,
recipient_email,
context.actor
) do
{:ok, previews} ->
records =
Enum.map(previews, fn preview_data ->
merged = Map.merge(preview_data, %{event_id: event_id})
struct(__MODULE__, merged)
end)
Ash.DataLayer.Simple.set_data(query, records)
{:error, reason} ->
Ash.Query.add_error(query, reason)
end
end
end
create :trigger do
description "Manually trigger an event with custom configuration"
accept [
:event_id,
:recipient_email,
:audience,
:transport,
:context_data,
:skip_preferences
]
change fn changeset, context ->
event_id = Ash.Changeset.get_attribute(changeset, :event_id)
context_data = Ash.Changeset.get_attribute(changeset, :context_data)
recipient_email = Ash.Changeset.get_attribute(changeset, :recipient_email)
audience = Ash.Changeset.get_attribute(changeset, :audience)
transport = Ash.Changeset.get_attribute(changeset, :transport)
skip_preferences = Ash.Changeset.get_attribute(changeset, :skip_preferences)
opts =
Helpers.build_trigger_opts(
recipient_email,
audience,
transport,
skip_preferences,
context.actor
)
# Use load_and_dispatch to load resource data before dispatching
case Helpers.load_and_dispatch(event_id, context_data, opts, context.actor) do
{:ok, results} ->
# Extract receipt IDs from results
receipt_ids =
results
|> Enum.flat_map(fn
{:ok, receipts} when is_list(receipts) ->
Enum.map(receipts, & &1.id)
_ ->
[]
end)
# Store receipt IDs in changeset so they can be returned
Ash.Changeset.force_change_attribute(
changeset,
:delivery_receipt_ids,
receipt_ids
)
{:error, reason} ->
Ash.Changeset.add_error(changeset, reason)
end
end
end
end
attributes do
attribute :event_id, :string do
allow_nil? false
public? true
end
attribute :recipient_email, :ci_string do
public? true
end
attribute :audience, :atom do
public? true
constraints one_of: [:user, :admin]
end
attribute :transport, :atom do
public? true
constraints one_of: [:email, :in_app]
end
attribute :context_data, :map do
public? true
default %{}
end
attribute :skip_preferences, :boolean do
public? true
default false
end
attribute :description, :string do
public? true
end
attribute :channels, {:array, :map} do
public? true
end
attribute :required_context, {:array, :string} do
public? true
end
attribute :example_context, :map do
public? true
end
attribute :domain, :string do
public? true
end
attribute :required_resources, {:array, :map} do
public? true
description "Resources required for manual trigger, with optional Ash filters"
end
attribute :subject, :string do
public? true
end
attribute :html_body, :string do
public? true
end
attribute :text_body, :string do
public? true
end
attribute :from_address, :string do
public? true
end
attribute :recipient, :string do
public? true
end
attribute :notification_title, :string do
public? true
description "Title for in-app notification preview"
end
attribute :notification_message, :string do
public? true
description "Message for in-app notification preview"
end
attribute :user_configurable, :boolean do
public? true
end
attribute :category, :string do
public? true
end
attribute :preference_enabled, :boolean do
public? true
end
attribute :delivery_receipt_ids, {:array, :string} do
public? true
description "IDs of delivery receipts created by trigger action"
end
end
end
end
end