Add group admin profiles
And other fixes Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
@@ -18,7 +18,7 @@ defmodule Mobilizon.GraphQL.API.Groups do
|
||||
with preferred_username <-
|
||||
args |> Map.get(:preferred_username) |> HTML.strip_tags() |> String.trim(),
|
||||
{:existing_group, nil} <-
|
||||
{:existing_group, Actors.get_local_group_by_title(preferred_username)},
|
||||
{:existing_group, Actors.get_local_actor_by_name(preferred_username)},
|
||||
args <- args |> Map.put(:type, :Group),
|
||||
{:ok, %Activity{} = activity, %Actor{} = group} <-
|
||||
ActivityPub.create(:actor, args, true, %{"actor" => args.creator_actor.url}) do
|
||||
|
||||
104
lib/graphql/resolvers/actor.ex
Normal file
104
lib/graphql/resolvers/actor.ex
Normal file
@@ -0,0 +1,104 @@
|
||||
defmodule Mobilizon.GraphQL.Resolvers.Actor do
|
||||
@moduledoc """
|
||||
Handles the group-related GraphQL calls.
|
||||
"""
|
||||
|
||||
import Mobilizon.Users.Guards
|
||||
alias Mobilizon.{Actors, Admin, Users}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
alias Mobilizon.Federation.ActivityPub.Refresher
|
||||
alias Mobilizon.Users.User
|
||||
|
||||
require Logger
|
||||
|
||||
def refresh_profile(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}})
|
||||
when is_admin(role) do
|
||||
case Actors.get_actor(id) do
|
||||
%Actor{domain: domain} = actor when not is_nil(domain) ->
|
||||
Refresher.refresh_profile(actor)
|
||||
{:ok, actor}
|
||||
|
||||
%Actor{} ->
|
||||
{:error, "Only remote actors may be refreshed"}
|
||||
|
||||
_ ->
|
||||
{:error, "No actor found with this ID"}
|
||||
end
|
||||
end
|
||||
|
||||
def suspend_profile(_parent, %{id: id}, %{
|
||||
context: %{current_user: %User{role: role} = user}
|
||||
})
|
||||
when is_moderator(role) do
|
||||
with {:moderator_actor, %Actor{} = moderator_actor} <-
|
||||
{:moderator_actor, Users.get_actor_for_user(user)},
|
||||
%Actor{suspended: false} = actor <- Actors.get_actor_with_preload(id) do
|
||||
case actor do
|
||||
# Suspend a group on this instance
|
||||
%Actor{type: :Group, domain: nil} ->
|
||||
Logger.debug("We're suspending a group on this very instance")
|
||||
ActivityPub.delete(actor, moderator_actor, true, %{suspension: true})
|
||||
Admin.log_action(moderator_actor, "suspend", actor)
|
||||
{:ok, actor}
|
||||
|
||||
# Delete a remote actor
|
||||
%Actor{domain: domain} when not is_nil(domain) ->
|
||||
Logger.debug("We're just deleting a remote instance")
|
||||
Actors.delete_actor(actor, suspension: true)
|
||||
Admin.log_action(moderator_actor, "suspend", actor)
|
||||
{:ok, actor}
|
||||
|
||||
%Actor{domain: nil} ->
|
||||
{:error, "No remote profile found with this ID"}
|
||||
end
|
||||
else
|
||||
{:moderator_actor, nil} ->
|
||||
{:error, "No actor found for the moderator user"}
|
||||
|
||||
%Actor{suspended: true} ->
|
||||
{:error, "Actor already suspended"}
|
||||
|
||||
{:error, _} ->
|
||||
{:error, "Error while performing background task"}
|
||||
end
|
||||
end
|
||||
|
||||
def suspend_profile(_parent, _args, _resolution) do
|
||||
{:error, "Only moderators and administrators can suspend a profile"}
|
||||
end
|
||||
|
||||
def unsuspend_profile(_parent, %{id: id}, %{
|
||||
context: %{current_user: %User{role: role} = user}
|
||||
})
|
||||
when is_moderator(role) do
|
||||
with {:moderator_actor, %Actor{} = moderator_actor} <-
|
||||
{:moderator_actor, Users.get_actor_for_user(user)},
|
||||
%Actor{suspended: true} = actor <-
|
||||
Actors.get_actor_with_preload(id, true),
|
||||
{:delete_tombstones, {_, nil}} <-
|
||||
{:delete_tombstones, Mobilizon.Tombstone.delete_actor_tombstones(id)},
|
||||
{:ok, %Actor{} = actor} <- Actors.update_actor(actor, %{suspended: false}),
|
||||
{:ok, %Actor{} = actor} <- refresh_if_remote(actor),
|
||||
{:ok, _} <- Admin.log_action(moderator_actor, "unsuspend", actor) do
|
||||
{:ok, actor}
|
||||
else
|
||||
{:moderator_actor, nil} ->
|
||||
{:error, "No actor found for the moderator user"}
|
||||
|
||||
nil ->
|
||||
{:error, "No remote profile found with this ID"}
|
||||
|
||||
{:error, _} ->
|
||||
{:error, "Error while performing background task"}
|
||||
end
|
||||
end
|
||||
|
||||
def unsuspend_profile(_parent, _args, _resolution) do
|
||||
{:error, "Only moderators and administrators can unsuspend a profile"}
|
||||
end
|
||||
|
||||
@spec refresh_if_remote(Actor.t()) :: {:ok, Actor.t()}
|
||||
defp refresh_if_remote(%Actor{domain: nil} = actor), do: {:ok, actor}
|
||||
defp refresh_if_remote(%Actor{} = actor), do: Refresher.refresh_profile(actor)
|
||||
end
|
||||
@@ -20,8 +20,9 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
|
||||
}
|
||||
) do
|
||||
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
|
||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
|
||||
{:ok, Discussions.find_discussions_for_actor(group_id)}
|
||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)},
|
||||
{:ok, %Actor{type: :Group} = group} <- Actors.get_group_by_actor_id(group_id) do
|
||||
{:ok, Discussions.find_discussions_for_actor(group)}
|
||||
else
|
||||
{:member, false} ->
|
||||
{:ok, %Page{total: 0, elements: []}}
|
||||
@@ -174,6 +175,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Discussion do
|
||||
{:ok, _activity, %Discussion{} = discussion} <-
|
||||
ActivityPub.delete(discussion, actor) do
|
||||
{:ok, discussion}
|
||||
else
|
||||
{:no_discussion, _} ->
|
||||
{:error, "No discussion with ID #{discussion_id}"}
|
||||
|
||||
{:member, _} ->
|
||||
{:error, "You are not a member of the group the discussion belongs to"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -3,6 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||
Handles the group-related GraphQL calls.
|
||||
"""
|
||||
|
||||
import Mobilizon.Users.Guards
|
||||
alias Mobilizon.{Actors, Events, Users}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
@@ -54,13 +55,46 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Get a group
|
||||
"""
|
||||
def get_group(_parent, %{id: id}, %{context: %{current_user: %User{role: role}}}) do
|
||||
with %Actor{type: :Group, suspended: suspended} = actor <-
|
||||
Actors.get_actor_with_preload(id, true),
|
||||
true <- suspended == false or is_moderator(role) do
|
||||
{:ok, actor}
|
||||
else
|
||||
_ ->
|
||||
{:error, "Group with ID #{id} not found"}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Lists all groups
|
||||
"""
|
||||
def list_groups(_parent, %{page: page, limit: limit}, _resolution) do
|
||||
{:ok, Actors.list_groups(page, limit)}
|
||||
def list_groups(
|
||||
_parent,
|
||||
%{
|
||||
preferred_username: preferred_username,
|
||||
name: name,
|
||||
domain: domain,
|
||||
local: local,
|
||||
suspended: suspended,
|
||||
page: page,
|
||||
limit: limit
|
||||
},
|
||||
%{
|
||||
context: %{current_user: %User{role: role}}
|
||||
}
|
||||
)
|
||||
when is_moderator(role) do
|
||||
{:ok,
|
||||
Actors.list_actors(:Group, preferred_username, name, domain, local, suspended, page, limit)}
|
||||
end
|
||||
|
||||
def list_groups(_parent, _args, _resolution),
|
||||
do: {:error, "You may not list groups unless moderator."}
|
||||
|
||||
@doc """
|
||||
Create a new group. The creator is automatically added as admin
|
||||
"""
|
||||
@@ -127,28 +161,23 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||
"""
|
||||
def delete_group(
|
||||
_parent,
|
||||
%{group_id: group_id, actor_id: actor_id},
|
||||
%{group_id: group_id},
|
||||
%{
|
||||
context: %{
|
||||
current_user: user
|
||||
}
|
||||
}
|
||||
) do
|
||||
with {actor_id, ""} <- Integer.parse(actor_id),
|
||||
{group_id, ""} <- Integer.parse(group_id),
|
||||
with %Actor{id: actor_id} = actor <- Users.get_actor_for_user(user),
|
||||
{:ok, %Actor{} = group} <- Actors.get_group_by_actor_id(group_id),
|
||||
{:is_owned, %Actor{}} <- User.owns_actor(user, actor_id),
|
||||
{:ok, %Member{} = member} <- Actors.get_member(actor_id, group.id),
|
||||
{:is_admin, true} <- {:is_admin, Member.is_administrator(member)},
|
||||
group <- Actors.delete_group!(group) do
|
||||
{:ok, _activity, group} <- ActivityPub.delete(group, actor, true) do
|
||||
{:ok, %{id: group.id}}
|
||||
else
|
||||
{:error, :group_not_found} ->
|
||||
{:error, "Group not found"}
|
||||
|
||||
{:is_owned, nil} ->
|
||||
{:error, "Actor id is not owned by authenticated user"}
|
||||
|
||||
{:error, :member_not_found} ->
|
||||
{:error, "Actor id is not a member of this group"}
|
||||
|
||||
@@ -231,7 +260,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||
{:group, nil} ->
|
||||
{:error, "Group not found"}
|
||||
|
||||
{:is_only_admin, true} ->
|
||||
{:is_not_only_admin, false} ->
|
||||
{:error, "You can't leave this group because you are the only administrator"}
|
||||
end
|
||||
end
|
||||
@@ -245,12 +274,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Group do
|
||||
_args,
|
||||
%{
|
||||
context: %{
|
||||
current_user: %User{} = user
|
||||
current_user: %User{role: user_role} = user
|
||||
}
|
||||
}
|
||||
) do
|
||||
with {:actor, %Actor{id: actor_id} = _actor} <- {:actor, Users.get_actor_for_user(user)},
|
||||
{:member, true} <- {:member, Actors.is_member?(actor_id, group_id)} do
|
||||
{:member, true} <-
|
||||
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)} do
|
||||
# TODO : Handle public / restricted to group members events
|
||||
{:ok, Events.list_organized_events_for_group(group)}
|
||||
else
|
||||
|
||||
@@ -3,6 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
|
||||
Handles the member-related GraphQL calls
|
||||
"""
|
||||
|
||||
import Mobilizon.Users.Guards
|
||||
alias Mobilizon.{Actors, Users}
|
||||
alias Mobilizon.Actors.{Actor, Member}
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
@@ -19,11 +20,12 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
|
||||
%Actor{id: group_id} = group,
|
||||
%{page: page, limit: limit, roles: roles},
|
||||
%{
|
||||
context: %{current_user: %User{} = user}
|
||||
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)} do
|
||||
{:member, true} <-
|
||||
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)} do
|
||||
roles =
|
||||
case roles do
|
||||
"" ->
|
||||
@@ -167,9 +169,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Member do
|
||||
{:error, :member_not_found} ->
|
||||
true
|
||||
|
||||
err ->
|
||||
require Logger
|
||||
Logger.error(inspect(err))
|
||||
_err ->
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,7 +7,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||
|
||||
alias Mobilizon.Actors
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Admin
|
||||
alias Mobilizon.Events
|
||||
alias Mobilizon.Events.Participant
|
||||
alias Mobilizon.Storage.Page
|
||||
@@ -321,64 +320,6 @@ defmodule Mobilizon.GraphQL.Resolvers.Person do
|
||||
end
|
||||
end
|
||||
|
||||
def suspend_profile(_parent, %{id: id}, %{
|
||||
context: %{current_user: %User{role: role} = user}
|
||||
})
|
||||
when is_moderator(role) do
|
||||
with {:moderator_actor, %Actor{} = moderator_actor} <-
|
||||
{:moderator_actor, Users.get_actor_for_user(user)},
|
||||
%Actor{suspended: false} = actor <- Actors.get_remote_actor_with_preload(id),
|
||||
{:ok, _} <- Actors.delete_actor(actor),
|
||||
{:ok, _} <- Admin.log_action(moderator_actor, "suspend", actor) do
|
||||
{:ok, actor}
|
||||
else
|
||||
{:moderator_actor, nil} ->
|
||||
{:error, "No actor found for the moderator user"}
|
||||
|
||||
%Actor{suspended: true} ->
|
||||
{:error, "Actor already suspended"}
|
||||
|
||||
nil ->
|
||||
{:error, "No remote profile found with this ID"}
|
||||
|
||||
{:error, _} ->
|
||||
{:error, "Error while performing background task"}
|
||||
end
|
||||
end
|
||||
|
||||
def suspend_profile(_parent, _args, _resolution) do
|
||||
{:error, "Only moderators and administrators can suspend a profile"}
|
||||
end
|
||||
|
||||
def unsuspend_profile(_parent, %{id: id}, %{
|
||||
context: %{current_user: %User{role: role} = user}
|
||||
})
|
||||
when is_moderator(role) do
|
||||
with {:moderator_actor, %Actor{} = moderator_actor} <-
|
||||
{:moderator_actor, Users.get_actor_for_user(user)},
|
||||
%Actor{preferred_username: preferred_username, domain: domain} = actor <-
|
||||
Actors.get_remote_actor_with_preload(id, true),
|
||||
{:ok, _} <- Actors.update_actor(actor, %{suspended: false}),
|
||||
{:ok, %Actor{} = actor} <-
|
||||
ActivityPub.make_actor_from_nickname("#{preferred_username}@#{domain}"),
|
||||
{:ok, _} <- Admin.log_action(moderator_actor, "unsuspend", actor) do
|
||||
{:ok, actor}
|
||||
else
|
||||
{:moderator_actor, nil} ->
|
||||
{:error, "No actor found for the moderator user"}
|
||||
|
||||
nil ->
|
||||
{:error, "No remote profile found with this ID"}
|
||||
|
||||
{:error, _} ->
|
||||
{:error, "Error while performing background task"}
|
||||
end
|
||||
end
|
||||
|
||||
def unsuspend_profile(_parent, _args, _resolution) do
|
||||
{:error, "Only moderators and administrators can unsuspend a profile"}
|
||||
end
|
||||
|
||||
# We check that the actor is not the last administrator/creator of a group
|
||||
@spec last_admin_of_a_group?(integer()) :: boolean()
|
||||
defp last_admin_of_a_group?(actor_id) do
|
||||
|
||||
@@ -3,6 +3,7 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
||||
Handles the posts-related GraphQL calls
|
||||
"""
|
||||
|
||||
import Mobilizon.Users.Guards
|
||||
alias Mobilizon.{Actors, Posts, Users}
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.Federation.ActivityPub
|
||||
@@ -24,12 +25,13 @@ defmodule Mobilizon.GraphQL.Resolvers.Post do
|
||||
%{page: page, limit: limit} = args,
|
||||
%{
|
||||
context: %{
|
||||
current_user: %User{} = user
|
||||
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)},
|
||||
{:member, true} <-
|
||||
{:member, Actors.is_member?(actor_id, group_id) or is_moderator(user_role)},
|
||||
%Page{} = page <- Posts.get_posts_for_group(group, page, limit) do
|
||||
{:ok, page}
|
||||
else
|
||||
|
||||
@@ -175,6 +175,7 @@ defmodule Mobilizon.GraphQL.Schema do
|
||||
import_fields(:discussion_mutations)
|
||||
import_fields(:resource_mutations)
|
||||
import_fields(:post_mutations)
|
||||
import_fields(:actor_mutations)
|
||||
end
|
||||
|
||||
@desc """
|
||||
|
||||
@@ -5,6 +5,7 @@ defmodule Mobilizon.GraphQL.Schema.ActorInterface do
|
||||
use Absinthe.Schema.Notation
|
||||
|
||||
alias Mobilizon.Actors.Actor
|
||||
alias Mobilizon.GraphQL.Resolvers.Actor, as: ActorResolver
|
||||
alias Mobilizon.GraphQL.Schema
|
||||
|
||||
import_types(Schema.Actors.FollowerType)
|
||||
@@ -59,4 +60,21 @@ defmodule Mobilizon.GraphQL.Schema.ActorInterface do
|
||||
value(:Organization, description: "An ActivityPub Organization")
|
||||
value(:Service, description: "An ActivityPub Service")
|
||||
end
|
||||
|
||||
object :actor_mutations do
|
||||
field :suspend_profile, :deleted_object do
|
||||
arg(:id, non_null(:id), description: "The profile ID to suspend")
|
||||
resolve(&ActorResolver.suspend_profile/3)
|
||||
end
|
||||
|
||||
field :unsuspend_profile, :actor do
|
||||
arg(:id, non_null(:id), description: "The profile ID to unsuspend")
|
||||
resolve(&ActorResolver.unsuspend_profile/3)
|
||||
end
|
||||
|
||||
field :refresh_profile, :actor do
|
||||
arg(:id, non_null(:id))
|
||||
resolve(&ActorResolver.refresh_profile/3)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -130,11 +130,22 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
||||
object :group_queries do
|
||||
@desc "Get all groups"
|
||||
field :groups, :paginated_group_list do
|
||||
arg(:preferred_username, :string, default_value: "")
|
||||
arg(:name, :string, default_value: "")
|
||||
arg(:domain, :string, default_value: "")
|
||||
arg(:local, :boolean, default_value: true)
|
||||
arg(:suspended, :boolean, default_value: false)
|
||||
arg(:page, :integer, default_value: 1)
|
||||
arg(:limit, :integer, default_value: 10)
|
||||
resolve(&Group.list_groups/3)
|
||||
end
|
||||
|
||||
@desc "Get a group by its ID"
|
||||
field :get_group, :group do
|
||||
arg(:id, non_null(:id))
|
||||
resolve(&Group.get_group/3)
|
||||
end
|
||||
|
||||
@desc "Get a group by its preferred username"
|
||||
field :group, :group do
|
||||
arg(:preferred_username, non_null(:string))
|
||||
@@ -199,7 +210,6 @@ defmodule Mobilizon.GraphQL.Schema.Actors.GroupType do
|
||||
@desc "Delete a group"
|
||||
field :delete_group, :deleted_object do
|
||||
arg(:group_id, non_null(:id))
|
||||
arg(:actor_id, non_null(:id))
|
||||
|
||||
resolve(&Group.delete_group/3)
|
||||
end
|
||||
|
||||
@@ -188,16 +188,6 @@ defmodule Mobilizon.GraphQL.Schema.Actors.PersonType do
|
||||
|
||||
resolve(handle_errors(&Person.register_person/3))
|
||||
end
|
||||
|
||||
field :suspend_profile, :deleted_object do
|
||||
arg(:id, :id, description: "The profile ID to suspend")
|
||||
resolve(&Person.suspend_profile/3)
|
||||
end
|
||||
|
||||
field :unsuspend_profile, :person do
|
||||
arg(:id, :id, description: "The profile ID to unsuspend")
|
||||
resolve(&Person.unsuspend_profile/3)
|
||||
end
|
||||
end
|
||||
|
||||
object :person_subscriptions do
|
||||
|
||||
@@ -33,6 +33,7 @@ defmodule Mobilizon.GraphQL.Schema.Discussions.CommentType do
|
||||
field(:inserted_at, :datetime)
|
||||
field(:updated_at, :datetime)
|
||||
field(:deleted_at, :datetime)
|
||||
field(:published_at, :datetime)
|
||||
end
|
||||
|
||||
@desc "The list of visibility options for a comment"
|
||||
|
||||
Reference in New Issue
Block a user