store pinned topics in new zset

keep pinned topics on top on different sort types
v1.18.x
barisusakli 8 years ago
parent d4638ffc15
commit c33d3e874a

@ -11,19 +11,30 @@ var privileges = require('../privileges');
module.exports = function (Categories) { module.exports = function (Categories) {
Categories.purge = function (cid, uid, callback) { Categories.purge = function (cid, uid, callback) {
batch.processSortedSet('cid:' + cid + ':tids', function (tids, next) { async.waterfall([
async.eachLimit(tids, 10, function (tid, next) { function (next) {
topics.purgePostsAndTopic(tid, uid, next); batch.processSortedSet('cid:' + cid + ':tids', function (tids, next) {
}, next); async.eachLimit(tids, 10, function (tid, next) {
}, {alwaysStartAt: 0}, function (err) { topics.purgePostsAndTopic(tid, uid, next);
if (err) { }, next);
return callback(err); }, {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([ ], callback);
async.apply(purgeCategory, cid),
async.apply(plugins.fireHook, 'action:category.delete', cid)
], callback);
});
}; };
function purgeCategory(cid, callback) { function purgeCategory(cid, callback) {
@ -37,6 +48,7 @@ module.exports = function (Categories) {
function (next) { function (next) {
db.deleteAll([ db.deleteAll([
'cid:' + cid + ':tids', 'cid:' + cid + ':tids',
'cid:' + cid + ':tids:pinned',
'cid:' + cid + ':tids:posts', 'cid:' + cid + ':tids:posts',
'cid:' + cid + ':pids', 'cid:' + cid + ':pids',
'cid:' + cid + ':read_by_uid', 'cid:' + cid + ':read_by_uid',
@ -50,7 +62,9 @@ module.exports = function (Categories) {
groups.destroy('cid:' + cid + ':privileges:' + privilege, next); groups.destroy('cid:' + cid + ':privileges:' + privilege, next);
}, next); }, next);
} }
], callback); ], function (err) {
callback(err);
});
} }
function removeFromParent(cid, callback) { function removeFromParent(cid, callback) {

@ -14,7 +14,7 @@ module.exports = function (Categories) {
plugins.fireHook('filter:category.topics.prepare', data, next); plugins.fireHook('filter:category.topics.prepare', data, next);
}, },
function (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) { function (tids, next) {
topics.getTopicsByTids(tids, data.uid, next); topics.getTopicsByTids(tids, data.uid, next);
@ -36,6 +36,58 @@ module.exports = function (Categories) {
], callback); ], 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) { Categories.modifyTopicsByPrivilege = function (topics, privileges) {
if (!Array.isArray(topics) || !topics.length || privileges.isAdminOrMod) { if (!Array.isArray(topics) || !topics.length || privileges.isAdminOrMod) {
return; 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) { Categories.getTopicIndex = function (tid, callback) {
topics.getTopicField(tid, 'cid', function (err, cid) { console.warn('[Categories.getTopicIndex] deprecated');
if (err) { callback(null, 1);
return callback(err);
}
db.sortedSetRevRank('cid:' + cid + ':tids', tid, callback);
});
}; };
Categories.onNewPostMade = function (cid, pinned, postData, callback) { Categories.onNewPostMade = function (cid, pinned, postData, callback) {

@ -62,7 +62,7 @@ module.exports = function (SocketTopics) {
return callback(new Error('[[error:no-privileges]]')); 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) { function (tids, next) {
async.eachLimit(tids, 50, function (tid, next) { async.eachLimit(tids, 50, function (tid, next) {

@ -53,23 +53,8 @@ var social = require('./social');
}; };
Topics.getTidPage = function (tid, uid, callback) { Topics.getTidPage = function (tid, uid, callback) {
if(!tid) { console.warn('[Topics.getTidPage] deprecated!');
return callback(new Error('[[error:invalid-tid]]')); callback(null, 1);
}
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));
});
}; };
Topics.getTopicsFromSet = function (set, uid, start, stop, callback) { Topics.getTopicsFromSet = function (set, uid, start, stop, callback) {

@ -177,6 +177,7 @@ module.exports = function (Topics) {
function (next) { function (next) {
db.sortedSetsRemove([ db.sortedSetsRemove([
'cid:' + topicData.cid + ':tids', 'cid:' + topicData.cid + ':tids',
'cid:' + topicData.cid + ':tids:pinned',
'cid:' + topicData.cid + ':tids:posts', 'cid:' + topicData.cid + ':tids:posts',
'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids', 'cid:' + topicData.cid + ':uid:' + topicData.uid + ':tids',
'uid:' + topicData.uid + ':topics' 'uid:' + topicData.uid + ':topics'

@ -7,6 +7,7 @@ var db = require('../database');
var plugins = require('../plugins'); var plugins = require('../plugins');
var privileges = require('../privileges'); var privileges = require('../privileges');
var user = require('../user'); var user = require('../user');
var categories = require('../categories');
module.exports = function (Topics) { module.exports = function (Topics) {
var terms = { var terms = {
@ -24,7 +25,11 @@ module.exports = function (Topics) {
async.waterfall([ async.waterfall([
function (next) { 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) { function (tids, next) {
filterTids(tids, uid, filter, next); filterTids(tids, uid, filter, next);

@ -72,7 +72,7 @@ module.exports = function (Topics) {
Topics.getTopicField(tid, 'cid', next); Topics.getTopicField(tid, 'cid', next);
}, },
function (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); ], callback);
} }

@ -4,7 +4,6 @@ var async = require('async');
var db = require('../database'); var db = require('../database');
var categories = require('../categories'); var categories = require('../categories');
var meta = require('../meta');
var plugins = require('../plugins'); var plugins = require('../plugins');
var privileges = require('../privileges'); var privileges = require('../privileges');
@ -167,7 +166,7 @@ module.exports = function (Topics) {
if (!exists) { if (!exists) {
return callback(new Error('[[error:no-topic]]')); return callback(new Error('[[error:no-topic]]'));
} }
Topics.getTopicFields(tid, ['cid', 'lastposttime'], next); Topics.getTopicFields(tid, ['cid', 'lastposttime', 'postcount'], next);
}, },
function (_topicData, next) { function (_topicData, next) {
topicData = _topicData; topicData = _topicData;
@ -177,9 +176,24 @@ module.exports = function (Topics) {
if (!isAdminOrMod) { if (!isAdminOrMod) {
return next(new Error('[[error:no-privileges]]')); return next(new Error('[[error:no-privileges]]'));
} }
async.parallel([ async.parallel([
async.apply(Topics.setTopicField, tid, 'pinned', pin ? 1 : 0), 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); ], next);
}, },
function (results, next) { function (results, next) {
@ -213,20 +227,24 @@ module.exports = function (Topics) {
topic = topicData; topic = topicData;
db.sortedSetsRemove([ db.sortedSetsRemove([
'cid:' + topicData.cid + ':tids', 'cid:' + topicData.cid + ':tids',
'cid:' + topicData.cid + ':tids:pinned',
'cid:' + topicData.cid + ':tids:posts' 'cid:' + topicData.cid + ':tids:posts'
], tid, next); ], tid, next);
}, },
function (next) { function (next) {
var timestamp = parseInt(topic.pinned, 10) ? Math.pow(2, 53) : topic.lastposttime; if (parseInt(topic.pinned, 10)) {
async.parallel([ db.sortedSetAdd('cid:' + cid + ':tids:pinned', Date.now(), tid, next);
function (next) { } else {
db.sortedSetAdd('cid:' + cid + ':tids', timestamp, tid, next); async.parallel([
}, function (next) {
function (next) { db.sortedSetAdd('cid:' + cid + ':tids', topic.lastposttime, tid, next);
topic.postcount = topic.postcount || 0; },
db.sortedSetAdd('cid:' + cid + ':tids:posts', topic.postcount, tid, next); function (next) {
} topic.postcount = topic.postcount || 0;
], next); db.sortedSetAdd('cid:' + cid + ':tids:posts', topic.postcount, tid, next);
}
], next);
}
} }
], function (err) { ], function (err) {
if (err) { if (err) {

@ -1,6 +1,6 @@
"use strict"; "use strict";
/* globals define, console, require */ /* globals console, require */
var db = require('./database'), var db = require('./database'),
async = require('async'), async = require('async'),
@ -1016,7 +1016,49 @@ Upgrade.upgrade = function (callback) {
winston.info('[2016/11/22] Update global and user language keys - skipped!'); winston.info('[2016/11/22] Update global and user language keys - skipped!');
next(); 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 // Add new schema updates here
// IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!! // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema IN LINE 24!!!
], function (err) { ], function (err) {

@ -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();
});
});
});
});
}); });

Loading…
Cancel
Save