diff --git a/lib/decidim/cleaner.rb b/lib/decidim/cleaner.rb index b41bfb5..c202785 100644 --- a/lib/decidim/cleaner.rb +++ b/lib/decidim/cleaner.rb @@ -3,6 +3,7 @@ require "decidim/cleaner/admin" require "decidim/cleaner/engine" require "decidim/cleaner/admin_engine" +require "decidim/cleaner/extends/commands/decidim/destroy_account" module Decidim # This namespace holds the logic of the `Cleaner` module. diff --git a/lib/decidim/cleaner/engine.rb b/lib/decidim/cleaner/engine.rb index aa8cc87..ecb02cb 100644 --- a/lib/decidim/cleaner/engine.rb +++ b/lib/decidim/cleaner/engine.rb @@ -8,6 +8,10 @@ module Cleaner # This is the engine that runs on the public interface of cleaner. class Engine < ::Rails::Engine isolate_namespace Decidim::Cleaner + + config.to_prepare do + Decidim::DestroyAccount.include(Decidim::Cleaner::Extends::DestroyAccount) + end end end end diff --git a/lib/decidim/cleaner/extends/commands/decidim/destroy_account.rb b/lib/decidim/cleaner/extends/commands/decidim/destroy_account.rb new file mode 100644 index 0000000..24df182 --- /dev/null +++ b/lib/decidim/cleaner/extends/commands/decidim/destroy_account.rb @@ -0,0 +1,31 @@ +# frozen_string_literal: true + +module Decidim + module Cleaner + module Extends + # This command destroys the user's account. + module DestroyAccount + extend ActiveSupport::Concern + + included do + private + + # Invalidate all sessions after cleaning Decidim::User record to prevent Active Record error + def destroy_user_account! + @user.name = "" + @user.nickname = "" + @user.email = "" + @user.delete_reason = @form.delete_reason + @user.admin = false if @user.admin? + @user.deleted_at = Time.current + @user.skip_reconfirmation! + @user.avatar.purge + @user.save! + + @user.invalidate_all_sessions! + end + end + end + end + end +end diff --git a/spec/commands/decidim/destroy_account_spec.rb b/spec/commands/decidim/destroy_account_spec.rb new file mode 100644 index 0000000..9340693 --- /dev/null +++ b/spec/commands/decidim/destroy_account_spec.rb @@ -0,0 +1,108 @@ +# frozen_string_literal: true + +require "spec_helper" + +module Decidim + describe DestroyAccount do + let(:command) { described_class.new(user, form) } + let(:user) { create(:user, :confirmed) } + let!(:identity) { create(:identity, user:) } + let(:valid) { true } + let(:data) do + { + delete_reason: "I want to delete my account" + } + end + + let(:form) do + form = double( + delete_reason: data[:delete_reason], + valid?: valid + ) + + form + end + + context "when invalid" do + let(:valid) { false } + + it "broadcasts invalid" do + expect { command.call }.to broadcast(:invalid) + end + end + + context "when valid" do + let(:valid) { true } + + it "broadcasts ok" do + expect { command.call }.to broadcast(:ok) + end + + it "changes the auth salt to invalidate all other sessions" do + old_salt = user.authenticatable_salt + command.call + expect(user.reload.authenticatable_salt).not_to eq(old_salt) + end + + it "stores the deleted_at and delete_reason to the user" do + command.call + expect(user.reload.delete_reason).to eq(data[:delete_reason]) + expect(user.reload.deleted_at).not_to be_nil + end + + it "set name, nickname and email to blank string" do + command.call + expect(user.reload.name).to eq("") + expect(user.reload.nickname).to eq("") + expect(user.reload.email).to eq("") + end + + it "destroys the current user avatar" do + command.call + expect(user.reload.avatar).not_to be_present + end + + it "deletes user's identities" do + expect do + command.call + end.to change(Identity, :count).by(-1) + end + + it "deletes user group memberships" do + user_group = create(:user_group) + create(:user_group_membership, user_group:, user:) + + expect do + command.call + end.to change(UserGroupMembership, :count).by(-1) + end + + it "deletes the follows" do + other_user = create(:user) + create(:follow, followable: user, user: other_user) + create(:follow, followable: other_user, user:) + + expect do + command.call + end.to change(Follow, :count).by(-2) + end + + it "deletes participatory space private user" do + create(:participatory_space_private_user, user:) + + expect do + command.call + end.to change(ParticipatorySpacePrivateUser, :count).by(-1) + end + + context "when user is admin" do + let(:user) { create(:user, :confirmed, :admin) } + + it "removes admin role" do + command.call + expect(user.reload.admin).to be_falsey + end + end + end + end +end \ No newline at end of file