add a way to delete posts and topics when deleting a user with UserDestroyer

This commit is contained in:
Neil Lalonde 2013-07-24 13:48:55 -04:00
parent a8df9778b5
commit e25638dab0
6 changed files with 87 additions and 49 deletions

View File

@ -117,11 +117,15 @@ class Admin::UsersController < Admin::AdminController
def destroy
user = User.where(id: params[:id]).first
guardian.ensure_can_delete_user!(user)
if UserDestroyer.new(current_user).destroy(user)
begin
if UserDestroyer.new(current_user).destroy(user, params.slice(:delete_posts))
render json: {deleted: true}
else
render json: {deleted: false, user: AdminDetailedUserSerializer.new(user, root: false).as_json}
end
rescue UserDestroyer::PostsExistError
raise Discourse::InvalidAccess.new("User #{user.username} has #{user.post_count} posts, so can't be deleted.")
end
end

View File

@ -150,8 +150,8 @@ class Guardian
user && is_staff?
end
def can_delete_user?(user_to_delete)
can_administer?(user_to_delete) && user_to_delete.post_count <= 0
def can_delete_user?(user)
can_administer?(user)
end
# Can we see who acted on a post in a particular way?

View File

@ -11,10 +11,16 @@ class UserDestroyer
# Returns false if the user failed to be deleted.
# Returns a frozen instance of the User if the delete succeeded.
def destroy(user)
def destroy(user, opts={})
raise Discourse::InvalidParameters.new('user is nil') unless user and user.is_a?(User)
raise PostsExistError if user.post_count != 0
raise PostsExistError if !opts[:delete_posts] && user.post_count != 0
User.transaction do
if opts[:delete_posts]
user.posts.each do |post|
PostDestroyer.new(@admin, post).destroy
end
raise PostsExistError if user.reload.post_count != 0
end
user.destroy.tap do |u|
if u
Post.with_deleted.where(user_id: user.id).update_all("nuked_user = true")

View File

@ -1008,9 +1008,9 @@ describe Guardian do
end
context "for admins" do
it "is false if user has posts" do
Fabricate(:post, user: user)
Guardian.new(admin).can_delete_user?(user).should be_false
it "is true if user has posts" do # UserDestroyer is responsible for checking for posts
user.stubs(:post_count).returns(1)
Guardian.new(admin).can_delete_user?(user).should be_true
end
it "is true if user has no posts" do

View File

@ -35,8 +35,6 @@ describe UserDestroyer do
@user = Fabricate(:user)
end
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
it 'raises an error when user is nil' do
expect { UserDestroyer.new(@admin).destroy(nil) }.to raise_error(Discourse::InvalidParameters)
end
@ -45,32 +43,7 @@ describe UserDestroyer do
expect { UserDestroyer.new(@admin).destroy('nothing') }.to raise_error(Discourse::InvalidParameters)
end
context 'user has posts' do
before do
Fabricate(:post, user: @user)
end
it 'should not delete the user' do
expect { destroy rescue nil }.to_not change { User.count }
end
it 'should raise an error' do
expect { destroy }.to raise_error( UserDestroyer::PostsExistError )
end
it 'should not log the action' do
StaffActionLogger.any_instance.expects(:log_user_deletion).never
destroy rescue nil
end
it 'should not unregister the user at the discourse hub' do
DiscourseHub.expects(:unregister_nickname).never
destroy rescue nil
end
end
context 'user has no posts' do
context 'and destroy succeeds' do
shared_examples "successfully destroy a user" do
it 'should delete the user' do
expect { destroy }.to change { User.count }.by(-1)
end
@ -97,6 +70,52 @@ describe UserDestroyer do
DiscourseHub.expects(:unregister_nickname).never
destroy
end
end
context 'user has posts' do
let!(:post) { Fabricate(:post, user: @user) }
context "delete_posts is false" do
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
it 'should not delete the user' do
expect { destroy rescue nil }.to_not change { User.count }
end
it 'should raise an error' do
expect { destroy }.to raise_error( UserDestroyer::PostsExistError )
end
it 'should not log the action' do
StaffActionLogger.any_instance.expects(:log_user_deletion).never
destroy rescue nil
end
it 'should not unregister the user at the discourse hub' do
DiscourseHub.expects(:unregister_nickname).never
destroy rescue nil
end
end
context "delete_posts is true" do
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user, delete_posts: true) }
include_examples "successfully destroy a user"
it "deletes the posts" do
destroy
post.reload.deleted_at.should_not be_nil
post.nuked_user.should be_true
end
end
end
context 'user has no posts' do
context 'and destroy succeeds' do
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
include_examples "successfully destroy a user"
it "should mark the user's deleted posts as belonging to a nuked user" do
post = Fabricate(:post, user: @user, deleted_at: 1.hour.ago)
@ -106,6 +125,8 @@ describe UserDestroyer do
end
context 'and destroy fails' do
subject(:destroy) { UserDestroyer.new(@admin).destroy(@user) }
before do
@user.stubs(:destroy).returns(false)
end

View File

@ -218,6 +218,13 @@ describe Admin::UsersController do
response.should be_forbidden
end
it "doesn't return an error if the user has posts and delete_posts == true" do
Fabricate(:post, user: @delete_me)
UserDestroyer.any_instance.expects(:destroy).with(@delete_me, has_entry('delete_posts' => true)).returns(true)
xhr :delete, :destroy, id: @delete_me.id, delete_posts: true
response.should be_success
end
it "deletes the user record" do
UserDestroyer.any_instance.expects(:destroy).returns(true)
xhr :delete, :destroy, id: @delete_me.id