2020-01-26 20:34:25 +01:00
|
|
|
|
defmodule Mobilizon.GraphQL.Resolvers.Member do
|
2019-04-03 17:29:03 +02:00
|
|
|
|
@moduledoc """
|
|
|
|
|
Handles the member-related GraphQL calls
|
|
|
|
|
"""
|
2020-01-26 21:11:16 +01:00
|
|
|
|
|
2020-08-27 11:53:24 +02:00
|
|
|
|
import Mobilizon.Users.Guards
|
2021-09-10 11:35:32 +02:00
|
|
|
|
alias Mobilizon.Actors
|
2020-02-18 08:57:00 +01:00
|
|
|
|
alias Mobilizon.Actors.{Actor, Member}
|
2021-09-28 19:40:37 +02:00
|
|
|
|
alias Mobilizon.Federation.ActivityPub.Actions
|
2021-04-22 12:17:56 +02:00
|
|
|
|
alias Mobilizon.Federation.ActivityPub.Actor, as: ActivityPubActor
|
2020-02-18 08:57:00 +01:00
|
|
|
|
alias Mobilizon.Storage.Page
|
|
|
|
|
alias Mobilizon.Users.User
|
2020-09-29 09:53:48 +02:00
|
|
|
|
import Mobilizon.Web.Gettext
|
2019-04-03 17:29:03 +02:00
|
|
|
|
|
|
|
|
|
@doc """
|
2020-02-18 08:57:00 +01:00
|
|
|
|
Find members for group.
|
|
|
|
|
|
|
|
|
|
If actor requesting is not part of the group, we only return the number of members, not members
|
2019-04-03 17:29:03 +02:00
|
|
|
|
"""
|
2021-09-28 19:40:37 +02:00
|
|
|
|
@spec find_members_for_group(Actor.t(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
|
{:ok, Page.t(Member.t())}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
def find_members_for_group(
|
|
|
|
|
%Actor{id: group_id} = group,
|
2021-12-13 17:02:10 +01:00
|
|
|
|
%{page: page, limit: limit, roles: roles} = args,
|
2020-02-18 08:57:00 +01:00
|
|
|
|
%{
|
2021-09-10 11:35:32 +02:00
|
|
|
|
context: %{current_user: %User{role: user_role}, current_actor: %Actor{id: actor_id}}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
} = _resolution
|
|
|
|
|
) do
|
2021-09-10 11:35:32 +02:00
|
|
|
|
if Actors.is_member?(actor_id, group_id) or is_moderator(user_role) do
|
2020-07-09 17:24:28 +02:00
|
|
|
|
roles =
|
|
|
|
|
case roles do
|
|
|
|
|
"" ->
|
|
|
|
|
[]
|
|
|
|
|
|
|
|
|
|
roles ->
|
|
|
|
|
roles
|
|
|
|
|
|> String.split(",")
|
|
|
|
|
|> Enum.map(&String.downcase/1)
|
|
|
|
|
|> Enum.map(&String.to_existing_atom/1)
|
|
|
|
|
end
|
|
|
|
|
|
2021-12-13 17:02:10 +01:00
|
|
|
|
%Page{} =
|
|
|
|
|
page = Actors.list_members_for_group(group, Map.get(args, :name), roles, page, limit)
|
|
|
|
|
|
2020-02-18 08:57:00 +01:00
|
|
|
|
{:ok, page}
|
|
|
|
|
else
|
2021-09-10 11:35:32 +02:00
|
|
|
|
# Actor is not member of group, fallback to public
|
|
|
|
|
%Page{} = page = Actors.list_members_for_group(group)
|
|
|
|
|
{:ok, %Page{page | elements: []}}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def find_members_for_group(%Actor{} = group, _args, _resolution) do
|
2021-09-28 19:40:37 +02:00
|
|
|
|
%Page{} = page = Actors.list_members_for_group(group)
|
|
|
|
|
{:ok, %Page{page | elements: []}}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
end
|
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
|
@spec invite_member(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
|
{:ok, Member.t()} | {:error, String.t()}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
def invite_member(
|
|
|
|
|
_parent,
|
|
|
|
|
%{group_id: group_id, target_actor_username: target_actor_username},
|
2021-09-10 11:35:32 +02:00
|
|
|
|
%{context: %{current_actor: %Actor{id: actor_id} = actor}}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
) do
|
2021-09-10 11:35:32 +02:00
|
|
|
|
with {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
2020-02-18 08:57:00 +01:00
|
|
|
|
{:has_rights_to_invite, {:ok, %Member{role: role}}}
|
|
|
|
|
when role in [:moderator, :administrator, :creator] <-
|
|
|
|
|
{:has_rights_to_invite, Actors.get_member(actor_id, group_id)},
|
2020-10-15 14:23:55 +02:00
|
|
|
|
target_actor_username <-
|
|
|
|
|
target_actor_username |> String.trim() |> String.trim_leading("@"),
|
2020-02-18 08:57:00 +01:00
|
|
|
|
{:target_actor_username, {:ok, %Actor{id: target_actor_id} = target_actor}} <-
|
|
|
|
|
{:target_actor_username,
|
2021-04-22 12:17:56 +02:00
|
|
|
|
ActivityPubActor.find_or_make_actor_from_nickname(target_actor_username)},
|
2020-10-15 14:23:55 +02:00
|
|
|
|
{:existant, true} <-
|
|
|
|
|
{:existant, check_member_not_existant_or_rejected(target_actor_id, group.id)},
|
2021-09-28 19:40:37 +02:00
|
|
|
|
{:ok, _activity, %Member{} = member} <-
|
|
|
|
|
Actions.Invite.invite(group, actor, target_actor) do
|
2020-02-18 08:57:00 +01:00
|
|
|
|
{:ok, member}
|
|
|
|
|
else
|
|
|
|
|
{:error, :group_not_found} ->
|
2020-09-29 09:53:48 +02:00
|
|
|
|
{:error, dgettext("errors", "Group not found")}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
|
|
|
|
{:target_actor_username, _} ->
|
2020-09-29 09:53:48 +02:00
|
|
|
|
{:error, dgettext("errors", "Profile invited doesn't exist")}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
|
|
|
|
{:has_rights_to_invite, {:error, :member_not_found}} ->
|
2020-09-29 09:53:48 +02:00
|
|
|
|
{:error, dgettext("errors", "You are not a member of this group")}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
|
|
|
|
{:has_rights_to_invite, _} ->
|
2020-09-29 09:53:48 +02:00
|
|
|
|
{:error, dgettext("errors", "You cannot invite to this group")}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
|
2020-10-15 14:23:55 +02:00
|
|
|
|
{:existant, _} ->
|
|
|
|
|
{:error, dgettext("errors", "Profile is already a member of this group")}
|
|
|
|
|
|
|
|
|
|
# Remove me ?
|
2020-02-18 08:57:00 +01:00
|
|
|
|
{:ok, %Member{}} ->
|
2020-09-29 09:53:48 +02:00
|
|
|
|
{:error, dgettext("errors", "Profile is already a member of this group")}
|
2020-02-18 08:57:00 +01:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
|
@spec accept_invitation(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
|
{:ok, Member.t()} | {:error, String.t()}
|
2021-09-10 11:35:32 +02:00
|
|
|
|
def accept_invitation(_parent, %{id: member_id}, %{
|
|
|
|
|
context: %{current_actor: %Actor{id: actor_id}}
|
|
|
|
|
}) do
|
|
|
|
|
with %Member{actor: %Actor{id: member_actor_id}} = member <-
|
2020-08-14 11:32:23 +02:00
|
|
|
|
Actors.get_member(member_id),
|
2021-09-10 11:27:59 +02:00
|
|
|
|
{:is_same_actor, true} <- {:is_same_actor, member_actor_id == actor_id},
|
2020-02-18 08:57:00 +01:00
|
|
|
|
{:ok, _activity, %Member{} = member} <-
|
2021-09-28 19:40:37 +02:00
|
|
|
|
Actions.Accept.accept(
|
2020-02-18 08:57:00 +01:00
|
|
|
|
:invite,
|
|
|
|
|
member,
|
|
|
|
|
true
|
|
|
|
|
) do
|
2020-08-14 11:32:23 +02:00
|
|
|
|
{:ok, member}
|
2020-10-02 16:19:15 +02:00
|
|
|
|
else
|
|
|
|
|
{:is_same_actor, false} ->
|
|
|
|
|
{:error, dgettext("errors", "You can't accept this invitation with this profile.")}
|
2020-08-14 11:32:23 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
|
@spec reject_invitation(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
|
{:ok, Member.t()} | {:error, String.t()}
|
2021-09-10 11:35:32 +02:00
|
|
|
|
def reject_invitation(_parent, %{id: member_id}, %{
|
|
|
|
|
context: %{current_actor: %Actor{id: actor_id}}
|
|
|
|
|
}) do
|
|
|
|
|
with {:invitation_exists, %Member{actor: %Actor{id: member_actor_id}} = member} <-
|
2020-10-15 14:23:55 +02:00
|
|
|
|
{:invitation_exists, Actors.get_member(member_id)},
|
2021-09-10 11:27:59 +02:00
|
|
|
|
{:is_same_actor, true} <- {:is_same_actor, member_actor_id == actor_id},
|
2020-08-14 11:32:23 +02:00
|
|
|
|
{:ok, _activity, %Member{} = member} <-
|
2021-09-28 19:40:37 +02:00
|
|
|
|
Actions.Reject.reject(
|
2020-08-14 11:32:23 +02:00
|
|
|
|
:invite,
|
|
|
|
|
member,
|
|
|
|
|
true
|
|
|
|
|
) do
|
|
|
|
|
{:ok, member}
|
2020-10-02 16:19:15 +02:00
|
|
|
|
else
|
|
|
|
|
{:is_same_actor, false} ->
|
|
|
|
|
{:error, dgettext("errors", "You can't reject this invitation with this profile.")}
|
2020-10-15 14:23:55 +02:00
|
|
|
|
|
|
|
|
|
{:invitation_exists, _} ->
|
|
|
|
|
{:error, dgettext("errors", "This invitation doesn't exist.")}
|
2020-08-14 11:32:23 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-11-12 15:42:52 +01:00
|
|
|
|
def approve_member(_parent, %{member_id: member_id}, %{
|
|
|
|
|
context: %{current_actor: %Actor{} = moderator}
|
|
|
|
|
}) do
|
|
|
|
|
case Actors.get_member(member_id) do
|
|
|
|
|
%Member{} = member ->
|
|
|
|
|
with {:ok, _activity, %Member{} = member} <-
|
|
|
|
|
Actions.Accept.accept(:member, member, true, %{moderator: moderator}) do
|
|
|
|
|
{:ok, member}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
{:error, :member_not_found} ->
|
|
|
|
|
{:error, dgettext("errors", "You are not a moderator or admin for this group")}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
# TODO : Maybe remove me ? Remove member with exclude parameter does the same
|
|
|
|
|
def reject_member(_parent, %{member_id: member_id}, %{
|
|
|
|
|
context: %{current_actor: %Actor{} = moderator}
|
|
|
|
|
}) do
|
|
|
|
|
case Actors.get_member(member_id) do
|
|
|
|
|
%Member{} = member ->
|
|
|
|
|
with {:ok, _activity, %Member{} = member} <-
|
|
|
|
|
Actions.Reject.reject(:member, member, true, %{moderator: moderator}) do
|
|
|
|
|
{:ok, member}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
{:error, :member_not_found} ->
|
|
|
|
|
{:error, dgettext("errors", "You are not a moderator or admin for this group")}
|
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
|
@spec update_member(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
|
{:ok, Member.t()} | {:error, String.t()}
|
2020-08-20 10:54:58 +02:00
|
|
|
|
def update_member(_parent, %{member_id: member_id, role: role}, %{
|
2021-09-10 11:35:32 +02:00
|
|
|
|
context: %{current_actor: %Actor{} = moderator}
|
2020-08-14 11:32:23 +02:00
|
|
|
|
}) do
|
2021-09-10 11:35:32 +02:00
|
|
|
|
with %Member{} = member <- Actors.get_member(member_id),
|
2020-08-20 10:54:58 +02:00
|
|
|
|
{:ok, _activity, %Member{} = member} <-
|
2021-09-28 19:40:37 +02:00
|
|
|
|
Actions.Update.update(member, %{role: role}, true, %{moderator: moderator}) do
|
2020-08-20 10:54:58 +02:00
|
|
|
|
{:ok, member}
|
|
|
|
|
else
|
2021-09-24 16:46:42 +02:00
|
|
|
|
{:error, :member_not_found} ->
|
2020-09-29 09:53:48 +02:00
|
|
|
|
{:error, dgettext("errors", "You are not a moderator or admin for this group")}
|
2020-08-20 10:54:58 +02:00
|
|
|
|
|
2021-09-24 16:46:42 +02:00
|
|
|
|
{:error, :only_admin_left} ->
|
2020-08-20 10:54:58 +02:00
|
|
|
|
{:error,
|
2020-09-29 09:53:48 +02:00
|
|
|
|
dgettext(
|
|
|
|
|
"errors",
|
|
|
|
|
"You can't set yourself to a lower member role for this group because you are the only administrator"
|
|
|
|
|
)}
|
2020-08-20 10:54:58 +02:00
|
|
|
|
end
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
def update_member(_parent, _args, _resolution),
|
|
|
|
|
do: {:error, "You must be logged-in to update a member"}
|
|
|
|
|
|
2021-09-28 19:40:37 +02:00
|
|
|
|
@spec remove_member(any(), map(), Absinthe.Resolution.t()) ::
|
|
|
|
|
{:ok, Member.t()} | {:error, String.t()}
|
2021-11-12 15:42:52 +01:00
|
|
|
|
def remove_member(_parent, %{member_id: member_id, exclude: _exclude}, %{
|
2021-09-10 11:35:32 +02:00
|
|
|
|
context: %{current_actor: %Actor{id: moderator_id} = moderator}
|
2020-08-20 10:54:58 +02:00
|
|
|
|
}) do
|
2021-11-12 15:42:52 +01:00
|
|
|
|
case Actors.get_member(member_id) do
|
|
|
|
|
nil ->
|
2020-10-15 14:23:55 +02:00
|
|
|
|
{:error,
|
|
|
|
|
dgettext(
|
|
|
|
|
"errors",
|
2021-11-12 15:42:52 +01:00
|
|
|
|
"This member does not exist"
|
2020-10-15 14:23:55 +02:00
|
|
|
|
)}
|
|
|
|
|
|
2021-11-12 15:42:52 +01:00
|
|
|
|
%Member{role: :rejected} ->
|
2020-10-15 14:23:55 +02:00
|
|
|
|
{:error,
|
|
|
|
|
dgettext(
|
|
|
|
|
"errors",
|
2021-11-12 15:42:52 +01:00
|
|
|
|
"This member already has been rejected."
|
2020-10-15 14:23:55 +02:00
|
|
|
|
)}
|
2021-11-12 15:42:52 +01:00
|
|
|
|
|
|
|
|
|
%Member{parent_id: group_id} = member ->
|
|
|
|
|
case Actors.get_member(moderator_id, group_id) do
|
|
|
|
|
{:ok, %Member{role: role}} when role in [:moderator, :administrator, :creator] ->
|
|
|
|
|
%Actor{type: :Group} = group = Actors.get_actor(group_id)
|
|
|
|
|
|
|
|
|
|
with {:ok, _activity, %Member{}} <-
|
|
|
|
|
Actions.Remove.remove(member, group, moderator, true) do
|
|
|
|
|
{:ok, member}
|
|
|
|
|
end
|
|
|
|
|
|
|
|
|
|
{:ok, %Member{}} ->
|
|
|
|
|
{:error,
|
|
|
|
|
dgettext(
|
|
|
|
|
"errors",
|
|
|
|
|
"You don't have the role needed to remove this member."
|
|
|
|
|
)}
|
|
|
|
|
|
|
|
|
|
{:error, :member_not_found} ->
|
|
|
|
|
{:error,
|
|
|
|
|
dgettext(
|
|
|
|
|
"errors",
|
|
|
|
|
"You don't have the right to remove this member."
|
|
|
|
|
)}
|
|
|
|
|
end
|
2020-02-18 08:57:00 +01:00
|
|
|
|
end
|
2019-04-03 17:29:03 +02:00
|
|
|
|
end
|
2020-08-14 11:32:23 +02:00
|
|
|
|
|
2021-11-12 15:42:52 +01:00
|
|
|
|
def remove_member(_parent, _args, _resolution),
|
|
|
|
|
do:
|
|
|
|
|
{:error,
|
|
|
|
|
dgettext(
|
|
|
|
|
"errors",
|
|
|
|
|
"You must be logged-in to remove a member"
|
|
|
|
|
)}
|
|
|
|
|
|
2020-08-14 11:32:23 +02:00
|
|
|
|
# Rejected members can be invited again
|
|
|
|
|
@spec check_member_not_existant_or_rejected(String.t() | integer, String.t() | integer()) ::
|
|
|
|
|
boolean()
|
|
|
|
|
defp check_member_not_existant_or_rejected(target_actor_id, group_id) do
|
|
|
|
|
case Actors.get_member(target_actor_id, group_id) do
|
|
|
|
|
{:ok, %Member{role: :rejected}} ->
|
|
|
|
|
true
|
|
|
|
|
|
|
|
|
|
{:error, :member_not_found} ->
|
|
|
|
|
true
|
|
|
|
|
|
2020-08-27 11:53:24 +02:00
|
|
|
|
_err ->
|
2020-08-14 11:32:23 +02:00
|
|
|
|
false
|
|
|
|
|
end
|
|
|
|
|
end
|
2019-04-03 17:29:03 +02:00
|
|
|
|
end
|