90 lines
2.6 KiB
Elixir
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
|