270 lines
8.7 KiB
Elixir
270 lines
8.7 KiB
Elixir
|
defmodule Mobilizon.GraphQL.Resolvers.Conversation do
|
||
|
@moduledoc """
|
||
|
Handles the group-related GraphQL calls.
|
||
|
"""
|
||
|
|
||
|
alias Mobilizon.{Actors, Conversations}
|
||
|
alias Mobilizon.Actors.Actor
|
||
|
alias Mobilizon.Conversations.{Conversation, ConversationParticipant, ConversationView}
|
||
|
alias Mobilizon.Events.Event
|
||
|
alias Mobilizon.GraphQL.API.Comments
|
||
|
alias Mobilizon.Storage.Page
|
||
|
alias Mobilizon.Users.User
|
||
|
alias Mobilizon.Web.Endpoint
|
||
|
# alias Mobilizon.Users.User
|
||
|
import Mobilizon.Web.Gettext, only: [dgettext: 2]
|
||
|
|
||
|
@spec find_conversations_for_event(Event.t(), map(), Absinthe.Resolution.t()) ::
|
||
|
{:ok, Page.t(ConversationView.t())} | {:error, :unauthenticated}
|
||
|
def find_conversations_for_event(
|
||
|
%Event{id: event_id, attributed_to_id: attributed_to_id},
|
||
|
%{page: page, limit: limit},
|
||
|
%{
|
||
|
context: %{
|
||
|
current_actor: %Actor{id: actor_id}
|
||
|
}
|
||
|
}
|
||
|
)
|
||
|
when not is_nil(attributed_to_id) do
|
||
|
if Actors.is_member?(actor_id, attributed_to_id) do
|
||
|
{:ok,
|
||
|
event_id
|
||
|
|> Conversations.find_conversations_for_event(actor_id, page, limit)
|
||
|
|> conversation_participant_to_view()}
|
||
|
else
|
||
|
{:ok, %Page{total: 0, elements: []}}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
@spec find_conversations_for_event(Event.t(), map(), Absinthe.Resolution.t()) ::
|
||
|
{:ok, Page.t(ConversationView.t())} | {:error, :unauthenticated}
|
||
|
def find_conversations_for_event(
|
||
|
%Event{id: event_id, organizer_actor_id: organizer_actor_id},
|
||
|
%{page: page, limit: limit},
|
||
|
%{
|
||
|
context: %{
|
||
|
current_actor: %Actor{id: actor_id}
|
||
|
}
|
||
|
}
|
||
|
) do
|
||
|
if organizer_actor_id == actor_id do
|
||
|
{:ok,
|
||
|
event_id
|
||
|
|> Conversations.find_conversations_for_event(actor_id, page, limit)
|
||
|
|> conversation_participant_to_view()}
|
||
|
else
|
||
|
{:ok, %Page{total: 0, elements: []}}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def list_conversations(%Actor{id: actor_id}, %{page: page, limit: limit}, %{
|
||
|
context: %{
|
||
|
current_actor: %Actor{id: _current_actor_id}
|
||
|
}
|
||
|
}) do
|
||
|
{:ok,
|
||
|
actor_id
|
||
|
|> Conversations.list_conversation_participants_for_actor(page, limit)
|
||
|
|> conversation_participant_to_view()}
|
||
|
end
|
||
|
|
||
|
def list_conversations(%User{id: user_id}, %{page: page, limit: limit}, %{
|
||
|
context: %{
|
||
|
current_actor: %Actor{id: _current_actor_id}
|
||
|
}
|
||
|
}) do
|
||
|
{:ok,
|
||
|
user_id
|
||
|
|> Conversations.list_conversation_participants_for_user(page, limit)
|
||
|
|> conversation_participant_to_view()}
|
||
|
end
|
||
|
|
||
|
def unread_conversations_count(%Actor{id: actor_id}, _args, %{
|
||
|
context: %{
|
||
|
current_user: %User{} = user
|
||
|
}
|
||
|
}) do
|
||
|
case User.owns_actor(user, actor_id) do
|
||
|
{:is_owned, %Actor{}} ->
|
||
|
{:ok, Conversations.count_unread_conversation_participants_for_person(actor_id)}
|
||
|
|
||
|
_ ->
|
||
|
{:error, :unauthorized}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def get_conversation(_parent, %{id: conversation_participant_id}, %{
|
||
|
context: %{
|
||
|
current_actor: %Actor{id: performing_actor_id}
|
||
|
}
|
||
|
}) do
|
||
|
case Conversations.get_conversation_participant(conversation_participant_id) do
|
||
|
nil ->
|
||
|
{:error, :not_found}
|
||
|
|
||
|
%ConversationParticipant{actor_id: actor_id} = conversation_participant ->
|
||
|
if actor_id == performing_actor_id or Actors.is_member?(performing_actor_id, actor_id) do
|
||
|
{:ok, conversation_participant_to_view(conversation_participant)}
|
||
|
else
|
||
|
{:error, :not_found}
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def get_comments_for_conversation(
|
||
|
%ConversationView{origin_comment_id: origin_comment_id, actor_id: conversation_actor_id},
|
||
|
%{page: page, limit: limit},
|
||
|
%{
|
||
|
context: %{
|
||
|
current_actor: %Actor{id: performing_actor_id}
|
||
|
}
|
||
|
}
|
||
|
) do
|
||
|
if conversation_actor_id == performing_actor_id or
|
||
|
Actors.is_member?(performing_actor_id, conversation_actor_id) do
|
||
|
{:ok,
|
||
|
Mobilizon.Discussions.get_comments_in_reply_to_comment_id(origin_comment_id, page, limit)}
|
||
|
else
|
||
|
{:error, :unauthorized}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def create_conversation(
|
||
|
_parent,
|
||
|
%{actor_id: actor_id} = args,
|
||
|
%{
|
||
|
context: %{
|
||
|
current_actor: %Actor{} = current_actor
|
||
|
}
|
||
|
}
|
||
|
) do
|
||
|
if authorized_to_reply?(
|
||
|
Map.get(args, :conversation_id),
|
||
|
Map.get(args, :attributed_to_id),
|
||
|
current_actor.id
|
||
|
) do
|
||
|
case Comments.create_conversation(args) do
|
||
|
{:ok, _activity, %Conversation{} = conversation} ->
|
||
|
Absinthe.Subscription.publish(
|
||
|
Endpoint,
|
||
|
Conversations.count_unread_conversation_participants_for_person(current_actor.id),
|
||
|
person_unread_conversations_count: current_actor.id
|
||
|
)
|
||
|
|
||
|
conversation_participant_actor =
|
||
|
args |> Map.get(:attributed_to_id, actor_id) |> Actors.get_actor()
|
||
|
|
||
|
{:ok, conversation_to_view(conversation, conversation_participant_actor)}
|
||
|
|
||
|
{:error, :empty_participants} ->
|
||
|
{:error, dgettext("errors", "Conversation needs to mention at least one participant")}
|
||
|
end
|
||
|
else
|
||
|
{:error, :unauthorized}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def update_conversation(_parent, %{conversation_id: conversation_participant_id, read: read}, %{
|
||
|
context: %{
|
||
|
current_actor: %Actor{id: current_actor_id}
|
||
|
}
|
||
|
}) do
|
||
|
with {:no_participant,
|
||
|
%ConversationParticipant{actor_id: actor_id} = conversation_participant} <-
|
||
|
{:no_participant,
|
||
|
Conversations.get_conversation_participant(conversation_participant_id)},
|
||
|
{:valid_actor, true} <-
|
||
|
{:valid_actor,
|
||
|
actor_id == current_actor_id or
|
||
|
Actors.is_member?(current_actor_id, actor_id)},
|
||
|
{:ok, %ConversationParticipant{} = conversation_participant} <-
|
||
|
Conversations.update_conversation_participant(conversation_participant, %{
|
||
|
unread: !read
|
||
|
}) do
|
||
|
Absinthe.Subscription.publish(
|
||
|
Endpoint,
|
||
|
Conversations.count_unread_conversation_participants_for_person(actor_id),
|
||
|
person_unread_conversations_count: actor_id
|
||
|
)
|
||
|
|
||
|
{:ok, conversation_participant_to_view(conversation_participant)}
|
||
|
else
|
||
|
{:no_participant, _} ->
|
||
|
{:error, :not_found}
|
||
|
|
||
|
{:valid_actor, _} ->
|
||
|
{:error, :unauthorized}
|
||
|
end
|
||
|
end
|
||
|
|
||
|
def delete_conversation(_, _, _), do: :ok
|
||
|
|
||
|
defp conversation_participant_to_view(%Page{elements: elements} = page) do
|
||
|
%Page{page | elements: Enum.map(elements, &conversation_participant_to_view/1)}
|
||
|
end
|
||
|
|
||
|
defp conversation_participant_to_view(%ConversationParticipant{} = conversation_participant) do
|
||
|
value =
|
||
|
conversation_participant
|
||
|
|> Map.from_struct()
|
||
|
|> Map.merge(Map.from_struct(conversation_participant.conversation))
|
||
|
|> Map.delete(:conversation)
|
||
|
|> Map.put(
|
||
|
:participants,
|
||
|
Enum.map(
|
||
|
conversation_participant.conversation.participants,
|
||
|
&conversation_participant_to_actor/1
|
||
|
)
|
||
|
)
|
||
|
|> Map.put(:conversation_participant_id, conversation_participant.id)
|
||
|
|
||
|
struct(ConversationView, value)
|
||
|
end
|
||
|
|
||
|
defp conversation_to_view(
|
||
|
%Conversation{id: conversation_id} = conversation,
|
||
|
%Actor{id: actor_id} = actor,
|
||
|
unread \\ true
|
||
|
) do
|
||
|
value =
|
||
|
conversation
|
||
|
|> Map.from_struct()
|
||
|
|> Map.put(:actor, actor)
|
||
|
|> Map.put(:unread, unread)
|
||
|
|> Map.put(
|
||
|
:conversation_participant_id,
|
||
|
Conversations.get_participant_by_conversation_and_actor(conversation_id, actor_id).id
|
||
|
)
|
||
|
|
||
|
struct(ConversationView, value)
|
||
|
end
|
||
|
|
||
|
defp conversation_participant_to_actor(%Actor{} = actor), do: actor
|
||
|
|
||
|
defp conversation_participant_to_actor(%ConversationParticipant{} = conversation_participant),
|
||
|
do: conversation_participant.actor
|
||
|
|
||
|
@spec authorized_to_reply?(String.t() | nil, String.t() | nil, String.t()) :: boolean()
|
||
|
# Not a reply
|
||
|
defp authorized_to_reply?(conversation_id, _attributed_to_id, _current_actor_id)
|
||
|
when is_nil(conversation_id),
|
||
|
do: true
|
||
|
|
||
|
# We are authorized to reply if we are one of the participants, or if we a a member of a participant group
|
||
|
defp authorized_to_reply?(conversation_id, attributed_to_id, current_actor_id) do
|
||
|
case Conversations.get_conversation(conversation_id) do
|
||
|
nil ->
|
||
|
false
|
||
|
|
||
|
%Conversation{participants: participants} ->
|
||
|
participant_ids = Enum.map(participants, fn participant -> to_string(participant.id) end)
|
||
|
|
||
|
current_actor_id in participant_ids or
|
||
|
Enum.any?(participant_ids, fn participant_id ->
|
||
|
Actors.is_member?(current_actor_id, participant_id) and
|
||
|
attributed_to_id == participant_id
|
||
|
end)
|
||
|
end
|
||
|
end
|
||
|
end
|