116 lines
3.4 KiB
Elixir
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
|