From d2bef79888f9ff2fdbe79c9da444a5e32ca0f639 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 13 Mar 2014 18:11:37 -0400 Subject: [PATCH 1/9] closes #1194 --- public/src/forum/admin/categories.js | 8 +++++--- src/categories.js | 2 +- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/public/src/forum/admin/categories.js b/public/src/forum/admin/categories.js index 4985ad01c8..015e4a12ca 100644 --- a/public/src/forum/admin/categories.js +++ b/public/src/forum/admin/categories.js @@ -87,12 +87,12 @@ define(['uploader'], function(uploader) { }, distance: 10 }); - $('.blockclass').each(function() { + + $('.blockclass, .admin-categories form select').each(function() { var $this = $(this); $this.val($this.attr('data-value')); }); - function showCreateCategoryModal() { $('#new-category-modal').modal(); } @@ -179,10 +179,12 @@ define(['uploader'], function(uploader) { select_icon($(this).find('i')); }); - $('.admin-categories form input').on('change', function(ev) { + $('.admin-categories form input, .admin-categories form select').on('change', function(ev) { modified(ev.target); }); + + $('.dropdown').on('click', '[data-disabled]', function(ev) { var btn = $(this), categoryRow = btn.parents('li'), diff --git a/src/categories.js b/src/categories.js index ba875bf423..e75ea055af 100644 --- a/src/categories.js +++ b/src/categories.js @@ -43,7 +43,7 @@ var db = require('./database'), link: '', numRecentReplies: 2, class: 'col-md-3 col-xs-6', - imageClass: 'default' + imageClass: 'auto' }; db.setObject('category:' + cid, category, function(err) { From 6011399063f08c3d2294b28382d8c4843b5ba39f Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 13 Mar 2014 18:50:57 -0400 Subject: [PATCH 2/9] removed comment --- src/user.js | 1 - 1 file changed, 1 deletion(-) diff --git a/src/user.js b/src/user.js index fcabfbb0de..0be4ae124c 100644 --- a/src/user.js +++ b/src/user.js @@ -212,7 +212,6 @@ var bcrypt = require('bcryptjs'), } if (user.picture === user.uploadedpicture) { - // Append relative url user.picture = nconf.get('relative_path') + user.picture; } } From a0e784a612edad2112d9716ef8f2158b2d705b7b Mon Sep 17 00:00:00 2001 From: barisusakli Date: Thu, 13 Mar 2014 20:24:04 -0400 Subject: [PATCH 3/9] closes #1138 --- public/src/forum/home.js | 74 +++++++++++++++++++++++++++++++++++----- src/posts.js | 11 ++---- src/socket.io/posts.js | 3 ++ 3 files changed, 72 insertions(+), 16 deletions(-) diff --git a/public/src/forum/home.js b/public/src/forum/home.js index efaaba4033..5a70082de1 100644 --- a/public/src/forum/home.js +++ b/public/src/forum/home.js @@ -1,31 +1,89 @@ 'use strict'; +/* globals define, socket, app, templates, translator*/ + define(function() { var home = {}; - $(window).on('action:ajaxify.end', function(ev, data) { - if (data.url === '') { - socket.removeListener('event:new_topic', home.onNewTopic); + $(window).on('action:ajaxify.start', function(ev, data) { + if (data.url !== '') { socket.removeListener('event:new_post', home.onNewPost); } }); home.init = function() { - app.enterRoom('home'); - socket.on('event:new_topic', home.onNewTopic); socket.on('event:new_post', home.onNewPost); }; - home.onNewTopic = function(data) { + home.onNewPost = function(data) { - }; + if (data && data.posts && data.posts.length) { - home.onNewPost = function(data) { + socket.emit('posts.getCategory', data.posts[0].pid, function(err, cid) { + if (err) { + return; + } + renderNewPost(cid, data.posts[0]); + }); + } }; + function renderNewPost(cid, post) { + var category = $('.home .category-item[data-cid="' + cid + '"]'); + var categoryBox = category.find('.category-box'); + var numRecentReplies = category.attr('data-numRecentReplies'); + if (!numRecentReplies) { + return; + } + + var recentPosts = categoryBox.find('.post-preview'); + var insertBefore = recentPosts.first(); + + parseAndTranslate([post], function(html) { + html.hide(); + if(recentPosts.length === 0) { + html.appendTo(categoryBox); + } else { + html.insertBefore(recentPosts.first()); + } + + html.fadeIn(); + + app.createUserTooltips(); + + if (categoryBox.find('.post-preview').length > parseInt(numRecentReplies, 10)) { + recentPosts.last().remove(); + } + }); + } + + function parseAndTranslate(posts, callback) { + templates.preload_template('home', function() { + + templates.home.parse({ + categories: { + posts: [] + } + }); + + var html = templates.prepare(templates.home.blocks['categories.posts']).parse({ + categories: { + posts: posts + } + }); + + translator.translate(html, function(translatedHTML) { + translatedHTML = $(translatedHTML); + translatedHTML.find('img').addClass('img-responsive'); + translatedHTML.find('span.timeago').timeago(); + callback(translatedHTML); + }); + }); + } + return home; }); diff --git a/src/posts.js b/src/posts.js index be10f66417..dff6af855d 100644 --- a/src/posts.js +++ b/src/posts.js @@ -407,15 +407,10 @@ var db = require('./database'), } topics.getTopicField(tid, 'cid', function(err, cid) { - if(err) { - return callback(err); - } - - if (cid) { - callback(null, cid); - } else { - callback(new Error('invalid-category-id')); + if(err || !cid) { + return callback(err || new Error('invalid-category-id')); } + callback(null, cid); }); }); }; diff --git a/src/socket.io/posts.js b/src/socket.io/posts.js index fedb968522..6723d16684 100644 --- a/src/socket.io/posts.js +++ b/src/socket.io/posts.js @@ -304,5 +304,8 @@ SocketPosts.getRecentPosts = function(socket, data, callback) { posts.getRecentPosts(socket.uid, 0, data.count - 1, data.term, callback); }; +SocketPosts.getCategory = function(socket, pid, callback) { + posts.getCidByPid(pid, callback); +} module.exports = SocketPosts; \ No newline at end of file From 1354739d199586844b0465ada61e513bf4a2d22e Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 14 Mar 2014 19:07:50 -0400 Subject: [PATCH 4/9] user deletion #746 user deletion NOT SKALABLE --- public/src/forum/admin/users.js | 49 ++--- src/admin/user.js | 38 ++-- src/categories/activeusers.js | 4 +- src/controllers/topics.js | 6 +- src/events.js | 49 +++-- src/favourites.js | 8 +- src/groups.js | 4 + src/socket.io/admin.js | 4 + src/threadTools.js | 2 +- src/topics.js | 8 + src/user.js | 2 +- src/user/admin.js | 328 +++++++++++++++++++++++++++++++- 12 files changed, 435 insertions(+), 67 deletions(-) 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, callback) { + logAdminEvent(adminUid, theirUid, 'changed password of', callback); + }; + + events.logAdminUserDelete = function(adminUid, theirUid, callback) { + logAdminEvent(adminUid, theirUid, 'deleted', callback); + }; - events.logAdminChangeUserPassword = function(adminUid, theirUid) { + 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); + }); + } +}; From 42f42adfafd0eb1dba797c0f48abdd86ace30470 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 14 Mar 2014 19:22:56 -0400 Subject: [PATCH 5/9] clean up, radix --- src/user/admin.js | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/user/admin.js b/src/user/admin.js index b9f3350135..3289cb1f90 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -81,13 +81,7 @@ module.exports = function(User) { }; function deletePosts(uid, callback) { - db.getSortedSetRange('uid:' + uid + ':posts', 0, -1, function(err, pids) { - if (err) { - return callback(err); - } - - async.each(pids, deletePost, callback); - }); + deleteSortedSetElements('uid:' + uid + ':posts', deletePost, callback); } function deletePost(pid, callback) { @@ -196,11 +190,16 @@ module.exports = function(User) { } function deleteTopics(uid, callback) { - db.getSortedSetRange('uid:' + uid + ':topics', 0, -1, function(err, tids) { + deleteSortedSetElements('uid:' + uid + ':topics', deleteTopic, callback); + } + + function deleteSortedSetElements(set, deleteMethod, callback) { + db.getSortedSetRange(set, 0, -1, function(err, ids) { if (err) { return callback(err); } - async.each(tids, deleteTopic, callback); + + async.each(ids, deleteMethod, callback); }); } @@ -253,7 +252,7 @@ module.exports = function(User) { return callback(err); } - if (parseInt(topicData.deleted) === 0) { + if (parseInt(topicData.deleted, 10) === 0) { db.decrObjectField('global', 'topicCount', callback); } else { callback(); From 3d6cce44c7d6837effdcc1fbda332ee7d504e941 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 14 Mar 2014 19:34:05 -0400 Subject: [PATCH 6/9] dont display chats with deleted users --- src/messaging.js | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/messaging.js b/src/messaging.js index e72ec95d2d..8de22b724e 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -124,7 +124,17 @@ var db = require('./database'), return callback(err); } - user.getMultipleUserFields(uids, ['username', 'picture', 'uid'], callback); + user.getMultipleUserFields(uids, ['username', 'picture', 'uid'], function(err, users) { + if (err) { + return callback(err) + } + + users = users.filter(function(user) { + return !!user.uid; + }) + + callback(null, users); + }); }); }; From 7873b90caf9b1acc4c46414fd833e5442b0a8741 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Fri, 14 Mar 2014 20:06:46 -0400 Subject: [PATCH 7/9] missing ; --- src/messaging.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/messaging.js b/src/messaging.js index 8de22b724e..443b557096 100644 --- a/src/messaging.js +++ b/src/messaging.js @@ -10,9 +10,7 @@ var db = require('./database'), (function(Messaging) { function sortUids(fromuid, touid) { - var uids = [fromuid, touid]; - uids.sort(); - return uids; + return [fromuid, touid].sort(); } Messaging.addMessage = function(fromuid, touid, content, callback) { @@ -126,12 +124,12 @@ var db = require('./database'), user.getMultipleUserFields(uids, ['username', 'picture', 'uid'], function(err, users) { if (err) { - return callback(err) + return callback(err); } users = users.filter(function(user) { return !!user.uid; - }) + }); callback(null, users); }); From d233e7927d532f608c020b4b5f19b314a3950dd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Fri, 14 Mar 2014 20:56:06 -0400 Subject: [PATCH 8/9] added tablet screenshot --- README.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b45429cdf8..bbc0b4b6dd 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,8 @@ ## Screenshots -[](http://i.imgur.com/FLOUuIq.png) [](http://i.imgur.com/Ud1LrfI.png) [](http://i.imgur.com/ZC8W39a.png) [](http://i.imgur.com/o90kVPi.png) [](http://i.imgur.com/AaRRrU2.png) [](http://i.imgur.com/LmHtPho.png) [](http://i.imgur.com/paiJPJk.jpg) [](http://i.imgur.com/ZfavPHD.png) +[](http://i.imgur.com/FLOUuIq.png) [](http://i.imgur.com/Ud1LrfI.png) [](http://i.imgur.com/ZC8W39a.png) [](http://i.imgur.com/o90kVPi.png) [](http://i.imgur.com/AaRRrU2.png) [](http://i.imgur.com/LmHtPho.png) [](http://i.imgur.com/paiJPJk.jpg) [](http://i.imgur.com/ZfavPHD.png) [](http://i.imgur.com/8vc1Ytc.png) + Credit: [Convoe](http://www.convoe.com), [Kano](http://www.kano.me), [Manchester United Forum](http://manutdforums.com/). From 611c16b5a62d3fb32c3c2c1764c22693dc504d67 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Sat, 15 Mar 2014 00:26:09 -0400 Subject: [PATCH 9/9] closes #1126 --- src/controllers/categories.js | 10 ++++++++++ src/controllers/topics.js | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/src/controllers/categories.js b/src/controllers/categories.js index 9d0a1aae49..4dd9d0e8a5 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -65,6 +65,16 @@ categoriesController.get = function(req, res, next) { page = req.query.page || 1, uid = req.user ? req.user.uid : 0; + if (!req.params.slug && !res.locals.isAPI) { + categories.getCategoryField(cid, 'slug', function(err, slug) { + if (err) { + return next(err); + } + res.redirect('/category/' + slug); + }); + return; + } + async.waterfall([ function(next) { categoryTools.privileges(cid, uid, function(err, categoryPrivileges) { diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 15452aaf80..50d3fefa37 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -18,6 +18,16 @@ topicsController.get = function(req, res, next) { uid = req.user ? req.user.uid : 0, privileges; + if (!req.params.slug && !res.locals.isAPI) { + topics.getTopicField(tid, 'slug', function(err, slug) { + if (err) { + return next(err); + } + res.redirect('/topic/' + slug); + }); + return; + } + async.waterfall([ function(next) { threadTools.privileges(tid, ((req.user) ? req.user.uid || 0 : 0), function(err, userPrivileges) {