Packages

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

Current section

Files

Jump to
ash_dispatch lib mix tasks ash_dispatch.gen.recipient_resolver.ex
Raw

lib/mix/tasks/ash_dispatch.gen.recipient_resolver.ex

if Code.ensure_loaded?(Igniter) do
defmodule Mix.Tasks.AshDispatch.Gen.RecipientResolver do
@example "mix ash_dispatch.gen.recipient_resolver MyApp.RecipientResolver"
@moduledoc """
Generates an AshDispatch.RecipientResolver module for declarative audience resolution.
## Example
```bash
#{@example}
```
## Options
* `--user-resource` - The user resource module (defaults to `MyApp.Accounts.User`)
## Generated Code
The generator creates a recipient resolver with:
- A `to_recipient/1` callback for formatting user structs as recipient maps
- Example audiences using different resolution strategies
- Comments explaining each strategy
## Usage
After generation, configure ash_dispatch to use your resolver:
```elixir
config :ash_dispatch,
recipient_resolver: MyApp.RecipientResolver
```
Then reference audiences in your dispatch DSL:
```elixir
dispatch do
event :order_created do
channel :in_app, audience: :owner
channel :email, audience: :admins
end
end
```
"""
@shortdoc "Generates an AshDispatch.RecipientResolver module"
use Igniter.Mix.Task
@impl Igniter.Mix.Task
def info(_argv, _parent) do
%Igniter.Mix.Task.Info{
positional: [:resolver],
schema: [
user_resource: :string
],
example: @example
}
end
@impl Igniter.Mix.Task
def igniter(igniter) do
resolver = Igniter.Project.Module.parse(igniter.args.positional.resolver)
app_name = Igniter.Project.Application.app_name(igniter)
user_resource =
case igniter.args.options[:user_resource] do
nil ->
# Default to MyApp.Accounts.User
app_module = Macro.camelize(to_string(app_name))
Module.concat([app_module, Accounts, User])
resource ->
Module.concat([resource])
end
{exists?, igniter} = Igniter.Project.Module.module_exists(igniter, resolver)
if "--ignore-if-exists" in igniter.args.argv_flags && exists? do
igniter
else
igniter
|> Igniter.Project.Module.create_module(resolver, """
@moduledoc \"\"\"
Recipient resolver for notification audiences.
Define how to resolve recipients for each audience type using
the AshDispatch.RecipientResolver DSL.
## Audience Strategies
| Strategy | Example | Description |
|----------|---------|-------------|
| `from_context` | `from_context: :user` | Extract from context.data |
| `query` | `query: [role: :admin]` | Query user_resource with Ash filter |
| `path` | `path: [:team, :users]` | Follow relationship path on resource |
| `combine` | `combine: [:owner, :team]` | Union of other audiences |
| `resolve` | `resolve: :resolve_owner` | Custom resolver function |
## Usage
Reference audiences in your dispatch DSL:
dispatch do
event :order_created do
channel :in_app, audience: :owner
channel :email, audience: :team
end
end
\"\"\"
use AshDispatch.RecipientResolver,
user_resource: #{inspect(user_resource)}
audiences do
# Context-based - extract user from event context
audience :user, from_context: :user
# Fallback chain - tries :user first, then :assignee
audience :assignee, from_context: [:user, :assignee]
# Query-based - find users matching filter
# audience :admins, query: [role: :admin, is_active: true]
# Relationship path - follow relationships on the resource
# audience :team, path: [:team_members, :user]
# Composite - union of other audiences (deduped by id)
# audience :stakeholders, combine: [:owner, :team]
# Custom resolver - for complex business logic
# audience :owner, resolve: :resolve_owner
# Custom resolver returning raw maps (not user structs)
# audience :lead_contact, resolve: :resolve_lead_contact, raw: true
end
@impl true
def to_recipient(%#{inspect(user_resource)}{} = user) do
%{
id: user.id,
email: to_string(user.email),
display_name: extract_display_name(user)
}
end
defp extract_display_name(user) do
cond do
Map.has_key?(user, :full_name) && user.full_name ->
to_string(user.full_name)
Map.has_key?(user, :first_name) && user.first_name ->
to_string(user.first_name)
true ->
to_string(user.email)
end
end
# Example custom resolver:
#
# def resolve_owner(resource, context) do
# case Map.get(context.data, :owner) do
# nil -> []
# owner -> [owner]
# end
# end
""")
|> Igniter.Project.Config.configure_new(
"config.exs",
:ash_dispatch,
[:recipient_resolver],
resolver
)
end
end
end
else
defmodule Mix.Tasks.AshDispatch.Gen.RecipientResolver do
@example "mix ash_dispatch.gen.recipient_resolver MyApp.RecipientResolver"
@moduledoc """
Generates an AshDispatch.RecipientResolver module for declarative audience resolution.
## Example
```bash
#{@example}
```
This task requires Igniter. Please install igniter and try again.
For more information, see: https://hexdocs.pm/igniter
"""
@shortdoc "Generates an AshDispatch.RecipientResolver module"
use Mix.Task
def run(_argv) do
Mix.shell().error("""
The task 'ash_dispatch.gen.recipient_resolver' requires igniter.
Please install igniter and try again.
For more information, see: https://hexdocs.pm/igniter
""")
exit({:shutdown, 1})
end
end
end