From 67aca822e6d3b9040f698b48580e8de9bc897f9c Mon Sep 17 00:00:00 2001 From: Julian Lam <julian@nodebb.org> Date: Mon, 8 Jun 2020 08:43:25 -0400 Subject: [PATCH] feat: account content deletion, closes #8381 --- public/language/en-GB/admin/manage/users.json | 11 +++++--- public/language/en-GB/user.json | 3 +++ public/src/admin/manage/users.js | 19 ++++++++++++++ public/src/client/account/header.js | 26 +++++++++++++++++++ public/src/client/flags/detail.js | 4 +++ src/socket.io/admin/user.js | 14 ++++++++++ src/user/delete.js | 11 +++++--- src/views/admin/manage/users.tpl | 1 + 8 files changed, 81 insertions(+), 8 deletions(-) diff --git a/public/language/en-GB/admin/manage/users.json b/public/language/en-GB/admin/manage/users.json index ae225d5b6f..93add0d7a4 100644 --- a/public/language/en-GB/admin/manage/users.json +++ b/public/language/en-GB/admin/manage/users.json @@ -12,8 +12,9 @@ "unban": "Unban User(s)", "reset-lockout": "Reset Lockout", "reset-flags": "Reset Flags", - "delete": "Delete User(s)", - "purge": "Delete User(s) and Content", + "delete": "Delete <strong>User(s)</strong>", + "delete-content": "Delete User(s) <strong>Content</strong>", + "purge": "Delete <strong>User(s)</strong> and <strong>Content</strong>", "download-csv": "Download CSV", "manage-groups": "Manage Groups", "add-group": "Add Group", @@ -93,9 +94,11 @@ "alerts.validate-email-success": "Emails validated", "alerts.validate-force-password-reset-success": "User(s) passwords have been reset and their existing sessions have been revoked.", "alerts.password-reset-confirm": "Do you want to send password reset email(s) to these user(s)?", - "alerts.confirm-delete": "<b>Warning!</b><br/>Do you really want to delete user(s)?<br/> This action is not reversable! Only the user account will be deleted, their posts and topics will remain.", + "alerts.confirm-delete": "<strong>Warning!</strong><p>Do you really want to delete <strong>user(s)</strong>?</p><p>This action is not reversible! Only the user account will be deleted, their posts and topics will remain.</p>", "alerts.delete-success": "User(s) Deleted!", - "alerts.confirm-purge": "<b>Warning!</b><br/>Do you really want to delete user(s) and their content?<br/> This action is not reversable! All user data and content will be erased!", + "alerts.confirm-delete-content": "<strong>Warning!</strong><p>Do you really want to delete these user(s) <strong>content</strong>?</p><p>This action is not reversible! The users' accounts will remain, but their posts and topics will be deleted.</p>", + "alerts.delete-content-success": "User(s) Content Deleted!", + "alerts.confirm-purge": "<strong>Warning!</strong><p>Do you really want to delete <strong>user(s) and their content</strong>?</p><p>This action is not reversible! All user data and content will be erased!</p>", "alerts.create": "Create User", "alerts.button-create": "Create", "alerts.button-cancel": "Cancel", diff --git a/public/language/en-GB/user.json b/public/language/en-GB/user.json index 54d0fcf272..aa43c69e4c 100644 --- a/public/language/en-GB/user.json +++ b/public/language/en-GB/user.json @@ -13,9 +13,12 @@ "ban_account_confirm": "Do you really want to ban this user?", "unban_account": "Unban Account", "delete_account": "Delete Account", + "delete_content": "Delete Account Content Only", "delete_account_confirm": "Are you sure you want to delete your account? <br /><strong>This action is irreversible and you will not be able to recover any of your data</strong><br /><br />Enter your password to confirm that you wish to destroy this account.", "delete_this_account_confirm": "Are you sure you want to delete this account? <br /><strong>This action is irreversible and you will not be able to recover any data</strong><br /><br />", + "delete_account_content_confirm": "Are you sure you want to delete this account's content (posts/topics/uploads)? <br /><strong>This action is irreversible and you will not be able to recover any data</strong><br /><br />", "account-deleted": "Account deleted", + "account-content-deleted": "Account content deleted", "fullname": "Full Name", "website": "Website", diff --git a/public/src/admin/manage/users.js b/public/src/admin/manage/users.js index ca4e63dc41..033613ee88 100644 --- a/public/src/admin/manage/users.js +++ b/public/src/admin/manage/users.js @@ -262,6 +262,25 @@ define('admin/manage/users', ['translator', 'benchpress', 'autocomplete'], funct }); }); + $('.delete-user-content').on('click', function () { + var uids = getSelectedUids(); + if (!uids.length) { + return; + } + + bootbox.confirm('[[admin/manage/users:alerts.confirm-delete-content]]', function (confirm) { + if (confirm) { + socket.emit('admin.user.deleteUsersContent', uids, function (err) { + if (err) { + return app.alertError(err.message); + } + + app.alertSuccess('[[admin/manage/users:alerts.delete-content-success]]'); + }); + } + }); + }); + $('.delete-user-and-content').on('click', function () { var uids = getSelectedUids(); if (!uids.length) { diff --git a/public/src/client/account/header.js b/public/src/client/account/header.js index 7ac50ef653..a8620ed98e 100644 --- a/public/src/client/account/header.js +++ b/public/src/client/account/header.js @@ -59,6 +59,7 @@ define('forum/account/header', [ // TODO: These exported methods are used in forum/flags/detail -- refactor?? AccountHeader.banAccount = banAccount; AccountHeader.deleteAccount = deleteAccount; + AccountHeader.deleteContent = deleteContent; function hidePrivateLinks() { if (!app.user.uid || app.user.uid !== parseInt(ajaxify.data.theirid, 10)) { @@ -201,6 +202,31 @@ define('forum/account/header', [ }); } + function deleteContent(theirid, onSuccess) { + theirid = theirid || ajaxify.data.theirid; + + translator.translate('[[user:delete_account_content_confirm]]', function (translated) { + bootbox.confirm(translated, function (confirm) { + if (!confirm) { + return; + } + + socket.emit('admin.user.deleteUsersContent', [theirid], function (err) { + if (err) { + return app.alertError(err.message); + } + app.alertSuccess('[[user:account-content-deleted]]'); + + if (typeof onSuccess === 'function') { + return onSuccess(); + } + + history.back(); + }); + }); + }); + } + function flagAccount() { require(['flags'], function (flags) { flags.showFlagModal({ diff --git a/public/src/client/flags/detail.js b/public/src/client/flags/detail.js index 353a09b66b..1f8ecbb1e2 100644 --- a/public/src/client/flags/detail.js +++ b/public/src/client/flags/detail.js @@ -52,6 +52,10 @@ define('forum/flags/detail', ['forum/flags/list', 'components', 'translator', 'b AccountHeader.deleteAccount(uid, ajaxify.refresh); break; + case 'delete-content': + AccountHeader.deleteContent(uid, ajaxify.refresh); + break; + case 'delete-post': postAction('delete', ajaxify.data.target.pid, ajaxify.data.target.tid); break; diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index f0192cf6f9..05a7e8cc27 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -126,6 +126,20 @@ User.deleteUsers = async function (socket, uids) { }); }; +User.deleteUsersContent = async function (socket, uids) { + if (!Array.isArray(uids)) { + throw new Error('[[error:invalid-data]]'); + } + const isMembers = await groups.isMembers(uids, 'administrators'); + if (isMembers.includes(true)) { + throw new Error('[[error:cant-delete-other-admins]]'); + } + + await Promise.all(uids.map(async (uid) => { + await user.deleteContent(socket.uid, uid); + })); +}; + User.deleteUsersAndContent = async function (socket, uids) { deleteUsers(socket, uids, async function (uid) { await user.delete(socket.uid, uid); diff --git a/src/user/delete.js b/src/user/delete.js index 1560005aeb..57ed4adeb7 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -17,7 +17,13 @@ const file = require('../file'); module.exports = function (User) { const deletesInProgress = {}; - User.delete = async function (callerUid, uid) { + User.delete = async (callerUid, uid) => { + await User.deleteContent(callerUid, uid); + await removeFromSortedSets(uid); + return await User.deleteAccount(uid); + }; + + User.deleteContent = async function (callerUid, uid) { if (parseInt(uid, 10) <= 0) { throw new Error('[[error:invalid-uid]]'); } @@ -25,13 +31,10 @@ module.exports = function (User) { throw new Error('[[error:already-deleting]]'); } deletesInProgress[uid] = 'user.delete'; - await removeFromSortedSets(uid); await deletePosts(callerUid, uid); await deleteTopics(callerUid, uid); await deleteUploads(uid); await deleteQueued(uid); - const userData = await User.deleteAccount(uid); - return userData; }; async function deletePosts(callerUid, uid) { diff --git a/src/views/admin/manage/users.tpl b/src/views/admin/manage/users.tpl index fb9061a700..07ab3a399c 100644 --- a/src/views/admin/manage/users.tpl +++ b/src/views/admin/manage/users.tpl @@ -22,6 +22,7 @@ <li><a href="#" class="reset-lockout"><i class="fa fa-fw fa-unlock"></i> [[admin/manage/users:reset-lockout]]</a></li> <li class="divider"></li> <li><a href="#" class="delete-user"><i class="fa fa-fw fa-trash-o"></i> [[admin/manage/users:delete]]</a></li> + <li><a href="#" class="delete-user-content"><i class="fa fa-fw fa-trash-o"></i> [[admin/manage/users:delete-content]]</a></li> <li><a href="#" class="delete-user-and-content"><i class="fa fa-fw fa-trash-o"></i> [[admin/manage/users:purge]]</a></li> </ul> </div>