defmodule Mobilizon.GraphQL.Resolvers.Member do @moduledoc """ Handles the member-related GraphQL calls """ import Mobilizon.Users.Guards alias Mobilizon.{Actors, Users} alias Mobilizon.Actors.{Actor, Member} alias Mobilizon.Federation.ActivityPub alias Mobilizon.Federation.ActivityPub.Refresher alias Mobilizon.Storage.Page alias Mobilizon.Users.User import Mobilizon.Web.Gettext @doc """ Find members for group. If actor requesting is not part of the group, we only return the number of members, not members """ def find_members_for_group( %Actor{id: group_id} = group, %{page: page, limit: limit, roles: roles}, %{ context: %{current_user: %User{role: user_role} = user} } = _resolution ) do with %Actor{id: actor_id} <- Users.get_actor_for_user(user), {:member, true} <- {:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)} do roles = case roles do "" -> [] roles -> roles |> String.split(",") |> Enum.map(&String.downcase/1) |> Enum.map(&String.to_existing_atom/1) end %Page{} = page = Actors.list_members_for_group(group, roles, page, limit) {:ok, page} else {:member, false} -> # Actor is not member of group, fallback to public with %Page{} = page <- Actors.list_members_for_group(group) do {:ok, %Page{page | elements: []}} end end end def find_members_for_group(%Actor{} = group, _args, _resolution) do with %Page{} = page <- Actors.list_members_for_group(group) do {:ok, %Page{page | elements: []}} end end def invite_member( _parent, %{group_id: group_id, target_actor_username: target_actor_username}, %{context: %{current_user: %User{} = user}} ) do with %Actor{id: actor_id} = actor <- Users.get_actor_for_user(user), {:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id), {: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)}, {:target_actor_username, {:ok, %Actor{id: target_actor_id} = target_actor}} <- {:target_actor_username, ActivityPub.find_or_make_actor_from_nickname(target_actor_username)}, true <- check_member_not_existant_or_rejected(target_actor_id, group.id), {:ok, _activity, %Member{} = member} <- ActivityPub.invite(group, actor, target_actor) do {:ok, member} else {:is_owned, nil} -> {:error, dgettext("errors", "Profile is not owned by authenticated user")} {:error, :group_not_found} -> {:error, dgettext("errors", "Group not found")} {:target_actor_username, _} -> {:error, dgettext("errors", "Profile invited doesn't exist")} {:has_rights_to_invite, {:error, :member_not_found}} -> {:error, dgettext("errors", "You are not a member of this group")} {:has_rights_to_invite, _} -> {:error, dgettext("errors", "You cannot invite to this group")} {:ok, %Member{}} -> {:error, dgettext("errors", "Profile is already a member of this group")} end end def accept_invitation(_parent, %{id: member_id}, %{context: %{current_user: %User{} = user}}) do with %Actor{id: actor_id} <- Users.get_actor_for_user(user), %Member{actor: %Actor{id: member_actor_id} = actor} = member <- Actors.get_member(member_id), {:is_same_actor, true} <- {:is_same_actor, member_actor_id === actor_id}, {:ok, _activity, %Member{} = member} <- ActivityPub.accept( :invite, member, true ) do # Launch an async task to refresh the group profile, fetch resources, discussions, members Refresher.fetch_group(member.parent.url, actor) {:ok, member} end end def reject_invitation(_parent, %{id: member_id}, %{context: %{current_user: %User{} = user}}) do with %Actor{id: actor_id} <- Users.get_actor_for_user(user), %Member{actor: %Actor{id: member_actor_id}} = member <- Actors.get_member(member_id), {:is_same_actor, true} <- {:is_same_actor, member_actor_id === actor_id}, {:ok, _activity, %Member{} = member} <- ActivityPub.reject( :invite, member, true ) do {:ok, member} end end def update_member(_parent, %{member_id: member_id, role: role}, %{ context: %{current_user: %User{} = user} }) do with %Actor{} = moderator <- Users.get_actor_for_user(user), %Member{} = member <- Actors.get_member(member_id), {:ok, _activity, %Member{} = member} <- ActivityPub.update(member, %{role: role}, true, %{moderator: moderator}) do {:ok, member} else {:has_rights_to_update_role, {:error, :member_not_found}} -> {:error, dgettext("errors", "You are not a moderator or admin for this group")} {:is_only_admin, true} -> {:error, dgettext( "errors", "You can't set yourself to a lower member role for this group because you are the only administrator" )} end end def update_member(_parent, _args, _resolution), do: {:error, "You must be logged-in to update a member"} def remove_member(_parent, %{member_id: member_id, group_id: group_id}, %{ context: %{current_user: %User{} = user} }) do with %Actor{id: moderator_id} = moderator <- Users.get_actor_for_user(user), %Member{} = member <- Actors.get_member(member_id), %Actor{type: :Group} = group <- Actors.get_actor(group_id), {:has_rights_to_invite, {:ok, %Member{role: role}}} when role in [:moderator, :administrator, :creator] <- {:has_rights_to_invite, Actors.get_member(moderator_id, group_id)}, {:ok, _activity, %Member{}} <- ActivityPub.remove(member, group, moderator, true) do {:ok, member} end end # 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 _err -> false end end end