diff --git a/src/categories/delete.js b/src/categories/delete.js index 5c8e5bdf9a..f91842e487 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -11,19 +11,30 @@ var privileges = require('../privileges'); module.exports = function (Categories) { Categories.purge = function (cid, uid, callback) { - batch.processSortedSet('cid:' + cid + ':tids', function (tids, next) { - async.eachLimit(tids, 10, function (tid, next) { - topics.purgePostsAndTopic(tid, uid, next); - }, next); - }, {alwaysStartAt: 0}, function (err) { - if (err) { - return callback(err); + async.waterfall([ + function (next) { + batch.processSortedSet('cid:' + cid + ':tids', function (tids, next) { + async.eachLimit(tids, 10, function (tid, next) { + topics.purgePostsAndTopic(tid, uid, next); + }, next); + }, {alwaysStartAt: 0}, next); + }, + function (next) { + Categories.getPinnedTids('cid:' + cid + ':tids:pinned', 0, -1, next); + }, + function (pinnedTids, next) { + async.eachLimit(pinnedTids, 10, function (tid, next) { + topics.purgePostsAndTopic(tid, uid, next); + }, next); + }, + function (next) { + purgeCategory(cid, next); + }, + function (next) { + plugins.fireHook('action:category.delete', cid); + next(); } - async.series([ - async.apply(purgeCategory, cid), - async.apply(plugins.fireHook, 'action:category.delete', cid) - ], callback); - }); + ], callback); }; function purgeCategory(cid, callback) { @@ -37,6 +48,7 @@ module.exports = function (Categories) { function (next) { db.deleteAll([ 'cid:' + cid + ':tids', + 'cid:' + cid + ':tids:pinned', 'cid:' + cid + ':tids:posts', 'cid:' + cid + ':pids', 'cid:' + cid + ':read_by_uid', @@ -50,7 +62,9 @@ module.exports = function (Categories) { groups.destroy('cid:' + cid + ':privileges:' + privilege, next); }, next); } - ], callback); + ], function (err) { + callback(err); + }); } function removeFromParent(cid, callback) { diff --git a/src/categories/topics.js b/src/categories/topics.js index ec211da960..c60050c667 100644 --- a/src/categories/topics.js +++ b/src/categories/topics.js @@ -14,7 +14,7 @@ module.exports = function (Categories) { plugins.fireHook('filter:category.topics.prepare', data, next); }, function (data, next) { - Categories.getTopicIds(data.set, data.reverse, data.start, data.stop, next); + Categories.getTopicIds(data.cid, data.set, data.reverse, data.start, data.stop, next); }, function (tids, next) { topics.getTopicsByTids(tids, data.uid, next); @@ -36,6 +36,58 @@ module.exports = function (Categories) { ], callback); }; + Categories.getTopicIds = function (cid, set, reverse, start, stop, callback) { + var pinnedTids; + var pinnedCount; + var totalPinnedCount; + + async.waterfall([ + function (next) { + Categories.getPinnedTids(cid, 0, -1, next); + }, + function (_pinnedTids, next) { + totalPinnedCount = _pinnedTids.length; + + pinnedTids = _pinnedTids.slice(start, stop === -1 ? undefined : stop + 1); + + pinnedCount = pinnedTids.length; + + var topicsPerPage = stop - start + 1; + + var normalTidsToGet = Math.max(0, topicsPerPage - pinnedCount); + + if (!normalTidsToGet && stop !== -1) { + return next(null, []); + } + if (start > 0 && totalPinnedCount) { + start -= totalPinnedCount - pinnedCount; + } + stop = stop === -1 ? stop : start + normalTidsToGet - 1; + + if (Array.isArray(set)) { + db[reverse ? 'getSortedSetRevIntersect' : 'getSortedSetIntersect']({sets: set, start: start, stop: stop}, next); + } else { + db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop, next); + } + }, + function (normalTids, next) { + normalTids = normalTids.filter(function (tid) { + return pinnedTids.indexOf(tid) === -1; + }); + + next(null, pinnedTids.concat(normalTids)); + } + ], callback); + }; + + Categories.getAllTopicIds = function (cid, start, stop, callback) { + db.getSortedSetRange(['cid:' + cid + ':tids:pinned', 'cid:' + cid + ':tids'], start, stop, callback); + }; + + Categories.getPinnedTids = function (cid, start, stop, callback) { + db.getSortedSetRevRange('cid:' + cid + ':tids:pinned', start, stop, callback); + }; + Categories.modifyTopicsByPrivilege = function (topics, privileges) { if (!Array.isArray(topics) || !topics.length || privileges.isAdminOrMod) { return; @@ -52,22 +104,9 @@ module.exports = function (Categories) { }); }; - Categories.getTopicIds = function (set, reverse, start, stop, callback) { - if (Array.isArray(set)) { - db[reverse ? 'getSortedSetRevIntersect' : 'getSortedSetIntersect']({sets: set, start: start, stop: stop}, callback); - } else { - db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop, callback); - } - }; - Categories.getTopicIndex = function (tid, callback) { - topics.getTopicField(tid, 'cid', function (err, cid) { - if (err) { - return callback(err); - } - - db.sortedSetRevRank('cid:' + cid + ':tids', tid, callback); - }); + console.warn('[Categories.getTopicIndex] deprecated'); + callback(null, 1); }; Categories.onNewPostMade = function (cid, pinned, postData, callback) { diff --git a/src/socket.io/topics/move.js b/src/socket.io/topics/move.js index 699c5e5d35..25f5f8a19d 100644 --- a/src/socket.io/topics/move.js +++ b/src/socket.io/topics/move.js @@ -62,7 +62,7 @@ module.exports = function (SocketTopics) { return callback(new Error('[[error:no-privileges]]')); } - categories.getTopicIds('cid:' + data.currentCid + ':tids', true, 0, -1, next); + categories.getAllTopicIds(data.currentCid, 0, -1, next); }, function (tids, next) { async.eachLimit(tids, 50, function (tid, next) { diff --git a/src/topics.js b/src/topics.js index 900a46d604..ff5d40fc2d 100644 --- a/src/topics.js +++ b/src/topics.js @@ -53,23 +53,8 @@ var social = require('./social'); }; Topics.getTidPage = function (tid, uid, callback) { - if(!tid) { - return callback(new Error('[[error:invalid-tid]]')); - } - - async.parallel({ - index: function (next) { - categories.getTopicIndex(tid, next); - }, - settings: function (next) { - user.getSettings(uid, next); - } - }, function (err, results) { - if (err) { - return callback(err); - } - callback(null, Math.ceil((results.index + 1) / results.settings.topicsPerPage)); - }); + console.warn('[Topics.getTidPage] deprecated!'); + callback(null, 1); }; Topics.getTopicsFromSet = function (set, uid, start, stop, callback) { diff --git a/src/topics/delete.js b/src/topics/delete.js index df63ebe3bc..91c1bf53e3 100644 --- a/src/topics/delete.js +++ b/src/topics/delete.js @@ -177,6 +177,7 @@ module.exports = function (Topics) { function (next) { db.sortedSetsRemove([ 'cid:' + topicData.cid + ':tids', + 'cid:' + topicData.cid + ':tids:pinned', 'cid:' + topicData.cid + ':tids:posts', 'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids', 'uid:' + topicData.uid + ':topics' diff --git a/src/topics/recent.js b/src/topics/recent.js index 421bb0cd71..23ca9ff5ba 100644 --- a/src/topics/recent.js +++ b/src/topics/recent.js @@ -7,6 +7,7 @@ var db = require('../database'); var plugins = require('../plugins'); var privileges = require('../privileges'); var user = require('../user'); +var categories = require('../categories'); module.exports = function (Topics) { var terms = { @@ -24,7 +25,11 @@ module.exports = function (Topics) { async.waterfall([ function (next) { - db.getSortedSetRevRange(cid ? 'cid:' + cid + ':tids' : 'topics:recent', 0, 199, next); + if (cid) { + categories.getTopicIds(cid, 'cid:' + cid + ':tids', true, 0, 199, next); + } else { + db.getSortedSetRevRange('topics:recent', 0, 199, next); + } }, function (tids, next) { filterTids(tids, uid, filter, next); diff --git a/src/topics/suggested.js b/src/topics/suggested.js index aebef72683..d51d51827b 100644 --- a/src/topics/suggested.js +++ b/src/topics/suggested.js @@ -72,7 +72,7 @@ module.exports = function (Topics) { Topics.getTopicField(tid, 'cid', next); }, function (cid, next) { - categories.getTopicIds('cid:' + cid + ':tids', true, 0, 9, next); + categories.getTopicIds(cid, 'cid:' + cid + ':tids', true, 0, 9, next); } ], callback); } diff --git a/src/topics/tools.js b/src/topics/tools.js index 6fce256f27..377b3bdfcd 100644 --- a/src/topics/tools.js +++ b/src/topics/tools.js @@ -4,7 +4,6 @@ var async = require('async'); var db = require('../database'); var categories = require('../categories'); -var meta = require('../meta'); var plugins = require('../plugins'); var privileges = require('../privileges'); @@ -167,7 +166,7 @@ module.exports = function (Topics) { if (!exists) { return callback(new Error('[[error:no-topic]]')); } - Topics.getTopicFields(tid, ['cid', 'lastposttime'], next); + Topics.getTopicFields(tid, ['cid', 'lastposttime', 'postcount'], next); }, function (_topicData, next) { topicData = _topicData; @@ -177,9 +176,24 @@ module.exports = function (Topics) { if (!isAdminOrMod) { return next(new Error('[[error:no-privileges]]')); } + async.parallel([ async.apply(Topics.setTopicField, tid, 'pinned', pin ? 1 : 0), - async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids', pin ? Math.pow(2, 53) : topicData.lastposttime, tid) + function (next) { + if (pin) { + async.parallel([ + async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids:pinned', Date.now(), tid), + async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids', tid), + async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids:posts', tid), + ], next); + } else { + async.parallel([ + async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids:pinned', tid), + async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids', topicData.lastposttime, tid), + async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids:posts', topicData.postcount, tid), + ], next); + } + } ], next); }, function (results, next) { @@ -213,20 +227,24 @@ module.exports = function (Topics) { topic = topicData; db.sortedSetsRemove([ 'cid:' + topicData.cid + ':tids', + 'cid:' + topicData.cid + ':tids:pinned', 'cid:' + topicData.cid + ':tids:posts' ], tid, next); }, function (next) { - var timestamp = parseInt(topic.pinned, 10) ? Math.pow(2, 53) : topic.lastposttime; - async.parallel([ - function (next) { - db.sortedSetAdd('cid:' + cid + ':tids', timestamp, tid, next); - }, - function (next) { - topic.postcount = topic.postcount || 0; - db.sortedSetAdd('cid:' + cid + ':tids:posts', topic.postcount, tid, next); - } - ], next); + if (parseInt(topic.pinned, 10)) { + db.sortedSetAdd('cid:' + cid + ':tids:pinned', Date.now(), tid, next); + } else { + async.parallel([ + function (next) { + db.sortedSetAdd('cid:' + cid + ':tids', topic.lastposttime, tid, next); + }, + function (next) { + topic.postcount = topic.postcount || 0; + db.sortedSetAdd('cid:' + cid + ':tids:posts', topic.postcount, tid, next); + } + ], next); + } } ], function (err) { if (err) { diff --git a/src/upgrade.js b/src/upgrade.js index 135d53b9fc..79ffa6b5ee 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -1,6 +1,6 @@ "use strict"; -/* globals define, console, require */ +/* globals console, require */ var db = require('./database'), async = require('async'), @@ -1016,7 +1016,49 @@ Upgrade.upgrade = function (callback) { winston.info('[2016/11/22] Update global and user language keys - skipped!'); next(); } - } + }, + function (next) { + thisSchemaDate = Date.UTC(2016, 10, 25); + + if (schemaDate < thisSchemaDate) { + updatesMade = true; + winston.info('[2016/11/25] Creating sorted sets for pinned topcis'); + + var topics = require('./topics'); + var batch = require('./batch'); + batch.processSortedSet('topics:tid', function (ids, next) { + topics.getTopicsFields(ids, ['tid', 'cid', 'pinned', 'lastposttime'], function (err, data) { + if (err) { + return next(err); + } + + data = data.filter(function (topicData) { + return parseInt(topicData.pinned, 10) === 1; + }); + + async.eachSeries(data, function (topicData, next) { + console.log('processing tid: ' + topicData.tid); + + async.parallel([ + async.apply(db.sortedSetAdd, 'cid:' + topicData.cid + ':tids:pinned', Date.now(), topicData.tid), + async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids', topicData.tid), + async.apply(db.sortedSetRemove, 'cid:' + topicData.cid + ':tids:posts', topicData.tid) + ], next); + }, next); + }); + }, function (err) { + if (err) { + return next(err); + } + + winston.info('[2016/11/25] Creating sorted sets for pinned topics - done'); + Upgrade.update(thisSchemaDate, next); + }); + } else { + winston.info('[2016/11/25] Creating sorted sets for pinned topics - skipped!'); + next(); + } + }, // Add new schema updates here // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!! ], function (err) { diff --git a/test/categories.js b/test/categories.js index a7e37f244e..abcaf118c8 100644 --- a/test/categories.js +++ b/test/categories.js @@ -366,6 +366,28 @@ describe('Categories', function () { }); }); }); + + it('should purge category', function (done) { + Categories.create({ + name: 'purge me', + description: 'update description' + }, function (err, category) { + assert.ifError(err); + Topics.post({ + uid: posterUid, + cid: category.cid, + title: 'Test Topic Title', + content: 'The content of test topic' + }, function (err) { + assert.ifError(err); + socketCategories.purge({uid: adminUid}, category.cid, function (err) { + assert.ifError(err); + done(); + }); + }); + + }); + }); });