269
lib/graphql/resolvers/conversation.ex
Normal file
269
lib/graphql/resolvers/conversation.ex
Normal file
@@ -0,0 +1,269 @@
|
||||
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
|
||||
@@ -2,9 +2,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||
@moduledoc """
|
||||
Handles the participation-related GraphQL calls.
|
||||
"""
|
||||
# alias Mobilizon.Conversations.ConversationParticipant
|
||||
alias Mobilizon.{Actors, Config, Crypto, Events}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Conversations.{Conversation, ConversationView}
|
||||
alias Mobilizon.Events.{Event, Participant}
|
||||
alias Mobilizon.GraphQL.API.Comments
|
||||
alias Mobilizon.GraphQL.API.Participations
|
||||
alias Mobilizon.Service.Export.Participants.{CSV, ODS, PDF}
|
||||
alias Mobilizon.Users.User
|
||||
@@ -346,6 +349,60 @@ defmodule Mobilizon.GraphQL.Resolvers.Participant do
|
||||
|
||||
def export_event_participants(_, _, _), do: {:error, :unauthorized}
|
||||
|
||||
def send_private_messages_to_participants(
|
||||
_parent,
|
||||
%{roles: roles, event_id: event_id, actor_id: actor_id} =
|
||||
args,
|
||||
%{
|
||||
context: %{
|
||||
current_user: %User{locale: _locale},
|
||||
current_actor: %Actor{id: current_actor_id}
|
||||
}
|
||||
}
|
||||
) do
|
||||
participant_actors =
|
||||
event_id
|
||||
|> Events.list_all_participants_for_event(roles)
|
||||
|> Enum.map(& &1.actor)
|
||||
|
||||
mentions =
|
||||
participant_actors
|
||||
|> Enum.map(& &1.id)
|
||||
|> Enum.uniq()
|
||||
|> Enum.map(&%{actor_id: &1, event_id: event_id})
|
||||
|
||||
args =
|
||||
Map.merge(args, %{
|
||||
mentions: mentions,
|
||||
visibility: :private
|
||||
})
|
||||
|
||||
with {:member, true} <-
|
||||
{:member,
|
||||
current_actor_id == actor_id or Actors.is_member?(current_actor_id, actor_id)},
|
||||
{:ok, _activity, %Conversation{} = conversation} <- Comments.create_conversation(args) do
|
||||
{:ok, conversation_to_view(conversation, Actors.get_actor(actor_id))}
|
||||
else
|
||||
{:member, false} ->
|
||||
{:error, :unauthorized}
|
||||
|
||||
{:error, err} ->
|
||||
{:error, err}
|
||||
end
|
||||
end
|
||||
|
||||
def send_private_messages_to_participants(_parent, _args, _resolution),
|
||||
do: {:error, :unauthorized}
|
||||
|
||||
defp conversation_to_view(%Conversation{} = conversation, %Actor{} = actor) do
|
||||
value =
|
||||
conversation
|
||||
|> Map.from_struct()
|
||||
|> Map.put(:actor, actor)
|
||||
|
||||
struct(ConversationView, value)
|
||||
end
|
||||
|
||||
@spec valid_email?(String.t() | nil) :: boolean
|
||||
defp valid_email?(email) when is_nil(email), do: false
|
||||
|
||||
|
||||
Reference in New Issue
Block a user