Improve member adding and excluding flow

Allow to exclude a member

Send emails to the member when it's excluded

Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
Thomas Citharel
2020-08-14 11:32:23 +02:00
parent ad13a57afc
commit 156eba0551
94 changed files with 2650 additions and 1862 deletions

View File

@@ -51,7 +51,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
) do
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
%CommentModel{actor_id: comment_actor_id} = comment <-
Mobilizon.Discussions.get_comment(comment_id),
Mobilizon.Discussions.get_comment_with_preload(comment_id),
true <- actor_id === comment_actor_id,
{:ok, _, %CommentModel{} = comment} <- Comments.update_comment(comment, %{text: text}) do
{:ok, comment}
@@ -64,15 +64,14 @@ defmodule Mobilizon.GraphQL.Resolvers.Comment do
def delete_comment(
_parent,
%{actor_id: actor_id, comment_id: comment_id},
%{comment_id: comment_id},
%{
context: %{
current_user: %User{role: role} = user
}
}
) do
with {actor_id, ""} <- Integer.parse(actor_id),
{:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
with {:actor, %Actor{id: actor_id} = actor} <- {:actor, Users.get_actor_for_user(user)},
%CommentModel{deleted_at: nil} = comment <-
Discussions.get_comment_with_preload(comment_id) do
cond do

View File

@@ -213,40 +213,25 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
"""
def leave_group(
_parent,
%{group_id: group_id, actor_id: actor_id},
%{group_id: group_id},
%{
context: %{
current_user: user
current_user: %User{} = user
}
}
) do
with {actor_id, ""} <- Integer.parse(actor_id),
{group_id, ""} <- Integer.parse(group_id),
{:is_owned, %Actor{} = actor} <- User.owns_actor(user, actor_id),
{:ok, %Member{} = member} <- Actors.get_member(actor.id, group_id),
{:only_administrator, false} <-
{:only_administrator, check_that_member_is_not_last_administrator(group_id, actor_id)},
{:ok, _} <-
Mobilizon.Actors.delete_member(member) do
{
:ok,
%{
parent: %{
id: group_id
},
actor: %{
id: actor_id
}
}
}
with {:actor, %Actor{} = actor} <- {:actor, Users.get_actor_for_user(user)},
{:group, %Actor{type: :Group} = group} <- {:group, Actors.get_actor(group_id)},
{:ok, _activity, %Member{} = member} <- ActivityPub.leave(group, actor, true) do
{:ok, member}
else
{:is_owned, nil} ->
{:error, "Actor id is not owned by authenticated user"}
{:error, :member_not_found} ->
{:error, "Member not found"}
{:only_administrator, true} ->
{:group, nil} ->
{:error, "Group not found"}
{:is_only_admin, true} ->
{:error, "You can't leave this group because you are the only administrator"}
end
end
@@ -278,32 +263,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
{:ok, %Page{total: 0, elements: []}}
end
# We check that the actor asking to leave the group is not it's only administrator
# We start by fetching the list of administrator or creators and if there's only one of them
# and that it's the actor requesting leaving the group we return true
@spec check_that_member_is_not_last_administrator(integer, integer) :: boolean
defp check_that_member_is_not_last_administrator(group_id, actor_id) do
case Actors.list_administrator_members_for_group(group_id) do
%Page{total: total} when total > 1 ->
true
%Page{
total: 1,
elements: [
%Member{
actor: %Actor{
id: member_actor_id
}
}
]
} ->
actor_id == member_actor_id
_ ->
false
end
end
defp restrict_fields_for_non_member_request(%Actor{} = group) do
Map.merge(
group,

View File

@@ -6,6 +6,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
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
@@ -65,7 +66,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
{:target_actor_username, {:ok, %Actor{id: target_actor_id} = target_actor}} <-
{:target_actor_username,
ActivityPub.find_or_make_actor_from_nickname(target_actor_username)},
{:error, :member_not_found} <- Actors.get_member(target_actor_id, group.id),
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
@@ -91,7 +92,8 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
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}} = member <- Actors.get_member(member_id),
%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(
@@ -100,7 +102,51 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
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 remove_member(_parent, %{member_id: member_id, group_id: group_id}, %{
context: %{current_user: %User{} = user}
}) do
with %Actor{} = moderator <- Users.get_actor_for_user(user),
%Member{} = member <- Actors.get_member(member_id),
%Actor{type: :Group} = group <- Actors.get_actor(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 ->
require Logger
Logger.error(inspect(err))
false
end
end
end

View File

@@ -16,6 +16,7 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
field(:role, :member_role_enum, description: "The role of this membership")
field(:invited_by, :person, description: "Who invited this member")
field(:inserted_at, :naive_datetime, description: "When was this member created")
field(:updated_at, :naive_datetime, description: "When was this member updated")
end
enum :member_role_enum do
@@ -28,12 +29,6 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
value(:rejected)
end
@desc "Represents a deleted member"
object :deleted_member do
field(:parent, :deleted_object)
field(:actor, :deleted_object)
end
object :paginated_member_list do
field(:elements, list_of(:member), description: "A list of members")
field(:total, :integer, description: "The total number of elements in the list")
@@ -49,9 +44,8 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
end
@desc "Leave a group"
field :leave_group, :deleted_member do
field :leave_group, :deleted_object do
arg(:group_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Group.leave_group/3)
end
@@ -70,5 +64,20 @@ defmodule Mobilizon.GraphQL.Schema.Actors.MemberType do
resolve(&Member.accept_invitation/3)
end
@desc "Reject an invitation to a group"
field :reject_invitation, :member do
arg(:id, non_null(:id))
resolve(&Member.reject_invitation/3)
end
@desc "Remove a member from a group"
field :remove_member, :member do
arg(:group_id, non_null(:id))
arg(:member_id, non_null(:id))
resolve(&Member.remove_member/3)
end
end
end

View File

@@ -80,9 +80,9 @@ defmodule Mobilizon.GraphQL.Schema.Discussions.CommentType do
resolve(&Comment.update_comment/3)
end
@desc "Delete a single comment"
field :delete_comment, type: :comment do
arg(:comment_id, non_null(:id))
arg(:actor_id, non_null(:id))
resolve(&Comment.delete_comment/3)
end