who_need_help/lib/who_need_help/messaging.ex

90 lines
2.6 KiB
Elixir

defmodule WhoNeedHelp.Messaging do
@moduledoc "Durable match chat with PubSub fan-out after commit."
import Ecto.Query
alias WhoNeedHelp.Accounts.Scope
alias WhoNeedHelp.Help
alias WhoNeedHelp.Help.Assignment
alias WhoNeedHelp.Messaging.Message
alias WhoNeedHelp.Pagination
alias WhoNeedHelp.Repo
alias WhoNeedHelp.Trust
def subscribe(assignment_id) do
Phoenix.PubSub.subscribe(WhoNeedHelp.PubSub, "messages:#{assignment_id}")
end
def list_messages(%Scope{} = scope, %Assignment{} = assignment) do
paginate_messages(scope, assignment).entries
end
def paginate_messages(%Scope{} = scope, %Assignment{} = assignment, options \\ []) do
if Trust.eligible?(scope) and Help.participant?(scope, assignment) and
not blocked_assignment?(scope, assignment) do
limit = Pagination.limit(options, 50)
cursor = Pagination.cursor(options)
page =
Message
|> where([message], message.assignment_id == ^assignment.id)
|> before_message(cursor)
|> order_by([message], desc: message.inserted_at, desc: message.id)
|> limit(^(limit + 1))
|> preload(:sender)
|> Repo.all()
|> Pagination.page(limit, &{&1.inserted_at, &1.id})
%{page | entries: Enum.reverse(page.entries)}
else
%Pagination.Page{}
end
end
def send_message(%Scope{user: user} = scope, %Assignment{} = assignment, attrs) do
with {:ok, _limit} <- Trust.authorize_action(scope, :send_message),
true <- Help.participant?(scope, assignment),
false <- blocked_assignment?(scope, assignment) do
result =
%Message{assignment_id: assignment.id, sender_id: user.id}
|> Message.changeset(attrs)
|> Repo.insert()
with {:ok, message} <- result do
message = Repo.preload(message, :sender)
Phoenix.PubSub.broadcast(
WhoNeedHelp.PubSub,
"messages:#{assignment.id}",
{:new_message, message}
)
{:ok, message}
end
else
true -> {:error, :blocked}
false -> {:error, :forbidden}
other -> other
end
end
defp blocked_assignment?(%Scope{user: user}, assignment) do
request = Repo.preload(assignment, :request).request
counterpart_id =
if user.id == assignment.helper_id, do: request.requester_id, else: assignment.helper_id
Trust.blocked_between?(user.id, counterpart_id)
end
defp before_message(query, nil), do: query
defp before_message(query, {inserted_at, id}) do
where(
query,
[message],
message.inserted_at < ^inserted_at or
(message.inserted_at == ^inserted_at and message.id < ^id)
)
end
end