diff --git a/src/socket.io/admin/user.js b/src/socket.io/admin/user.js index d8d9a0f282..6b2853447b 100644 --- a/src/socket.io/admin/user.js +++ b/src/socket.io/admin/user.js @@ -2,6 +2,7 @@ var async = require('async'); var validator = require('validator'); +var winston = require('winston'); var db = require('../../database'); var groups = require('../../groups'); @@ -146,37 +147,46 @@ function deleteUsers(socket, uids, method, callback) { if (!Array.isArray(uids)) { return callback(new Error('[[error:invalid-data]]')); } + async.waterfall([ + function (next) { + groups.isMembers(uids, 'administrators', next); + }, + function (isMembers, next) { + if (isMembers.includes(true)) { + return callback(new Error('[[error:cant-delete-other-admins]]')); + } - async.each(uids, function (uid, next) { - async.waterfall([ - function (next) { - user.isAdministrator(uid, next); - }, - function (isAdmin, next) { - if (isAdmin) { - return next(new Error('[[error:cant-delete-other-admins]]')); - } + callback(); - method(uid, next); - }, - function (next) { - events.log({ - type: 'user-delete', - uid: socket.uid, - targetUid: uid, - ip: socket.ip, - }, next); - }, - function (next) { - plugins.fireHook('action:user.delete', { - callerUid: socket.uid, - uid: uid, - ip: socket.ip, - }); - next(); - }, - ], next); - }, callback); + async.each(uids, function (uid, next) { + async.waterfall([ + function (next) { + method(uid, next); + }, + function (next) { + events.log({ + type: 'user-delete', + uid: socket.uid, + targetUid: uid, + ip: socket.ip, + }, next); + }, + function (next) { + plugins.fireHook('action:user.delete', { + callerUid: socket.uid, + uid: uid, + ip: socket.ip, + }); + next(); + }, + ], next); + }, next); + }, + ], function (err) { + if (err) { + winston.error(err); + } + }); } User.search = function (socket, data, callback) { diff --git a/src/socket.io/topics.js b/src/socket.io/topics.js index 4468b4fb53..bbeda1bee5 100644 --- a/src/socket.io/topics.js +++ b/src/socket.io/topics.js @@ -2,7 +2,6 @@ var async = require('async'); -var db = require('../database'); var topics = require('../topics'); var posts = require('../posts'); var websockets = require('./index'); @@ -73,22 +72,7 @@ SocketTopics.postcount = function (socket, tid, callback) { return next(new Error('[[no-privileges]]')); } - async.parallel({ - replyCount: function (next) { - db.sortedSetCard('tid:' + tid + ':posts', next); - }, - topicData: function (next) { - topics.getTopicFields(tid, ['mainPid', 'postcount'], next); - }, - }, next); - }, - function (results, next) { - if (results.topicData.mainPid && parseInt(results.topicData.postcount, 10) === parseInt(results.replyCount, 10) + 1) { - return next(null, results.topicData.postcount); - } - var postcount = results.replyCount + (results.topicData.mainPid ? 1 : 0); - topics.setTopicField(tid, 'postcount', postcount); - next(null, postcount); + topics.getTopicField(tid, 'postcount', next); }, ], callback); }; diff --git a/src/user/delete.js b/src/user/delete.js index 3085a5c404..78320d698e 100644 --- a/src/user/delete.js +++ b/src/user/delete.js @@ -15,12 +15,20 @@ var batch = require('../batch'); var file = require('../file'); module.exports = function (User) { + var deletesInProgress = {}; + User.delete = function (callerUid, uid, callback) { if (!parseInt(uid, 10)) { - return callback(new Error('[[error:invalid-uid]]')); + return setImmediate(callback, new Error('[[error:invalid-uid]]')); } - + if (deletesInProgress[uid]) { + return setImmediate(callback); + } + deletesInProgress[uid] = 'user.delete'; async.waterfall([ + function (next) { + removeFromSortedSets(uid, next); + }, function (next) { deletePosts(callerUid, uid, next); }, @@ -67,14 +75,38 @@ module.exports = function (User) { }, { alwaysStartAt: 0 }, callback); } + function removeFromSortedSets(uid, callback) { + db.sortedSetsRemove([ + 'users:joindate', + 'users:postcount', + 'users:reputation', + 'users:banned', + 'users:banned:expire', + 'users:flags', + 'users:online', + 'users:notvalidated', + 'digest:day:uids', + 'digest:week:uids', + 'digest:month:uids', + ], uid, callback); + } + User.deleteAccount = function (uid, callback) { + if (deletesInProgress[uid] === 'user.deleteAccount') { + return setImmediate(callback); + } + deletesInProgress[uid] = 'user.deleteAccount'; var userData; async.waterfall([ + function (next) { + removeFromSortedSets(uid, next); + }, function (next) { db.getObject('user:' + uid, next); }, function (_userData, next) { if (!_userData || !_userData.username) { + delete deletesInProgress[uid]; return callback(); } userData = _userData; @@ -113,20 +145,6 @@ module.exports = function (User) { next(); } }, - function (next) { - db.sortedSetsRemove([ - 'users:joindate', - 'users:postcount', - 'users:reputation', - 'users:banned', - 'users:banned:expire', - 'users:online', - 'users:notvalidated', - 'digest:day:uids', - 'digest:week:uids', - 'digest:month:uids', - ], uid, next); - }, function (next) { db.decrObjectField('global', 'userCount', next); }, @@ -164,7 +182,10 @@ module.exports = function (User) { function (results, next) { db.deleteAll(['followers:' + uid, 'following:' + uid, 'user:' + uid], next); }, - ], callback); + ], function (err) { + delete deletesInProgress[uid]; + callback(err); + }); }; function deleteVotes(uid, callback) { diff --git a/test/socket.io.js b/test/socket.io.js index 40f4d813b4..d56e01a087 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -198,11 +198,13 @@ describe('socket.io', function () { it('should delete users', function (done) { socketAdmin.user.deleteUsers({ uid: adminUid }, [uid], function (err) { assert.ifError(err); - groups.isMember(uid, 'registered-users', function (err, isMember) { - assert.ifError(err); - assert(!isMember); - done(); - }); + setTimeout(function () { + groups.isMember(uid, 'registered-users', function (err, isMember) { + assert.ifError(err); + assert(!isMember); + done(); + }); + }, 500); }); });