who_need_help/lib/who_need_help/push.ex

116 lines
3.4 KiB
Elixir

defmodule WhoNeedHelp.Push do
@moduledoc """
Provider-neutral boundary and durable product-event enqueueing for remote push.
The product deliberately remains disabled until a complete HTTP adapter
configuration is supplied. Product events target a stable user identifier;
the selected provider is responsible for resolving that identifier to one or
more registered devices.
"""
alias WhoNeedHelp.Push.DeliveryWorker
@type notification :: %{
required(:idempotency_key) => String.t(),
required(:recipient) => String.t(),
required(:title) => String.t(),
required(:body) => String.t(),
optional(:data) => map()
}
@type receipt :: %{id: String.t(), duplicate: boolean()}
@type delivery_error :: :disabled | {:invalid_notification, atom()} | term()
@callback deliver(notification(), keyword()) ::
{:ok, receipt()} | {:error, delivery_error()}
def enabled?, do: Application.get_env(:who_need_help, :push_product_enabled, false) == true
def enqueue_request_accepted(assignment_id, request_id, requester_id) do
enqueue(%{
idempotency_key: "request-accepted:#{assignment_id}:#{requester_id}",
recipient: user_recipient(requester_id),
title: "A helper responded",
body: "Open Who Need Help to see the request update.",
data: %{
"kind" => "request_accepted",
"assignment_id" => assignment_id,
"request_id" => request_id
}
})
end
def enqueue_message_created(message_id, assignment_id, request_id, recipient_id) do
enqueue(%{
idempotency_key: "message-created:#{message_id}:#{recipient_id}",
recipient: user_recipient(recipient_id),
title: "New message",
body: "Open Who Need Help to read the conversation.",
data: %{
"kind" => "message_created",
"assignment_id" => assignment_id,
"message_id" => message_id,
"request_id" => request_id
}
})
end
def enqueue(notification) do
if enabled?() do
notification
|> DeliveryWorker.new()
|> Oban.insert()
else
{:ok, :disabled}
end
end
def deliver(notification, opts \\ []) do
adapter =
Keyword.get_lazy(opts, :adapter, fn ->
Application.get_env(
:who_need_help,
:push_adapter,
WhoNeedHelp.Push.DisabledAdapter
)
end)
with :ok <- validate(notification) do
adapter.deliver(notification, Keyword.delete(opts, :adapter))
end
end
def delivery_options do
Application.get_env(:who_need_help, :push_delivery_options, [])
end
defp user_recipient(user_id), do: "user:#{user_id}"
defp validate(%{
idempotency_key: idempotency_key,
recipient: recipient,
title: title,
body: body
})
when is_binary(idempotency_key) and idempotency_key != "" and
is_binary(recipient) and recipient != "" and
is_binary(title) and title != "" and is_binary(body) and body != "" do
:ok
end
defp validate(notification) when not is_map(notification),
do: {:error, {:invalid_notification, :not_a_map}}
defp validate(notification) do
required = [:idempotency_key, :recipient, :title, :body]
case Enum.find(required, fn key ->
value = Map.get(notification, key)
not (is_binary(value) and value != "")
end) do
nil -> :ok
key -> {:error, {:invalid_notification, key}}
end
end
end