diff --git a/public/src/forum/admin/users.js b/public/src/forum/admin/users.js
index d3ade3d363..9c2af40ce5 100644
--- a/public/src/forum/admin/users.js
+++ b/public/src/forum/admin/users.js
@@ -25,15 +25,9 @@ define(function() {
elements.each(function(index, element) {
var banBtn = $(element);
var uid = getUID(banBtn);
- if (isUserAdmin(banBtn) || uid === yourid) {
- banBtn.addClass('disabled');
- } else if (isUserBanned(banBtn)) {
- banBtn.addClass('btn-warning');
- } else if (!isUserAdmin(banBtn)) {
- banBtn.removeClass('disabled');
- } else {
- banBtn.removeClass('btn-warning');
- }
+
+ banBtn.toggleClass('disabled', isUserAdmin(banBtn) || uid === yourid);
+ banBtn.toggleClass('btn-warning', isUserBanned(banBtn));
});
}
@@ -41,18 +35,9 @@ define(function() {
elements.each(function(index, element) {
var adminBtn = $(element);
var uid = getUID(adminBtn);
- if (isUserAdmin(adminBtn)) {
- adminBtn.attr('value', 'UnMake Admin').html('Remove Admin');
- if (uid === yourid) {
- adminBtn.addClass('disabled');
- }
- } else if (isUserBanned(adminBtn)) {
- adminBtn.addClass('disabled');
- } else if (!isUserBanned(adminBtn)) {
- adminBtn.removeClass('disabled');
- } else {
- adminBtn.removeClass('btn-warning');
- }
+
+ adminBtn.toggleClass('disabled', (isUserAdmin(adminBtn) && uid === yourid) || isUserBanned(adminBtn));
+ adminBtn.toggleClass('btn-success', isUserAdmin(adminBtn));
});
}
@@ -101,23 +86,39 @@ define(function() {
});
} else if (!isUserAdmin(adminBtn)) {
socket.emit('admin.user.makeAdmin', uid);
- adminBtn.attr('value', 'UnMake Admin').html('Remove Admin');
parent.attr('data-admin', 1);
updateUserBanButtons($('.ban-btn'));
-
+ updateUserAdminButtons($('.admin-btn'));
} else if(uid !== yourid) {
bootbox.confirm('Do you really want to remove this user as admin "' + parent.attr('data-username') + '"?', function(confirm) {
if (confirm) {
socket.emit('admin.user.removeAdmin', uid);
- adminBtn.attr('value', 'Make Admin').html('Make Admin');
parent.attr('data-admin', 0);
updateUserBanButtons($('.ban-btn'));
+ updateUserAdminButtons($('.admin-btn'));
}
});
}
return false;
});
+ $('#users-container').on('click', '.delete-btn', function() {
+ var deleteBtn = $(this);
+ var parent = deleteBtn.parents('.users-box');
+ var uid = getUID(deleteBtn);
+ bootbox.confirm('Warning!
Do you really want to delete this user "' + parent.attr('data-username') + '"?
This action is not reversable, all user data and content will be erased!', function(confirm) {
+ if (confirm) {
+ socket.emit('admin.user.deleteUser', uid, function(err) {
+ if (err) {
+ return app.alertError(err.message);
+ }
+ parent.remove();
+ app.alertSuccess('User Deleted!');
+ });
+ }
+ });
+ });
+
function handleUserCreate() {
var errorEl = $('#create-modal-error');
$('#createUser').on('click', function() {
diff --git a/src/admin/user.js b/src/admin/user.js
index 36b8db4704..387988fa6b 100644
--- a/src/admin/user.js
+++ b/src/admin/user.js
@@ -1,4 +1,8 @@
-var utils = require('../../public/src/utils'),
+'use strict';
+
+
+var async = require('async'),
+ utils = require('../../public/src/utils'),
user = require('../user'),
groups = require('../groups');
@@ -6,23 +10,13 @@ var utils = require('../../public/src/utils'),
UserAdmin.createUser = function(uid, userData, callback) {
user.isAdministrator(uid, function(err, isAdmin) {
- if(err) {
- return callback(err);
+ if(err || !isAdmin) {
+ return callback(err || new Error('You are not an administrator'));
}
- if (isAdmin) {
- user.create(userData, function(err) {
- if(err) {
- return callback(err);
- }
-
- callback(null);
- });
- } else {
- callback(new Error('You are not an administrator'));
- }
+ user.create(userData, callback);
});
- }
+ };
UserAdmin.makeAdmin = function(uid, theirid, socket) {
user.isAdministrator(uid, function(err, isAdmin) {
@@ -105,4 +99,18 @@ var utils = require('../../public/src/utils'),
});
};
+ UserAdmin.deleteUser = function(uid, theirid, callback) {
+ async.waterfall([
+ function(next) {
+ user.isAdministrator(uid, next);
+ },
+ function(isAdmin, next) {
+ if(!isAdmin) {
+ return next(new Error('You are not an administrator'));
+ }
+ user.delete(uid, theirid, next);
+ }
+ ], callback);
+ };
+
}(exports));
\ No newline at end of file
diff --git a/src/categories/activeusers.js b/src/categories/activeusers.js
index 059432f3e5..7fd86651aa 100644
--- a/src/categories/activeusers.js
+++ b/src/categories/activeusers.js
@@ -49,8 +49,8 @@ module.exports = function(Categories) {
}
};
- Categories.removeActiveUser = function(cid, uid) {
- db.sortedSetRemove('cid:' + cid + ':active_users', uid);
+ Categories.removeActiveUser = function(cid, uid, callback) {
+ db.sortedSetRemove('cid:' + cid + ':active_users', uid, callback);
};
Categories.getActiveUsers = function(cid, callback) {
diff --git a/src/controllers/topics.js b/src/controllers/topics.js
index 8edf274e15..15452aaf80 100644
--- a/src/controllers/topics.js
+++ b/src/controllers/topics.js
@@ -145,9 +145,9 @@ topicsController.get = function(req, res, next) {
], function (err, data) {
if (err) {
if (err.message === 'not-enough-privileges') {
- return res.redirect('403');
+ return res.locals.isAPI ? res.json(403, err.message) : res.redirect('403');
} else {
- return res.redirect('404');
+ return res.locals.isAPI ? res.json(404, 'not-found') : res.redirect('404');
}
}
@@ -170,7 +170,7 @@ topicsController.get = function(req, res, next) {
// Paginator for noscript
data.pages = [];
- for(var x=1;x<=data.pageCount;x++) {
+ for(var x=1; x<=data.pageCount; x++) {
data.pages.push({
page: x,
active: x === parseInt(page, 10)
diff --git a/src/events.js b/src/events.js
index 57b61c8aeb..3890b707e7 100644
--- a/src/events.js
+++ b/src/events.js
@@ -1,6 +1,8 @@
+'use strict';
var fs = require('fs'),
+ winston = require('winston'),
path = require('path'),
nconf = require('nconf'),
user = require('./user');
@@ -11,54 +13,62 @@ var fs = require('fs'),
events.logPasswordChange = function(uid) {
logWithUser(uid, 'changed password');
- }
+ };
- events.logAdminChangeUserPassword = function(adminUid, theirUid) {
+ events.logAdminChangeUserPassword = function(adminUid, theirUid, callback) {
+ logAdminEvent(adminUid, theirUid, 'changed password of', callback);
+ };
+
+ events.logAdminUserDelete = function(adminUid, theirUid, callback) {
+ logAdminEvent(adminUid, theirUid, 'deleted', callback);
+ };
+
+ function logAdminEvent(adminUid, theirUid, message, callback) {
user.getMultipleUserFields([adminUid, theirUid], ['username'], function(err, userData) {
if(err) {
return winston.error('Error logging event. ' + err.message);
}
- var msg = userData[0].username + '(uid ' + adminUid + ') changed password of ' + userData[1].username + '(uid ' + theirUid + ')';
- events.log(msg);
+ var msg = userData[0].username + '(uid ' + adminUid + ') ' + message + ' ' + userData[1].username + '(uid ' + theirUid + ')';
+ events.log(msg, callback);
});
}
events.logPasswordReset = function(uid) {
logWithUser(uid, 'reset password');
- }
+ };
events.logEmailChange = function(uid, oldEmail, newEmail) {
logWithUser(uid,'changed email from "' + oldEmail + '" to "' + newEmail +'"');
- }
+ };
events.logUsernameChange = function(uid, oldUsername, newUsername) {
logWithUser(uid,'changed username from "' + oldUsername + '" to "' + newUsername +'"');
- }
+ };
events.logAdminLogin = function(uid) {
logWithUser(uid, 'logged into admin panel');
- }
+ };
events.logPostEdit = function(uid, pid) {
logWithUser(uid, 'edited post (pid ' + pid + ')');
- }
+ };
events.logPostDelete = function(uid, pid) {
logWithUser(uid, 'deleted post (pid ' + pid + ')');
- }
+ };
events.logPostRestore = function(uid, pid) {
logWithUser(uid, 'restored post (pid ' + pid + ')');
- }
+ };
events.logTopicDelete = function(uid, tid) {
logWithUser(uid, 'deleted topic (tid ' + tid + ')');
- }
+ };
events.logTopicRestore = function(uid, tid) {
logWithUser(uid, 'restored topic (tid ' + tid + ')');
- }
+ };
function logWithUser(uid, string) {
@@ -72,7 +82,7 @@ var fs = require('fs'),
});
}
- events.log = function(msg) {
+ events.log = function(msg, callback) {
var logFile = path.join(nconf.get('base_dir'), logFileName);
msg = '[' + new Date().toUTCString() + '] - ' + msg;
@@ -80,10 +90,17 @@ var fs = require('fs'),
fs.appendFile(logFile, msg + '\n', function(err) {
if(err) {
winston.error('Error logging event. ' + err.message);
+ if (typeof callback === 'function') {
+ callback(err);
+ }
return;
}
+
+ if (typeof callback === 'function') {
+ callback();
+ }
});
- }
+ };
events.getLog = function(callback) {
var logFile = path.join(nconf.get('base_dir'), logFileName);
@@ -95,6 +112,6 @@ var fs = require('fs'),
callback(null, 'No logs found');
}
});
- }
+ };
}(module.exports));
\ No newline at end of file
diff --git a/src/favourites.js b/src/favourites.js
index 8084647bdd..2c164d1f28 100644
--- a/src/favourites.js
+++ b/src/favourites.js
@@ -224,9 +224,11 @@ var async = require('async'),
});
}
- socket.emit('posts.unfavourite', {
- pid: pid
- });
+ if (socket) {
+ socket.emit('posts.unfavourite', {
+ pid: pid
+ });
+ }
}
});
});
diff --git a/src/groups.js b/src/groups.js
index db6ecdb81b..4b0ba0ea4a 100644
--- a/src/groups.js
+++ b/src/groups.js
@@ -7,6 +7,10 @@
user = require('./user'),
db = require('./database');
+ Groups.getGroupIds = function (callback) {
+ db.getObjectValues('group:gid', callback);
+ };
+
Groups.list = function(options, callback) {
db.getObjectValues('group:gid', function (err, gids) {
if (gids.length > 0) {
diff --git a/src/socket.io/admin.js b/src/socket.io/admin.js
index 316d77a347..14093734ec 100644
--- a/src/socket.io/admin.js
+++ b/src/socket.io/admin.js
@@ -112,6 +112,10 @@ SocketAdmin.user.unbanUser = function(socket, theirid) {
admin.user.unbanUser(socket.uid, theirid, socket);
};
+SocketAdmin.user.deleteUser = function(socket, theirid, callback) {
+ admin.user.deleteUser(socket.uid, theirid, callback);
+};
+
SocketAdmin.user.search = function(socket, username, callback) {
user.search(username, function(err, data) {
function isAdmin(userData, next) {
diff --git a/src/threadTools.js b/src/threadTools.js
index 05d5f2d09a..415e2db5bb 100644
--- a/src/threadTools.js
+++ b/src/threadTools.js
@@ -311,7 +311,7 @@ var winston = require('winston'),
return callback(err);
}
- if (pids.length === 0) {
+ if (!pids.length) {
return callback(null, null);
}
diff --git a/src/topics.js b/src/topics.js
index 32af5a6ebe..032e3ce440 100644
--- a/src/topics.js
+++ b/src/topics.js
@@ -695,7 +695,11 @@ var async = require('async'),
privilegeCache = {},
userCache = {};
+
function loadTopicInfo(topicData, next) {
+ if (!topicData) {
+ return next(null, null);
+ }
function isTopicVisible(topicData, topicInfo) {
var deleted = parseInt(topicData.deleted, 10) !== 0;
@@ -736,6 +740,10 @@ var async = require('async'),
categoryCache[topicData.cid] = topicInfo.categoryData;
userCache[topicData.uid] = topicInfo.user;
+ if (!topicInfo.teaser) {
+ return next(null, null);
+ }
+
if (!isTopicVisible(topicData, topicInfo)) {
return next(null, null);
}
diff --git a/src/user.js b/src/user.js
index 0be4ae124c..ba55215512 100644
--- a/src/user.js
+++ b/src/user.js
@@ -281,7 +281,7 @@ var bcrypt = require('bcryptjs'),
User.isAdministrator(user.uid, next);
},
function(isAdmin, next) {
- user.status = !user.status ? 'online' : '';
+ user.status = !user.status ? 'online' : user.status;
user.administrator = isAdmin ? '1':'0';
if (set === 'users:online') {
return callback(null, user);
diff --git a/src/user/admin.js b/src/user/admin.js
index ece5b56cf3..b9f3350135 100644
--- a/src/user/admin.js
+++ b/src/user/admin.js
@@ -2,7 +2,15 @@
'use strict';
var async = require('async'),
- db = require('./../database');
+ db = require('./../database'),
+ posts = require('./../posts'),
+ user = require('./../user'),
+ topics = require('./../topics'),
+ categories = require('./../categories'),
+ plugins = require('./../plugins'),
+ events = require('./../events'),
+ groups = require('./../groups');
+
module.exports = function(User) {
@@ -51,4 +59,320 @@ module.exports = function(User) {
User.unban = function(uid, callback) {
User.setUserField(uid, 'banned', 0, callback);
};
-};
\ No newline at end of file
+
+ User.delete = function(adminUid, uid, callback) {
+ async.waterfall([
+ function(next) {
+ deletePosts(uid, next);
+ },
+ function(next) {
+ deleteTopics(uid, next);
+ },
+ function(next) {
+ events.logAdminUserDelete(adminUid, uid, next);
+ }
+ ], function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ deleteAccount(uid, callback);
+ });
+ };
+
+ function deletePosts(uid, callback) {
+ db.getSortedSetRange('uid:' + uid + ':posts', 0, -1, function(err, pids) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.each(pids, deletePost, callback);
+ });
+ }
+
+ function deletePost(pid, callback) {
+ async.parallel([
+ function(next) {
+ deletePostFromTopic(pid, next);
+ },
+ function(next) {
+ deletePostFromCategoryRecentPosts(pid, next);
+ },
+ function(next) {
+ deletePostFromUsersFavourites(pid, next);
+ },
+ function(next) {
+ deletePostFromUsersVotes(pid, next);
+ },
+ function(next) {
+ db.sortedSetRemove('posts:pid', pid, next);
+ }
+ ], function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ plugins.fireHook('action:post.delete', pid);
+ db.delete('post:' + pid, callback);
+ });
+ }
+
+ function deletePostFromTopic(pid, callback) {
+ posts.getPostFields(pid, ['tid', 'deleted'], function(err, postData) {
+ if (err) {
+ return callback(err);
+ }
+
+ db.sortedSetRemove('tid:' + postData.tid + ':posts', pid, function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ if (parseInt(postData.deleted, 10) === 0) {
+ db.decrObjectField('global', 'postCount', callback);
+ } else {
+ callback();
+ }
+ });
+ });
+ }
+
+ function deletePostFromCategoryRecentPosts(pid, callback) {
+ db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.each(cids, function(cid, next) {
+ db.sortedSetRemove('categories:recent_posts:cid:' + cid, pid, next);
+ }, callback);
+ });
+ }
+
+ function deletePostFromUsersFavourites(pid, callback) {
+ db.getSetMembers('pid:' + pid + ':users_favourited', function(err, uids) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.each(uids, function(uid, next) {
+ db.sortedSetRemove('uid:' + uid + ':favourites', pid, next);
+ }, function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ db.delete('pid:' + pid + ':users_favourited', callback);
+ });
+ });
+ }
+
+ function deletePostFromUsersVotes(pid, callback) {
+ async.parallel({
+ upvoters: function(next) {
+ db.getSetMembers('pid:' + pid + ':upvote', next);
+ },
+ downvoters: function(next) {
+ db.getSetMembers('pid:' + pid + ':downvote', next);
+ }
+ }, function(err, results) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.parallel([
+ function(next) {
+ async.each(results.upvoters, function(uid, next) {
+ db.sortedSetRemove('uid:' + uid + ':upvote', pid, next);
+ }, next);
+ },
+ function(next) {
+ async.each(results.downvoters, function(uid, next) {
+ db.sortedSetRemove('uid:' + uid + ':downvote', pid, next);
+ }, next);
+ }
+ ], callback);
+ });
+ }
+
+ function deleteTopics(uid, callback) {
+ db.getSortedSetRange('uid:' + uid + ':topics', 0, -1, function(err, tids) {
+ if (err) {
+ return callback(err);
+ }
+ async.each(tids, deleteTopic, callback);
+ });
+ }
+
+ function deleteTopic(tid, callback) {
+
+ async.parallel([
+ function(next) {
+ db.delete('tid:' + tid + ':followers', next);
+ },
+ function(next) {
+ db.delete('tid:' + tid + ':read_by_uid', next);
+ },
+ function(next) {
+ db.sortedSetRemove('topics:tid', tid, next);
+ },
+ function(next) {
+ db.sortedSetRemove('topics:recent', tid, next);
+ },
+ function(next) {
+ db.sortedSetRemove('topics:posts', tid, next);
+ },
+ function(next) {
+ db.sortedSetRemove('topics:views', tid, next);
+ },
+ function(next) {
+ deleteTopicFromCategory(tid, next);
+ }
+ ], function(err) {
+ if (err) {
+ return callback(err);
+ }
+ plugins.fireHook('action:topic.delete', tid);
+ db.delete('topic:' + tid, callback);
+ });
+ }
+
+ function deleteTopicFromCategory(tid, callback) {
+ topics.getTopicFields(tid, ['cid', 'deleted'], function(err, topicData) {
+ if (err) {
+ return callback(err);
+ }
+
+ db.sortedSetRemove('categories:' + topicData.cid + ':tid', tid, function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ db.decrObjectField('category:' + topicData.cid, 'topic_count', function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ if (parseInt(topicData.deleted) === 0) {
+ db.decrObjectField('global', 'topicCount', callback);
+ } else {
+ callback();
+ }
+ });
+ });
+ });
+ }
+
+ function deleteAccount(uid, callback) {
+ user.getUserFields(uid, ['username', 'userslug', 'email'], function(err, userData) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.parallel([
+ function(next) {
+ db.deleteObjectField('username:uid', userData.username, next);
+ },
+ function(next) {
+ db.deleteObjectField('userslug:uid', userData.userslug, next);
+ },
+ function(next) {
+ db.deleteObjectField('email:uid', userData.email, next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':notifications:read', next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':notifications:unread', next);
+ },
+ function(next) {
+ db.sortedSetRemove('users:joindate', uid, next);
+ },
+ function(next) {
+ db.sortedSetRemove('users:postcount', uid, next);
+ },
+ function(next) {
+ db.sortedSetRemove('users:reputation', uid, next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':favourites', next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':topics', next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':posts', next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':chats', next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':ip', next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':upvote', next);
+ },
+ function(next) {
+ db.delete('uid:' + uid + ':downvote', next);
+ },
+ function(next) {
+ deleteUserFromCategoryActiveUsers(uid, next);
+ },
+ function(next) {
+ deleteUserFromFollowers(uid, next);
+ },
+ function(next) {
+ deleteUserFromGroups(uid, next);
+ }
+ ], function(err) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.parallel([
+ function(next) {
+ db.delete('followers:' + uid, next);
+ },
+ function(next) {
+ db.delete('following:' + uid, next);
+ },
+ function(next) {
+ db.delete('user:' + uid, next);
+ }
+ ], callback);
+ });
+ });
+ }
+
+ function deleteUserFromCategoryActiveUsers(uid, callback) {
+ db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.each(cids, function(cid, next) {
+ categories.removeActiveUser(cid, uid, next);
+ }, callback);
+ });
+ }
+
+ function deleteUserFromFollowers(uid, callback) {
+ db.getSetMembers('followers:' + uid, function(err, uids) {
+ if (err) {
+ return callback(err);
+ }
+
+ async.each(uids, function(theiruid, next) {
+ db.setRemove('following:' + theiruid, uid, next);
+ }, callback);
+ });
+ }
+
+ function deleteUserFromGroups(uid, callback) {
+ groups.getGroupIds(function(err, gids) {
+ async.each(gids, function(gid, next) {
+ groups.leave(gid, uid, next);
+ }, callback);
+ });
+ }
+};