From 062bced3dd2a926037e9b37e0c363073505e0090 Mon Sep 17 00:00:00 2001 From: Baris Usakli Date: Thu, 25 May 2017 16:40:03 -0400 Subject: [PATCH] cleanup and tests --- src/controllers/topics.js | 17 +-- src/middleware/index.js | 17 +-- src/middleware/maintenance.js | 46 ++++--- src/posts/bookmarks.js | 97 ++++++------- src/posts/create.js | 8 +- src/posts/delete.js | 136 ++++++++++--------- src/posts/edit.js | 128 +++++++++--------- src/posts/summary.js | 75 ++++++----- src/posts/user.js | 116 ++++++++-------- src/posts/votes.js | 242 +++++++++++++++++---------------- src/widgets/index.js | 247 ++++++++++++++++++---------------- test/mocks/databasemock.js | 3 + 12 files changed, 580 insertions(+), 552 deletions(-) diff --git a/src/controllers/topics.js b/src/controllers/topics.js index 5c592f5bb3..4b7fce05e8 100644 --- a/src/controllers/topics.js +++ b/src/controllers/topics.js @@ -313,16 +313,13 @@ topicsController.teaser = function (req, res, next) { } posts.getPostSummaryByPids([pid], req.uid, { stripTags: false }, next); }, - ], function (err, posts) { - if (err) { - return next(err); - } - - if (!Array.isArray(posts) || !posts.length) { - return res.status(404).json('not-found'); - } - res.json(posts[0]); - }); + function (posts) { + if (!Array.isArray(posts) || !posts.length) { + return res.status(404).json('not-found'); + } + res.json(posts[0]); + }, + ], next); }; topicsController.pagination = function (req, res, callback) { diff --git a/src/middleware/index.js b/src/middleware/index.js index 99fc62b14b..f196ad7e54 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -108,14 +108,15 @@ function expose(exposedField, method, field, req, res, next) { if (!req.params.hasOwnProperty(field)) { return next(); } - method(req.params[field], function (err, id) { - if (err) { - return next(err); - } - - res.locals[exposedField] = id; - next(); - }); + async.waterfall([ + function (next) { + method(req.params[field], next); + }, + function (id, next) { + res.locals[exposedField] = id; + next(); + }, + ], next); } middleware.privateUploads = function (req, res, next) { diff --git a/src/middleware/maintenance.js b/src/middleware/maintenance.js index 4f4cb03982..ce5053a616 100644 --- a/src/middleware/maintenance.js +++ b/src/middleware/maintenance.js @@ -1,38 +1,44 @@ 'use strict'; +var async = require('async'); var nconf = require('nconf'); var meta = require('../meta'); var user = require('../user'); module.exports = function (middleware) { - middleware.maintenanceMode = function (req, res, next) { + middleware.maintenanceMode = function (req, res, callback) { if (parseInt(meta.config.maintenanceMode, 10) !== 1) { - return next(); + return callback(); } var url = req.url.replace(nconf.get('relative_path'), ''); if (url.startsWith('/login') || url.startsWith('/api/login')) { - return next(); + return callback(); } + var data; + async.waterfall([ + function (next) { + user.isAdministrator(req.uid, next); + }, + function (isAdmin, next) { + if (isAdmin) { + return callback(); + } + res.status(503); + data = { + site_title: meta.config.title || 'NodeBB', + message: meta.config.maintenanceModeMessage, + }; - user.isAdministrator(req.uid, function (err, isAdmin) { - if (err || isAdmin) { - return next(err); - } + if (res.locals.isAPI) { + return res.json(data); + } - res.status(503); - var data = { - site_title: meta.config.title || 'NodeBB', - message: meta.config.maintenanceModeMessage, - }; - - if (res.locals.isAPI) { - return res.json(data); - } - - middleware.buildHeader(req, res, function () { + middleware.buildHeader(req, res, next); + }, + function () { res.render('503', data); - }); - }); + }, + ], callback); }; }; diff --git a/src/posts/bookmarks.js b/src/posts/bookmarks.js index 6038a50b54..c33591415c 100644 --- a/src/posts/bookmarks.js +++ b/src/posts/bookmarks.js @@ -18,69 +18,70 @@ module.exports = function (Posts) { if (!parseInt(uid, 10)) { return callback(new Error('[[error:not-logged-in]]')); } - var isBookmarking = type === 'bookmark'; - async.parallel({ - owner: function (next) { - Posts.getPostField(pid, 'uid', next); - }, - postData: function (next) { - Posts.getPostFields(pid, ['pid', 'uid'], next); - }, - hasBookmarked: function (next) { - Posts.hasBookmarked(pid, uid, next); + var isBookmarking = type === 'bookmark'; + var postData; + var hasBookmarked; + var owner; + async.waterfall([ + function (next) { + async.parallel({ + owner: function (next) { + Posts.getPostField(pid, 'uid', next); + }, + postData: function (next) { + Posts.getPostFields(pid, ['pid', 'uid'], next); + }, + hasBookmarked: function (next) { + Posts.hasBookmarked(pid, uid, next); + }, + }, next); }, - }, function (err, results) { - if (err) { - return callback(err); - } - - if (isBookmarking && results.hasBookmarked) { - return callback(new Error('[[error:already-bookmarked]]')); - } + function (results, next) { + owner = results.owner; + postData = results.postData; + hasBookmarked = results.hasBookmarked; - if (!isBookmarking && !results.hasBookmarked) { - return callback(new Error('[[error:already-unbookmarked]]')); - } + if (isBookmarking && hasBookmarked) { + return callback(new Error('[[error:already-bookmarked]]')); + } - async.waterfall([ - function (next) { - if (isBookmarking) { - db.sortedSetAdd('uid:' + uid + ':bookmarks', Date.now(), pid, next); - } else { - db.sortedSetRemove('uid:' + uid + ':bookmarks', pid, next); - } - }, - function (next) { - db[isBookmarking ? 'setAdd' : 'setRemove']('pid:' + pid + ':users_bookmarked', uid, next); - }, - function (next) { - db.setCount('pid:' + pid + ':users_bookmarked', next); - }, - function (count, next) { - results.postData.bookmarks = count; - Posts.setPostField(pid, 'bookmarks', count, next); - }, - ], function (err) { - if (err) { - return callback(err); + if (!isBookmarking && !hasBookmarked) { + return callback(new Error('[[error:already-unbookmarked]]')); } - var current = results.hasBookmarked ? 'bookmarked' : 'unbookmarked'; + if (isBookmarking) { + db.sortedSetAdd('uid:' + uid + ':bookmarks', Date.now(), pid, next); + } else { + db.sortedSetRemove('uid:' + uid + ':bookmarks', pid, next); + } + }, + function (next) { + db[isBookmarking ? 'setAdd' : 'setRemove']('pid:' + pid + ':users_bookmarked', uid, next); + }, + function (next) { + db.setCount('pid:' + pid + ':users_bookmarked', next); + }, + function (count, next) { + postData.bookmarks = count; + Posts.setPostField(pid, 'bookmarks', count, next); + }, + function (next) { + var current = hasBookmarked ? 'bookmarked' : 'unbookmarked'; plugins.fireHook('action:post.' + type, { pid: pid, uid: uid, - owner: results.owner, + owner: owner, current: current, }); - callback(null, { - post: results.postData, + next(null, { + post: postData, isBookmarked: isBookmarking, }); - }); - }); + }, + ], callback); } Posts.hasBookmarked = function (pid, uid, callback) { diff --git a/src/posts/create.js b/src/posts/create.js index 75b0f8321e..8386582eae 100644 --- a/src/posts/create.js +++ b/src/posts/create.js @@ -102,12 +102,12 @@ module.exports = function (Posts) { db.incrObjectField('global', 'postCount', next); }, ], function (err) { - if (err) { - return next(err); - } - plugins.fireHook('filter:post.get', { post: postData, uid: data.uid }, next); + next(err); }); }, + function (next) { + plugins.fireHook('filter:post.get', { post: postData, uid: data.uid }, next); + }, function (data, next) { data.post.isMain = isMain; plugins.fireHook('action:post.save', { post: _.clone(data.post) }); diff --git a/src/posts/delete.js b/src/posts/delete.js index 61793f68df..fe718d327c 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -216,87 +216,89 @@ module.exports = function (Posts) { } function deletePostFromCategoryRecentPosts(pid, callback) { - db.getSortedSetRange('categories:cid', 0, -1, function (err, cids) { - if (err) { - return callback(err); - } - - var sets = cids.map(function (cid) { - return 'cid:' + cid + ':pids'; - }); + async.waterfall([ + function (next) { + db.getSortedSetRange('categories:cid', 0, -1, next); + }, + function (cids, next) { + var sets = cids.map(function (cid) { + return 'cid:' + cid + ':pids'; + }); - db.sortedSetsRemove(sets, pid, callback); - }); + db.sortedSetsRemove(sets, pid, next); + }, + ], callback); } function deletePostFromUsersBookmarks(pid, callback) { - db.getSetMembers('pid:' + pid + ':users_bookmarked', function (err, uids) { - if (err) { - return callback(err); - } - - var sets = uids.map(function (uid) { - return 'uid:' + uid + ':bookmarks'; - }); - - db.sortedSetsRemove(sets, pid, function (err) { - if (err) { - return callback(err); - } + async.waterfall([ + function (next) { + db.getSetMembers('pid:' + pid + ':users_bookmarked', next); + }, + function (uids, next) { + var sets = uids.map(function (uid) { + return 'uid:' + uid + ':bookmarks'; + }); - db.delete('pid:' + pid + ':users_bookmarked', callback); - }); - }); + db.sortedSetsRemove(sets, pid, next); + }, + function (next) { + db.delete('pid:' + pid + ':users_bookmarked', next); + }, + ], 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); + async.waterfall([ + function (next) { + async.parallel({ + upvoters: function (next) { + db.getSetMembers('pid:' + pid + ':upvote', next); + }, + downvoters: function (next) { + db.getSetMembers('pid:' + pid + ':downvote', next); + }, + }, next); }, - }, function (err, results) { - if (err) { - return callback(err); - } - - var upvoterSets = results.upvoters.map(function (uid) { - return 'uid:' + uid + ':upvote'; - }); + function (results, next) { + var upvoterSets = results.upvoters.map(function (uid) { + return 'uid:' + uid + ':upvote'; + }); - var downvoterSets = results.downvoters.map(function (uid) { - return 'uid:' + uid + ':downvote'; - }); + var downvoterSets = results.downvoters.map(function (uid) { + return 'uid:' + uid + ':downvote'; + }); - async.parallel([ - function (next) { - db.sortedSetsRemove(upvoterSets, pid, next); - }, - function (next) { - db.sortedSetsRemove(downvoterSets, pid, next); - }, - function (next) { - db.deleteAll(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], next); - }, - ], callback); - }); + async.parallel([ + function (next) { + db.sortedSetsRemove(upvoterSets, pid, next); + }, + function (next) { + db.sortedSetsRemove(downvoterSets, pid, next); + }, + function (next) { + db.deleteAll(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], next); + }, + ], next); + }, + ], callback); } function deletePostFromReplies(pid, callback) { - Posts.getPostField(pid, 'toPid', function (err, toPid) { - if (err) { - return callback(err); - } - if (!parseInt(toPid, 10)) { - return callback(null); - } - async.parallel([ - async.apply(db.sortedSetRemove, 'pid:' + toPid + ':replies', pid), - async.apply(db.decrObjectField, 'post:' + toPid, 'replies'), - ], callback); - }); + async.waterfall([ + function (next) { + Posts.getPostField(pid, 'toPid', next); + }, + function (toPid, next) { + if (!parseInt(toPid, 10)) { + return callback(null); + } + async.parallel([ + async.apply(db.sortedSetRemove, 'pid:' + toPid + ':replies', pid), + async.apply(db.decrObjectField, 'post:' + toPid, 'replies'), + ], next); + }, + ], callback); } function deletePostFromGroups(pid, callback) { diff --git a/src/posts/edit.js b/src/posts/edit.js index 99cc8e1692..4780296594 100644 --- a/src/posts/edit.js +++ b/src/posts/edit.js @@ -83,74 +83,74 @@ module.exports = function (Posts) { var tid = postData.tid; var title = data.title ? data.title.trim() : ''; - async.parallel({ - topic: function (next) { - topics.getTopicFields(tid, ['cid', 'title', 'timestamp'], next); - }, - isMain: function (next) { - Posts.isMain(data.pid, next); + var topicData; + var results; + async.waterfall([ + function (next) { + async.parallel({ + topic: function (next) { + topics.getTopicFields(tid, ['cid', 'title', 'timestamp'], next); + }, + isMain: function (next) { + Posts.isMain(data.pid, next); + }, + }, next); }, - }, function (err, results) { - if (err) { - return callback(err); - } + function (_results, next) { + results = _results; + if (!results.isMain) { + return callback(null, { + tid: tid, + cid: results.topic.cid, + isMainPost: false, + renamed: false, + }); + } - if (!results.isMain) { - return callback(null, { + topicData = { tid: tid, cid: results.topic.cid, - isMainPost: false, - renamed: false, + uid: postData.uid, + mainPid: data.pid, + }; + + if (title) { + topicData.title = title; + topicData.slug = tid + '/' + (utils.slugify(title) || 'topic'); + } + + topicData.thumb = data.thumb || ''; + + data.tags = data.tags || []; + + plugins.fireHook('filter:topic.edit', { req: data.req, topic: topicData, data: data }, next); + }, + function (results, next) { + db.setObject('topic:' + tid, results.topic, next); + }, + function (next) { + topics.updateTags(tid, data.tags, next); + }, + function (next) { + topics.getTopicTagsObjects(tid, next); + }, + function (tags, next) { + topicData.tags = data.tags; + topicData.oldTitle = results.topic.title; + topicData.timestamp = results.topic.timestamp; + plugins.fireHook('action:topic.edit', { topic: topicData, uid: data.uid }); + next(null, { + tid: tid, + cid: topicData.cid, + uid: postData.uid, + title: validator.escape(String(title)), + oldTitle: results.topic.title, + slug: topicData.slug, + isMainPost: true, + renamed: title !== results.topic.title, + tags: tags, }); - } - - var topicData = { - tid: tid, - cid: results.topic.cid, - uid: postData.uid, - mainPid: data.pid, - }; - - if (title) { - topicData.title = title; - topicData.slug = tid + '/' + (utils.slugify(title) || 'topic'); - } - - topicData.thumb = data.thumb || ''; - - data.tags = data.tags || []; - - async.waterfall([ - function (next) { - plugins.fireHook('filter:topic.edit', { req: data.req, topic: topicData, data: data }, next); - }, - function (results, next) { - db.setObject('topic:' + tid, results.topic, next); - }, - function (next) { - topics.updateTags(tid, data.tags, next); - }, - function (next) { - topics.getTopicTagsObjects(tid, next); - }, - function (tags, next) { - topicData.tags = data.tags; - topicData.oldTitle = results.topic.title; - topicData.timestamp = results.topic.timestamp; - plugins.fireHook('action:topic.edit', { topic: topicData, uid: data.uid }); - next(null, { - tid: tid, - cid: results.topic.cid, - uid: postData.uid, - title: validator.escape(String(title)), - oldTitle: results.topic.title, - slug: topicData.slug, - isMainPost: true, - renamed: title !== results.topic.title, - tags: tags, - }); - }, - ], callback); - }); + }, + ], callback); } }; diff --git a/src/posts/summary.js b/src/posts/summary.js index 065007dca8..89223e80f9 100644 --- a/src/posts/summary.js +++ b/src/posts/summary.js @@ -89,47 +89,48 @@ module.exports = function (Posts) { function parsePosts(posts, options, callback) { async.map(posts, function (post, next) { - if (!post.content || !options.parse) { - if (options.stripTags) { - post.content = stripTags(post.content); - } - post.content = post.content ? validator.escape(String(post.content)) : post.content; - return next(null, post); - } - - Posts.parsePost(post, function (err, post) { - if (err) { - return next(err); - } - if (options.stripTags) { - post.content = stripTags(post.content); - } - - next(null, post); - }); + async.waterfall([ + function (next) { + if (!post.content || !options.parse) { + post.content = post.content ? validator.escape(String(post.content)) : post.content; + return next(null, post); + } + Posts.parsePost(post, next); + }, + function (post, next) { + if (options.stripTags) { + post.content = stripTags(post.content); + } + next(null, post); + }, + ], next); }, callback); } function getTopicAndCategories(tids, callback) { - topics.getTopicsFields(tids, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted', 'postcount', 'mainPid'], function (err, topics) { - if (err) { - return callback(err); - } - - var cids = topics.map(function (topic) { - if (topic) { - topic.title = String(topic.title); - topic.deleted = parseInt(topic.deleted, 10) === 1; - } - return topic && topic.cid; - }).filter(function (topic, index, array) { - return topic && array.indexOf(topic) === index; - }); - - categories.getCategoriesFields(cids, ['cid', 'name', 'icon', 'slug', 'parentCid', 'bgColor', 'color'], function (err, categories) { - callback(err, { topics: topics, categories: categories }); - }); - }); + var topicsData; + async.waterfall([ + function (next) { + topics.getTopicsFields(tids, ['uid', 'tid', 'title', 'cid', 'slug', 'deleted', 'postcount', 'mainPid'], next); + }, + function (_topicsData, next) { + topicsData = _topicsData; + var cids = topicsData.map(function (topic) { + if (topic) { + topic.title = String(topic.title); + topic.deleted = parseInt(topic.deleted, 10) === 1; + } + return topic && topic.cid; + }).filter(function (topic, index, array) { + return topic && array.indexOf(topic) === index; + }); + + categories.getCategoriesFields(cids, ['cid', 'name', 'icon', 'slug', 'parentCid', 'bgColor', 'color'], next); + }, + function (categoriesData, next) { + next(null, { topics: topicsData, categories: categoriesData }); + }, + ], callback); } function toObject(key, data) { diff --git a/src/posts/user.js b/src/posts/user.js index 59906bf2ca..00fa1c0631 100644 --- a/src/posts/user.js +++ b/src/posts/user.js @@ -25,69 +25,67 @@ module.exports = function (Posts) { }); groups.getGroupsData(groupTitles, next); }, - ], function (err, groupsData) { - if (err) { - return callback(err); - } - - groupsData.forEach(function (group) { - if (group && group.userTitleEnabled) { - groupsMap[group.name] = { - name: group.name, - slug: group.slug, - labelColor: group.labelColor, - icon: group.icon, - userTitle: group.userTitle, - }; - } - }); - - userData.forEach(function (userData) { - userData.uid = userData.uid || 0; - userData.username = userData.username || '[[global:guest]]'; - userData.userslug = userData.userslug || ''; - userData.reputation = userData.reputation || 0; - userData.postcount = userData.postcount || 0; - userData.banned = parseInt(userData.banned, 10) === 1; - userData.picture = userData.picture || ''; - userData.status = user.getStatus(userData); - userData.signature = validator.escape(String(userData.signature || '')); - userData.fullname = validator.escape(String(userData.fullname || '')); - }); - - async.map(userData, function (userData, next) { - async.parallel({ - isMemberOfGroup: function (next) { - if (!userData.groupTitle || !groupsMap[userData.groupTitle]) { - return next(); - } - groups.isMember(userData.uid, userData.groupTitle, next); - }, - signature: function (next) { - if (!userData.signature || parseInt(meta.config.disableSignatures, 10) === 1) { - userData.signature = ''; - return next(); - } - Posts.parseSignature(userData, uid, next); - }, - customProfileInfo: function (next) { - plugins.fireHook('filter:posts.custom_profile_info', { profile: [], uid: userData.uid }, next); - }, - }, function (err, results) { - if (err) { - return next(err); + function (groupsData, next) { + groupsData.forEach(function (group) { + if (group && group.userTitleEnabled) { + groupsMap[group.name] = { + name: group.name, + slug: group.slug, + labelColor: group.labelColor, + icon: group.icon, + userTitle: group.userTitle, + }; } + }); - if (results.isMemberOfGroup && userData.groupTitle && groupsMap[userData.groupTitle]) { - userData.selectedGroup = groupsMap[userData.groupTitle]; - } + userData.forEach(function (userData) { + userData.uid = userData.uid || 0; + userData.username = userData.username || '[[global:guest]]'; + userData.userslug = userData.userslug || ''; + userData.reputation = userData.reputation || 0; + userData.postcount = userData.postcount || 0; + userData.banned = parseInt(userData.banned, 10) === 1; + userData.picture = userData.picture || ''; + userData.status = user.getStatus(userData); + userData.signature = validator.escape(String(userData.signature || '')); + userData.fullname = validator.escape(String(userData.fullname || '')); + }); - userData.custom_profile_info = results.customProfileInfo.profile; + async.map(userData, function (userData, next) { + async.waterfall([ + function (next) { + async.parallel({ + isMemberOfGroup: function (next) { + if (!userData.groupTitle || !groupsMap[userData.groupTitle]) { + return next(); + } + groups.isMember(userData.uid, userData.groupTitle, next); + }, + signature: function (next) { + if (!userData.signature || parseInt(meta.config.disableSignatures, 10) === 1) { + userData.signature = ''; + return next(); + } + Posts.parseSignature(userData, uid, next); + }, + customProfileInfo: function (next) { + plugins.fireHook('filter:posts.custom_profile_info', { profile: [], uid: userData.uid }, next); + }, + }, next); + }, + function (results, next) { + if (results.isMemberOfGroup && userData.groupTitle && groupsMap[userData.groupTitle]) { + userData.selectedGroup = groupsMap[userData.groupTitle]; + } - plugins.fireHook('filter:posts.modifyUserInfo', userData, next); - }); - }, callback); - }); + userData.custom_profile_info = results.customProfileInfo.profile; + + plugins.fireHook('filter:posts.modifyUserInfo', userData, next); + }, + ], next); + }, next); + }, + ], callback); }; Posts.isOwner = function (pid, uid, callback) { diff --git a/src/posts/votes.js b/src/posts/votes.js index ddc860f5b6..5598be9b24 100644 --- a/src/posts/votes.js +++ b/src/posts/votes.js @@ -65,14 +65,14 @@ module.exports = function (Posts) { if (!parseInt(uid, 10)) { return callback(null, { upvoted: false, downvoted: false }); } - - db.isMemberOfSets(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], uid, function (err, hasVoted) { - if (err) { - return callback(err); - } - - callback(null, { upvoted: hasVoted[0], downvoted: hasVoted[1] }); - }); + async.waterfall([ + function (next) { + db.isMemberOfSets(['pid:' + pid + ':upvote', 'pid:' + pid + ':downvote'], uid, next); + }, + function (hasVoted, next) { + next(null, { upvoted: hasVoted[0], downvoted: hasVoted[1] }); + }, + ], callback); }; Posts.getVoteStatusByPostIDs = function (pids, uid, callback) { @@ -124,151 +124,157 @@ module.exports = function (Posts) { } function toggleVote(type, pid, uid, callback) { - unvote(pid, uid, type, function (err) { - if (err) { - return callback(err); - } - - vote(type, false, pid, uid, callback); - }); + async.waterfall([ + function (next) { + unvote(pid, uid, type, function (err) { + next(err); + }); + }, + function (next) { + vote(type, false, pid, uid, next); + }, + ], callback); } function unvote(pid, uid, command, callback) { - async.parallel({ - owner: function (next) { - Posts.getPostField(pid, 'uid', next); - }, - voteStatus: function (next) { - Posts.hasVoted(pid, uid, next); - }, - reputation: function (next) { - user.getUserField(uid, 'reputation', next); + async.waterfall([ + function (next) { + async.parallel({ + owner: function (next) { + Posts.getPostField(pid, 'uid', next); + }, + voteStatus: function (next) { + Posts.hasVoted(pid, uid, next); + }, + reputation: function (next) { + user.getUserField(uid, 'reputation', next); + }, + }, next); }, - }, function (err, results) { - if (err) { - return callback(err); - } - - if (parseInt(uid, 10) === parseInt(results.owner, 10)) { - return callback(new Error('self-vote')); - } + function (results, next) { + if (parseInt(uid, 10) === parseInt(results.owner, 10)) { + return callback(new Error('self-vote')); + } - if (command === 'downvote' && parseInt(results.reputation, 10) < parseInt(meta.config['privileges:downvote'], 10)) { - return callback(new Error('[[error:not-enough-reputation-to-downvote]]')); - } + if (command === 'downvote' && parseInt(results.reputation, 10) < parseInt(meta.config['privileges:downvote'], 10)) { + return callback(new Error('[[error:not-enough-reputation-to-downvote]]')); + } - var voteStatus = results.voteStatus; - var hook; - var current = voteStatus.upvoted ? 'upvote' : 'downvote'; - - if ((voteStatus.upvoted && command === 'downvote') || (voteStatus.downvoted && command === 'upvote')) { // e.g. User *has* upvoted, and clicks downvote - hook = command; - } else if (voteStatus.upvoted || voteStatus.downvoted) { // e.g. User *has* upvoted, clicks upvote (so we "unvote") - hook = 'unvote'; - } else { // e.g. User *has not* voted, clicks upvote - hook = command; - current = 'unvote'; - } + var voteStatus = results.voteStatus; + var hook; + var current = voteStatus.upvoted ? 'upvote' : 'downvote'; + + if ((voteStatus.upvoted && command === 'downvote') || (voteStatus.downvoted && command === 'upvote')) { // e.g. User *has* upvoted, and clicks downvote + hook = command; + } else if (voteStatus.upvoted || voteStatus.downvoted) { // e.g. User *has* upvoted, clicks upvote (so we "unvote") + hook = 'unvote'; + } else { // e.g. User *has not* voted, clicks upvote + hook = command; + current = 'unvote'; + } - plugins.fireHook('action:post.' + hook, { - pid: pid, - uid: uid, - owner: results.owner, - current: current, - }); + plugins.fireHook('action:post.' + hook, { + pid: pid, + uid: uid, + owner: results.owner, + current: current, + }); - if (!voteStatus || (!voteStatus.upvoted && !voteStatus.downvoted)) { - return callback(); - } + if (!voteStatus || (!voteStatus.upvoted && !voteStatus.downvoted)) { + return callback(); + } - vote(voteStatus.upvoted ? 'downvote' : 'upvote', true, pid, uid, callback); - }); + vote(voteStatus.upvoted ? 'downvote' : 'upvote', true, pid, uid, next); + }, + ], callback); } function vote(type, unvote, pid, uid, callback) { uid = parseInt(uid, 10); - if (uid === 0) { + if (!uid) { return callback(new Error('[[error:not-logged-in]]')); } + var postData; + var newreputation; + async.waterfall([ + function (next) { + Posts.getPostFields(pid, ['pid', 'uid', 'tid'], next); + }, + function (_postData, next) { + postData = _postData; + var now = Date.now(); - Posts.getPostFields(pid, ['pid', 'uid', 'tid'], function (err, postData) { - if (err) { - return callback(err); - } - - var now = Date.now(); - - if (type === 'upvote' && !unvote) { - db.sortedSetAdd('uid:' + uid + ':upvote', now, pid); - } else { - db.sortedSetRemove('uid:' + uid + ':upvote', pid); - } - - if (type === 'upvote' || unvote) { - db.sortedSetRemove('uid:' + uid + ':downvote', pid); - } else { - db.sortedSetAdd('uid:' + uid + ':downvote', now, pid); - } + if (type === 'upvote' && !unvote) { + db.sortedSetAdd('uid:' + uid + ':upvote', now, pid); + } else { + db.sortedSetRemove('uid:' + uid + ':upvote', pid); + } - user[type === 'upvote' ? 'incrementUserFieldBy' : 'decrementUserFieldBy'](postData.uid, 'reputation', 1, function (err, newreputation) { - if (err) { - return callback(err); + if (type === 'upvote' || unvote) { + db.sortedSetRemove('uid:' + uid + ':downvote', pid); + } else { + db.sortedSetAdd('uid:' + uid + ':downvote', now, pid); } + user[type === 'upvote' ? 'incrementUserFieldBy' : 'decrementUserFieldBy'](postData.uid, 'reputation', 1, next); + }, + function (_newreputation, next) { + newreputation = _newreputation; if (parseInt(postData.uid, 10)) { db.sortedSetAdd('users:reputation', newreputation, postData.uid); } - adjustPostVotes(postData, uid, type, unvote, function (err) { - callback(err, { - user: { - reputation: newreputation, - }, - post: postData, - upvote: type === 'upvote' && !unvote, - downvote: type === 'downvote' && !unvote, - }); + adjustPostVotes(postData, uid, type, unvote, next); + }, + function (next) { + next(null, { + user: { + reputation: newreputation, + }, + post: postData, + upvote: type === 'upvote' && !unvote, + downvote: type === 'downvote' && !unvote, }); - }); - }); + }, + ], callback); } function adjustPostVotes(postData, uid, type, unvote, callback) { var notType = (type === 'upvote' ? 'downvote' : 'upvote'); - - async.series([ + async.waterfall([ function (next) { - if (unvote) { - db.setRemove('pid:' + postData.pid + ':' + type, uid, next); - } else { - db.setAdd('pid:' + postData.pid + ':' + type, uid, next); - } + async.series([ + function (next) { + if (unvote) { + db.setRemove('pid:' + postData.pid + ':' + type, uid, next); + } else { + db.setAdd('pid:' + postData.pid + ':' + type, uid, next); + } + }, + function (next) { + db.setRemove('pid:' + postData.pid + ':' + notType, uid, next); + }, + ], function (err) { + next(err); + }); }, function (next) { - db.setRemove('pid:' + postData.pid + ':' + notType, uid, next); + async.parallel({ + upvotes: function (next) { + db.setCount('pid:' + postData.pid + ':upvote', next); + }, + downvotes: function (next) { + db.setCount('pid:' + postData.pid + ':downvote', next); + }, + }, next); }, - ], function (err) { - if (err) { - return callback(err); - } - - async.parallel({ - upvotes: function (next) { - db.setCount('pid:' + postData.pid + ':upvote', next); - }, - downvotes: function (next) { - db.setCount('pid:' + postData.pid + ':downvote', next); - }, - }, function (err, results) { - if (err) { - return callback(err); - } + function (results, next) { postData.upvotes = parseInt(results.upvotes, 10); postData.downvotes = parseInt(results.downvotes, 10); postData.votes = postData.upvotes - postData.downvotes; - Posts.updatePostVoteCount(postData, callback); - }); - }); + Posts.updatePostVoteCount(postData, next); + }, + ], callback); } }; diff --git a/src/widgets/index.js b/src/widgets/index.js index b671effa8e..2e228dab63 100644 --- a/src/widgets/index.js +++ b/src/widgets/index.js @@ -8,119 +8,132 @@ var plugins = require('../plugins'); var translator = require('../translator'); var db = require('../database'); -var widgets = {}; +var widgets = module.exports; widgets.render = function (uid, area, req, res, callback) { if (!area.locations || !area.template) { return callback(new Error('[[error:invalid-data]]')); } - widgets.getAreas(['global', area.template], area.locations, function (err, data) { - if (err) { - return callback(err); - } - - var widgetsByLocation = {}; - - async.map(area.locations, function (location, done) { - widgetsByLocation[location] = data.global[location].concat(data[area.template][location]); + async.waterfall([ + function (next) { + widgets.getAreas(['global', area.template], area.locations, next); + }, + function (data, next) { + var widgetsByLocation = {}; - if (!widgetsByLocation[location].length) { - return done(null, { location: location, widgets: [] }); - } + async.map(area.locations, function (location, done) { + widgetsByLocation[location] = data.global[location].concat(data[area.template][location]); - async.map(widgetsByLocation[location], function (widget, next) { - if (!widget || !widget.data || - (!!widget.data['hide-registered'] && uid !== 0) || - (!!widget.data['hide-guests'] && uid === 0) || - (!!widget.data['hide-mobile'] && area.isMobile)) { - return next(); + if (!widgetsByLocation[location].length) { + return done(null, { location: location, widgets: [] }); } - plugins.fireHook('filter:widget.render:' + widget.widget, { - uid: uid, - area: area, - data: widget.data, - req: req, - res: res, - }, function (err, data) { - if (err || data === null) { - return next(err); - } - var html = data; - if (typeof html !== 'string') { - html = data.html; - } else { - winston.warn('[widgets.render] passing a string is deprecated!, filter:widget.render:' + widget.widget + '. Please set hookData.html in your plugin.'); + async.map(widgetsByLocation[location], function (widget, next) { + if (!widget || !widget.data || + (!!widget.data['hide-registered'] && uid !== 0) || + (!!widget.data['hide-guests'] && uid === 0) || + (!!widget.data['hide-mobile'] && area.isMobile)) { + return next(); } - if (widget.data.container && widget.data.container.match('{body}')) { - translator.translate(widget.data.title, function (title) { - html = templates.parse(widget.data.container, { - title: title, - body: html, - }); - - next(null, { html: html }); - }); - } else { - next(null, { html: html }); - } + renderWidget(widget, uid, area, req, res, next); + }, function (err, result) { + done(err, { location: location, widgets: result.filter(Boolean) }); }); - }, function (err, result) { - done(err, { location: location, widgets: result.filter(Boolean) }); - }); - }, callback); - }); + }, next); + }, + ], callback); }; +function renderWidget(widget, uid, area, req, res, callback) { + async.waterfall([ + function (next) { + plugins.fireHook('filter:widget.render:' + widget.widget, { + uid: uid, + area: area, + data: widget.data, + req: req, + res: res, + }, next); + }, + function (data, next) { + if (!data) { + return callback(); + } + var html = data; + if (typeof html !== 'string') { + html = data.html; + } else { + winston.warn('[widgets.render] passing a string is deprecated!, filter:widget.render:' + widget.widget + '. Please set hookData.html in your plugin.'); + } + + if (widget.data.container && widget.data.container.match('{body}')) { + translator.translate(widget.data.title, function (title) { + html = templates.parse(widget.data.container, { + title: title, + body: html, + }); + + next(null, { html: html }); + }); + } else { + next(null, { html: html }); + } + }, + ], callback); +} + widgets.getAreas = function (templates, locations, callback) { var keys = templates.map(function (tpl) { return 'widgets:' + tpl; }); - db.getObjectsFields(keys, locations, function (err, data) { - if (err) { - return callback(err); - } - - var returnData = {}; - - templates.forEach(function (template, index) { - returnData[template] = returnData[template] || {}; - locations.forEach(function (location) { - if (data && data[index] && data[index][location]) { - try { - returnData[template][location] = JSON.parse(data[index][location]); - } catch (err) { - winston.error('can not parse widget data. template: ' + template + ' location: ' + location); + async.waterfall([ + function (next) { + db.getObjectsFields(keys, locations, next); + }, + function (data, next) { + var returnData = {}; + + templates.forEach(function (template, index) { + returnData[template] = returnData[template] || {}; + locations.forEach(function (location) { + if (data && data[index] && data[index][location]) { + try { + returnData[template][location] = JSON.parse(data[index][location]); + } catch (err) { + winston.error('can not parse widget data. template: ' + template + ' location: ' + location); + returnData[template][location] = []; + } + } else { returnData[template][location] = []; } - } else { - returnData[template][location] = []; - } + }); }); - }); - callback(null, returnData); - }); + next(null, returnData); + }, + ], callback); }; widgets.getArea = function (template, location, callback) { - db.getObjectField('widgets:' + template, location, function (err, result) { - if (err) { - return callback(err); - } - if (!result) { - return callback(null, []); - } - try { - result = JSON.parse(result); - } catch (err) { - return callback(err); - } - - callback(null, result); - }); + async.waterfall([ + function (next) { + db.getObjectField('widgets:' + template, location, next); + }, + function (result, next) { + if (!result) { + return callback(null, []); + } + try { + result = JSON.parse(result); + } catch (err) { + return callback(err); + } + + next(null, result); + }, + ], callback); }; widgets.setArea = function (area, callback) { @@ -137,42 +150,42 @@ widgets.reset = function (callback) { { name: 'Draft Zone', template: 'global', location: 'footer' }, { name: 'Draft Zone', template: 'global', location: 'sidebar' }, ]; - - async.parallel({ - areas: function (next) { - plugins.fireHook('filter:widgets.getAreas', defaultAreas, next); + var drafts; + async.waterfall([ + function (next) { + async.parallel({ + areas: function (next) { + plugins.fireHook('filter:widgets.getAreas', defaultAreas, next); + }, + drafts: function (next) { + widgets.getArea('global', 'drafts', next); + }, + }, next); }, - drafts: function (next) { - widgets.getArea('global', 'drafts', next); + function (results, next) { + drafts = results.drafts || []; + + async.each(results.areas, function (area, next) { + async.waterfall([ + function (next) { + widgets.getArea(area.template, area.location, next); + }, + function (areaData, next) { + drafts = drafts.concat(areaData); + area.widgets = []; + widgets.setArea(area, next); + }, + ], next); + }, next); }, - }, function (err, results) { - if (err) { - return callback(err); - } - - var drafts = results.drafts || []; - - async.each(results.areas, function (area, next) { - widgets.getArea(area.template, area.location, function (err, areaData) { - if (err) { - return next(err); - } - - drafts = drafts.concat(areaData); - area.widgets = []; - widgets.setArea(area, next); - }); - }, function (err) { - if (err) { - return callback(err); - } + function (next) { widgets.setArea({ template: 'global', location: 'drafts', widgets: drafts, - }, callback); - }); - }); + }, next); + }, + ], callback); }; module.exports = widgets; diff --git a/test/mocks/databasemock.js b/test/mocks/databasemock.js index 675aa9e866..d9ce5432c9 100644 --- a/test/mocks/databasemock.js +++ b/test/mocks/databasemock.js @@ -138,6 +138,9 @@ function setupMockDefaults(callback) { function (next) { db.emptydb(next); }, + function (next) { + db.createIndices(next); + }, function (next) { winston.info('test_database flushed'); setupDefaultConfigs(meta, next);