Add a CLI command to delete actors
Signed-off-by: Thomas Citharel <tcit@tcit.fr>
This commit is contained in:
parent
071185204e
commit
5bc63185fd
110
lib/mix/tasks/mobilizon/actors/delete.ex
Normal file
110
lib/mix/tasks/mobilizon/actors/delete.ex
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
defmodule Mix.Tasks.Mobilizon.Actors.Delete do
|
||||||
|
@moduledoc """
|
||||||
|
Task to delete an actor
|
||||||
|
"""
|
||||||
|
use Mix.Task
|
||||||
|
alias Mobilizon.{Actors, Users}
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Service.ActorSuspension
|
||||||
|
alias Mobilizon.Users.User
|
||||||
|
import Mix.Tasks.Mobilizon.Common
|
||||||
|
|
||||||
|
@shortdoc "Deletes a Mobilizon person or a group"
|
||||||
|
|
||||||
|
@impl Mix.Task
|
||||||
|
def run([federated_username | rest]) do
|
||||||
|
{options, [], []} =
|
||||||
|
OptionParser.parse(
|
||||||
|
rest,
|
||||||
|
strict: [
|
||||||
|
assume_yes: :boolean,
|
||||||
|
keep_username: :boolean
|
||||||
|
],
|
||||||
|
aliases: [
|
||||||
|
y: :assume_yes,
|
||||||
|
k: :keep_username
|
||||||
|
]
|
||||||
|
)
|
||||||
|
|
||||||
|
assume_yes? = Keyword.get(options, :assume_yes, false)
|
||||||
|
keep_username? = Keyword.get(options, :keep_username, false)
|
||||||
|
|
||||||
|
start_mobilizon()
|
||||||
|
|
||||||
|
# To make sure we can delete actors created by mistake with "@" in their username
|
||||||
|
case Actors.get_local_actor_by_name(federated_username) ||
|
||||||
|
Actors.get_actor_by_name(federated_username) do
|
||||||
|
%Actor{preferred_username: username, domain: nil} when username in ["relay", "anonymous"] ->
|
||||||
|
shell_error("This actor can't be deleted.")
|
||||||
|
|
||||||
|
%Actor{} = actor ->
|
||||||
|
if check_everything(actor, assume_yes?) do
|
||||||
|
ActorSuspension.suspend_actor(actor,
|
||||||
|
reserve_username: keep_username?,
|
||||||
|
suspension: false
|
||||||
|
)
|
||||||
|
|
||||||
|
display_name = Actor.display_name_and_username(actor)
|
||||||
|
|
||||||
|
shell_info("""
|
||||||
|
The actor #{display_name} has been deleted
|
||||||
|
""")
|
||||||
|
else
|
||||||
|
shell_error("Actor has not been deleted.")
|
||||||
|
end
|
||||||
|
|
||||||
|
nil ->
|
||||||
|
shell_error("No actor found with this username")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def run(_) do
|
||||||
|
shell_error(
|
||||||
|
"mobilizon.actors.delete requires an username or a federated username as argument"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec check_everything(Actor.t(), boolean()) :: boolean()
|
||||||
|
defp check_everything(%Actor{} = actor, assume_yes?) do
|
||||||
|
display_name = Actor.display_name_and_username(actor)
|
||||||
|
|
||||||
|
(assume_yes? or
|
||||||
|
shell_yes?(
|
||||||
|
"All content by this profile or group will be deleted. Continue with deleting #{display_name}?"
|
||||||
|
)) and
|
||||||
|
check_actor(actor, assume_yes?)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec check_actor(Actor.t(), boolean()) :: boolean()
|
||||||
|
defp check_actor(%Actor{type: :Group} = group, assume_yes?) do
|
||||||
|
display_name = Actor.display_name_and_username(group)
|
||||||
|
nb_members = Actors.count_members_for_group(group)
|
||||||
|
nb_followers = Actors.count_followers_for_actor(group)
|
||||||
|
|
||||||
|
if nb_followers + nb_members > 0 do
|
||||||
|
shell_info("Group members will be notified of the group deletion.")
|
||||||
|
|
||||||
|
assume_yes? or
|
||||||
|
shell_yes?(
|
||||||
|
"Group #{display_name} has #{nb_members} members and #{nb_followers} followers. Continue deleting?"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_actor(%Actor{type: :Person, domain: nil} = profile, assume_yes?) do
|
||||||
|
%User{actors: actors, email: email} = Users.get_user_with_actors!(profile.user_id)
|
||||||
|
|
||||||
|
if length(actors) == 1 do
|
||||||
|
assume_yes? or
|
||||||
|
shell_yes?(
|
||||||
|
"This profile is the only one user #{email} has. Mobilizon will invite the user to create a new profile on their next login. If you want to remove the whole user account, use the `mobilizon.users.delete` command. Continue deleting?"
|
||||||
|
)
|
||||||
|
else
|
||||||
|
true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp check_actor(%Actor{} = _actor, assume_yes?), do: assume_yes?
|
||||||
|
end
|
178
test/tasks/actors/delete_test.exs
Normal file
178
test/tasks/actors/delete_test.exs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
defmodule Mix.Tasks.Mobilizon.Actors.DeleteTest do
|
||||||
|
use Mobilizon.DataCase
|
||||||
|
|
||||||
|
import Mobilizon.Factory
|
||||||
|
|
||||||
|
alias Mix.Tasks.Mobilizon.Actors.Delete
|
||||||
|
|
||||||
|
alias Mobilizon.Actors
|
||||||
|
alias Mobilizon.Actors.Actor
|
||||||
|
alias Mobilizon.Federation.ActivityPub.Relay
|
||||||
|
alias Mobilizon.Users.User
|
||||||
|
|
||||||
|
Mix.shell(Mix.Shell.Process)
|
||||||
|
|
||||||
|
@preferred_username "toto"
|
||||||
|
@name "Léo Pandaï"
|
||||||
|
|
||||||
|
describe "delete local profile" do
|
||||||
|
setup do
|
||||||
|
%User{} = user = insert(:user)
|
||||||
|
|
||||||
|
%Actor{} =
|
||||||
|
profile = insert(:actor, user: user, preferred_username: @preferred_username, name: @name)
|
||||||
|
|
||||||
|
{:ok, user: user, profile: profile}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete when username isn't set" do
|
||||||
|
Delete.run([])
|
||||||
|
|
||||||
|
# Debug message
|
||||||
|
assert_received {:mix_shell, :error, [message]}
|
||||||
|
|
||||||
|
assert message =~
|
||||||
|
"mobilizon.actors.delete requires an username or a federated username as argument"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete when no actor can't be found" do
|
||||||
|
Delete.run(["other_one"])
|
||||||
|
|
||||||
|
# Debug message
|
||||||
|
assert_received {:mix_shell, :error, [message]}
|
||||||
|
|
||||||
|
assert message =~
|
||||||
|
"No actor found with this username"
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete with -y", %{profile: profile} do
|
||||||
|
Delete.run([@preferred_username, "-y"])
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [message]}
|
||||||
|
refute_received {:mix_shell, :yes?}
|
||||||
|
|
||||||
|
assert message =~
|
||||||
|
"The actor #{Actor.display_name_and_username(profile)} has been deleted"
|
||||||
|
|
||||||
|
assert nil == Actors.get_actor_by_name(@preferred_username)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "delete while accepting", %{profile: profile} do
|
||||||
|
display_name = Actor.display_name_and_username(profile)
|
||||||
|
send(self(), {:mix_shell_input, :yes?, true})
|
||||||
|
send(self(), {:mix_shell_input, :yes?, true})
|
||||||
|
|
||||||
|
Delete.run([@preferred_username])
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :yes?, [input]}
|
||||||
|
|
||||||
|
assert input ==
|
||||||
|
"All content by this profile or group will be deleted. Continue with deleting #{display_name}?"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :yes?, [input2]}
|
||||||
|
|
||||||
|
assert input2 ==
|
||||||
|
"This profile is the only one user #{profile.user.email} has. Mobilizon will invite the user to create a new profile on their next login. If you want to remove the whole user account, use the `mobilizon.users.delete` command. Continue deleting?"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [message2]}
|
||||||
|
assert message2 =~ "The actor #{display_name} has been deleted"
|
||||||
|
|
||||||
|
assert nil == Actors.get_actor_by_name(@preferred_username)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete group" do
|
||||||
|
@group_username "already_there"
|
||||||
|
@profile_username "theo"
|
||||||
|
|
||||||
|
setup do
|
||||||
|
group = insert(:group, preferred_username: @group_username)
|
||||||
|
profile = insert(:actor, preferred_username: @profile_username)
|
||||||
|
insert(:member, parent: group, actor: profile, role: :administrator)
|
||||||
|
insert(:member, parent: group, role: :member)
|
||||||
|
{:ok, group: group, profile: profile}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when everything is accepted", %{group: group} do
|
||||||
|
display_name = Actor.display_name_and_username(group)
|
||||||
|
send(self(), {:mix_shell_input, :yes?, true})
|
||||||
|
send(self(), {:mix_shell_input, :yes?, true})
|
||||||
|
Delete.run([@group_username])
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :yes?, [input]}
|
||||||
|
|
||||||
|
assert input ==
|
||||||
|
"All content by this profile or group will be deleted. Continue with deleting #{display_name}?"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :yes?, [input2]}
|
||||||
|
assert input2 == "Group #{display_name} has 2 members and 0 followers. Continue deleting?"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [message]}
|
||||||
|
assert message =~ "Group members will be notified of the group deletion."
|
||||||
|
assert_received {:mix_shell, :info, [message2]}
|
||||||
|
assert message2 =~ "The actor #{display_name} has been deleted"
|
||||||
|
|
||||||
|
assert nil == Actors.get_actor_by_name(@preferred_username)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "when something is rejected", %{group: group} do
|
||||||
|
display_name = Actor.display_name_and_username(group)
|
||||||
|
send(self(), {:mix_shell_input, :yes?, false})
|
||||||
|
Delete.run([@group_username])
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :yes?, [input]}
|
||||||
|
|
||||||
|
assert input ==
|
||||||
|
"All content by this profile or group will be deleted. Continue with deleting #{display_name}?"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :error, [message]}
|
||||||
|
assert message =~ "Actor has not been deleted."
|
||||||
|
|
||||||
|
assert nil == Actors.get_actor_by_name(@preferred_username)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "with assume_yes option", %{group: group} do
|
||||||
|
display_name = Actor.display_name_and_username(group)
|
||||||
|
Delete.run([@group_username, "-y"])
|
||||||
|
|
||||||
|
refute_received {:mix_shell, :yes?}
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :info, [message]}
|
||||||
|
assert message =~ "Group members will be notified of the group deletion."
|
||||||
|
assert_received {:mix_shell, :info, [message2]}
|
||||||
|
assert message2 =~ "The actor #{display_name} has been deleted"
|
||||||
|
|
||||||
|
assert nil == Actors.get_actor_by_name(@preferred_username)
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails when called for an internal actor" do
|
||||||
|
Relay.get_actor()
|
||||||
|
Delete.run(["relay", "-y"])
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :error, [message]}
|
||||||
|
assert message =~ "This actor can't be deleted."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "delete something else" do
|
||||||
|
@actor_username "whatever"
|
||||||
|
|
||||||
|
setup do
|
||||||
|
actor = insert(:actor, preferred_username: @actor_username, type: :Application)
|
||||||
|
{:ok, actor: actor}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "fails", %{actor: actor} do
|
||||||
|
display_name = Actor.display_name_and_username(actor)
|
||||||
|
send(self(), {:mix_shell_input, :yes?, true})
|
||||||
|
Delete.run([@actor_username])
|
||||||
|
assert_received {:mix_shell, :yes?, [input]}
|
||||||
|
|
||||||
|
assert input ==
|
||||||
|
"All content by this profile or group will be deleted. Continue with deleting #{display_name}?"
|
||||||
|
|
||||||
|
assert_received {:mix_shell, :error, [message2]}
|
||||||
|
assert message2 =~ "Actor has not been deleted."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
Loading…
Reference in New Issue
Block a user