mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-11-06 14:05:46 +01:00
store pinned topics in new zset
keep pinned topics on top on different sort types
This commit is contained in:
@@ -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) {
|
||||||
|
async.waterfall([
|
||||||
|
function (next) {
|
||||||
batch.processSortedSet('cid:' + cid + ':tids', function (tids, next) {
|
batch.processSortedSet('cid:' + cid + ':tids', function (tids, next) {
|
||||||
async.eachLimit(tids, 10, function (tid, next) {
|
async.eachLimit(tids, 10, function (tid, next) {
|
||||||
topics.purgePostsAndTopic(tid, uid, next);
|
topics.purgePostsAndTopic(tid, uid, next);
|
||||||
}, next);
|
}, next);
|
||||||
}, {alwaysStartAt: 0}, function (err) {
|
}, {alwaysStartAt: 0}, next);
|
||||||
if (err) {
|
},
|
||||||
return callback(err);
|
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) {
|
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,14 +227,17 @@ 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)) {
|
||||||
|
db.sortedSetAdd('cid:' + cid + ':tids:pinned', Date.now(), tid, next);
|
||||||
|
} else {
|
||||||
async.parallel([
|
async.parallel([
|
||||||
function (next) {
|
function (next) {
|
||||||
db.sortedSetAdd('cid:' + cid + ':tids', timestamp, tid, next);
|
db.sortedSetAdd('cid:' + cid + ':tids', topic.lastposttime, tid, next);
|
||||||
},
|
},
|
||||||
function (next) {
|
function (next) {
|
||||||
topic.postcount = topic.postcount || 0;
|
topic.postcount = topic.postcount || 0;
|
||||||
@@ -228,6 +245,7 @@ module.exports = function (Topics) {
|
|||||||
}
|
}
|
||||||
], next);
|
], next);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
], function (err) {
|
], function (err) {
|
||||||
if (err) {
|
if (err) {
|
||||||
return callback(err);
|
return callback(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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user