diff --git a/app/assets/javascripts/discourse/controllers/queued-post.js.es6 b/app/assets/javascripts/discourse/controllers/queued-post.js.es6 index 47ac01a00..100b0b158 100644 --- a/app/assets/javascripts/discourse/controllers/queued-post.js.es6 +++ b/app/assets/javascripts/discourse/controllers/queued-post.js.es6 @@ -1,10 +1,16 @@ import BufferedContent from 'discourse/mixins/buffered-content'; import { popupAjaxError } from 'discourse/lib/ajax-error'; -function updateState(state) { +function updateState(state, opts) { + opts = opts || {}; + return function() { const post = this.get('post'); - post.update({ state }).then(() => { + const args = { state }; + + if (opts.deleteUser) { args.delete_user = true; } + + post.update(args).then(() => { this.get('controllers.queued-posts.model').removeObject(post); }).catch(popupAjaxError); }; @@ -17,10 +23,18 @@ export default Ember.Controller.extend(BufferedContent, { editing: Discourse.computed.propertyEqual('model', 'currentlyEditing'), + _confirmDelete: updateState('rejected', {deleteUser: true}), + actions: { approve: updateState('approved'), reject: updateState('rejected'), + deleteUser() { + bootbox.confirm(I18n.t('queue.delete_prompt', {username: this.get('model.user.username')}), (confirmed) => { + if (confirmed) { this._confirmDelete(); } + }); + }, + edit() { // This is stupid but pagedown cannot be on the screen twice or it will break this.set('currentlyEditing', null); diff --git a/app/assets/javascripts/discourse/models/result-set.js.es6 b/app/assets/javascripts/discourse/models/result-set.js.es6 index bb0d54b2e..cda174b60 100644 --- a/app/assets/javascripts/discourse/models/result-set.js.es6 +++ b/app/assets/javascripts/discourse/models/result-set.js.es6 @@ -13,7 +13,7 @@ export default Ember.ArrayProxy.extend({ this.set('loadingMore', true); const self = this; - return this.store.appendResults(this, this.get('__type'), loadMoreUrl).then(function() { + return this.store.appendResults(this, this.get('__type'), loadMoreUrl).finally(function() { self.set('loadingMore', false); }); } @@ -29,7 +29,7 @@ export default Ember.ArrayProxy.extend({ const self = this; this.set('refreshing', true); - return this.store.refreshResults(this, this.get('__type'), refreshUrl).then(function() { + return this.store.refreshResults(this, this.get('__type'), refreshUrl).finally(function() { self.set('refreshing', false); }); diff --git a/app/assets/javascripts/discourse/templates/queued-posts.hbs b/app/assets/javascripts/discourse/templates/queued-posts.hbs index 3c9e0b047..9b16c088b 100644 --- a/app/assets/javascripts/discourse/templates/queued-posts.hbs +++ b/app/assets/javascripts/discourse/templates/queued-posts.hbs @@ -50,7 +50,6 @@ icon="times" disabled=ctrl.post.isSaving class="btn-danger cancel"}} - {{else}} {{d-button action="approve" disabled=ctrl.post.isSaving @@ -62,6 +61,11 @@ label="queue.reject" icon="times" class="btn-danger reject"}} + {{d-button action="deleteUser" + disabled=ctrl.post.isSaving + label="queue.delete_user" + icon="trash" + class="btn-danger delete-user"}} {{d-button action="edit" disabled=ctrl.post.isSaving label="queue.edit" diff --git a/app/controllers/queued_posts_controller.rb b/app/controllers/queued_posts_controller.rb index 8ca61a962..bfa139121 100644 --- a/app/controllers/queued_posts_controller.rb +++ b/app/controllers/queued_posts_controller.rb @@ -29,9 +29,29 @@ class QueuedPostsController < ApplicationController qp.approve!(current_user) elsif state == 'rejected' qp.reject!(current_user) + if params[:queued_post][:delete_user] == 'true' && guardian.can_delete_user?(qp.user) + UserDestroyer.new(current_user).destroy(qp.user, user_deletion_opts) + end end render_serialized(qp, QueuedPostSerializer, root: :queued_posts) end + + private + + def user_deletion_opts + base = { + context: I18n.t('queue.delete_reason', {performed_by: current_user.username}), + delete_posts: true, + delete_as_spammer: true + } + + if Rails.env.production? && ENV["Staging"].nil? + base.merge!({block_email: true, block_ip: true}) + end + + base + end + end diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 3dc116371..e0ab53a68 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -231,11 +231,13 @@ en: topic: "Topic:" approve: 'Approve' reject: 'Reject' + delete_user: 'Delete User' title: "Needs Approval" none: "There are no posts to review." edit: "Edit" cancel: "Cancel" confirm: "Save Changes" + delete_prompt: "Are you sure you want to delete %{username}? This will remove all of their posts and block their email and ip address." approval: title: "Post Needs Approval" diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml index 36960de79..4f58d3cd0 100644 --- a/config/locales/server.en.yml +++ b/config/locales/server.en.yml @@ -177,6 +177,9 @@ en: excerpt_image: "image" + queue: + delete_reason: "Deleted via post moderation queue" + groups: errors: can_not_modify_automatic: "You can not modify an automatic group" diff --git a/test/javascripts/acceptance/queued-posts-test.js.es6 b/test/javascripts/acceptance/queued-posts-test.js.es6 index de358c92a..c1a755503 100644 --- a/test/javascripts/acceptance/queued-posts-test.js.es6 +++ b/test/javascripts/acceptance/queued-posts-test.js.es6 @@ -20,6 +20,28 @@ test("reject a post", () => { }); }); +test("delete user", () => { + visit("/queued-posts"); + + click('.queued-post:eq(0) button.delete-user'); + andThen(() => { + ok(exists('.bootbox.modal'), 'it pops up a confirmation dialog'); + }); + + click('.modal-footer a:eq(1)'); + andThen(() => { + ok(!exists('.bootbox.modal'), 'it dismisses the modal'); + ok(exists('.queued-post'), "it doesn't remove the post"); + }); + + click('.queued-post:eq(0) button.delete-user'); + click('.modal-footer a:eq(0)'); + andThen(() => { + ok(!exists('.bootbox.modal'), 'it dismisses the modal'); + ok(!exists('.queued-post'), "it removes the post"); + }); +}); + test("edit a post - cancel", () => { visit("/queued-posts");