
2 changed files with 288 additions and 0 deletions
@ -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 |
@ -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