mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-26 16:46:12 +01:00
feat: #7743 categories
This commit is contained in:
@@ -1,28 +1,17 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var _ = require('lodash');
|
||||
const _ = require('lodash');
|
||||
|
||||
var posts = require('../posts');
|
||||
var db = require('../database');
|
||||
const posts = require('../posts');
|
||||
const db = require('../database');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.getActiveUsers = function (cids, callback) {
|
||||
Categories.getActiveUsers = async function (cids) {
|
||||
if (!Array.isArray(cids)) {
|
||||
cids = [cids];
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRevRange(cids.map(cid => 'cid:' + cid + ':pids'), 0, 24, next);
|
||||
},
|
||||
function (pids, next) {
|
||||
posts.getPostsFields(pids, ['uid'], next);
|
||||
},
|
||||
function (posts, next) {
|
||||
var uids = _.uniq(posts.map(post => post.uid).filter(uid => uid));
|
||||
|
||||
next(null, uids);
|
||||
},
|
||||
], callback);
|
||||
const pids = await db.getSortedSetRevRange(cids.map(cid => 'cid:' + cid + ':pids'), 0, 24);
|
||||
const postData = await posts.getPostsFields(pids, ['uid']);
|
||||
return _.uniq(postData.map(post => post.uid).filter(uid => uid));
|
||||
};
|
||||
};
|
||||
|
||||
@@ -11,125 +11,102 @@ var utils = require('../utils');
|
||||
var cache = require('../cache');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.create = function (data, callback) {
|
||||
var category;
|
||||
var parentCid = data.parentCid ? data.parentCid : 0;
|
||||
Categories.create = async function (data) {
|
||||
const parentCid = data.parentCid ? data.parentCid : 0;
|
||||
const cid = await db.incrObjectField('global', 'nextCid');
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.incrObjectField('global', 'nextCid', next);
|
||||
},
|
||||
function (cid, next) {
|
||||
data.name = data.name || 'Category ' + cid;
|
||||
var slug = cid + '/' + utils.slugify(data.name);
|
||||
var order = data.order || cid; // If no order provided, place it at the end
|
||||
var colours = Categories.assignColours();
|
||||
data.name = data.name || 'Category ' + cid;
|
||||
const slug = cid + '/' + utils.slugify(data.name);
|
||||
const order = data.order || cid; // If no order provided, place it at the end
|
||||
const colours = Categories.assignColours();
|
||||
|
||||
category = {
|
||||
cid: cid,
|
||||
name: data.name,
|
||||
description: data.description ? data.description : '',
|
||||
descriptionParsed: data.descriptionParsed ? data.descriptionParsed : '',
|
||||
icon: data.icon ? data.icon : '',
|
||||
bgColor: data.bgColor || colours[0],
|
||||
color: data.color || colours[1],
|
||||
slug: slug,
|
||||
parentCid: parentCid,
|
||||
topic_count: 0,
|
||||
post_count: 0,
|
||||
disabled: data.disabled ? 1 : 0,
|
||||
order: order,
|
||||
link: data.link || '',
|
||||
numRecentReplies: 1,
|
||||
class: (data.class ? data.class : 'col-md-3 col-xs-6'),
|
||||
imageClass: 'cover',
|
||||
isSection: 0,
|
||||
};
|
||||
let category = {
|
||||
cid: cid,
|
||||
name: data.name,
|
||||
description: data.description ? data.description : '',
|
||||
descriptionParsed: data.descriptionParsed ? data.descriptionParsed : '',
|
||||
icon: data.icon ? data.icon : '',
|
||||
bgColor: data.bgColor || colours[0],
|
||||
color: data.color || colours[1],
|
||||
slug: slug,
|
||||
parentCid: parentCid,
|
||||
topic_count: 0,
|
||||
post_count: 0,
|
||||
disabled: data.disabled ? 1 : 0,
|
||||
order: order,
|
||||
link: data.link || '',
|
||||
numRecentReplies: 1,
|
||||
class: (data.class ? data.class : 'col-md-3 col-xs-6'),
|
||||
imageClass: 'cover',
|
||||
isSection: 0,
|
||||
};
|
||||
|
||||
if (data.backgroundImage) {
|
||||
category.backgroundImage = data.backgroundImage;
|
||||
}
|
||||
if (data.backgroundImage) {
|
||||
category.backgroundImage = data.backgroundImage;
|
||||
}
|
||||
|
||||
plugins.fireHook('filter:category.create', { category: category, data: data }, next);
|
||||
},
|
||||
function (data, next) {
|
||||
category = data.category;
|
||||
const result = await plugins.fireHook('filter:category.create', { category: category, data: data });
|
||||
category = result.category;
|
||||
|
||||
var defaultPrivileges = [
|
||||
'find',
|
||||
'read',
|
||||
'topics:read',
|
||||
'topics:create',
|
||||
'topics:reply',
|
||||
'topics:tag',
|
||||
'posts:edit',
|
||||
'posts:history',
|
||||
'posts:delete',
|
||||
'posts:upvote',
|
||||
'posts:downvote',
|
||||
'topics:delete',
|
||||
];
|
||||
const modPrivileges = defaultPrivileges.concat([
|
||||
'posts:view_deleted',
|
||||
'purge',
|
||||
]);
|
||||
const defaultPrivileges = [
|
||||
'find',
|
||||
'read',
|
||||
'topics:read',
|
||||
'topics:create',
|
||||
'topics:reply',
|
||||
'topics:tag',
|
||||
'posts:edit',
|
||||
'posts:history',
|
||||
'posts:delete',
|
||||
'posts:upvote',
|
||||
'posts:downvote',
|
||||
'topics:delete',
|
||||
];
|
||||
const modPrivileges = defaultPrivileges.concat([
|
||||
'posts:view_deleted',
|
||||
'purge',
|
||||
]);
|
||||
|
||||
async.series([
|
||||
async.apply(db.setObject, 'category:' + category.cid, category),
|
||||
function (next) {
|
||||
if (category.descriptionParsed) {
|
||||
return next();
|
||||
}
|
||||
Categories.parseDescription(category.cid, category.description, next);
|
||||
},
|
||||
async.apply(db.sortedSetsAdd, ['categories:cid', 'cid:' + parentCid + ':children'], category.order, category.cid),
|
||||
async.apply(privileges.categories.give, defaultPrivileges, category.cid, 'registered-users'),
|
||||
async.apply(privileges.categories.give, modPrivileges, category.cid, ['administrators', 'Global Moderators']),
|
||||
async.apply(privileges.categories.give, ['find', 'read', 'topics:read'], category.cid, ['guests', 'spiders']),
|
||||
], next);
|
||||
},
|
||||
function (results, next) {
|
||||
cache.del(['categories:cid', 'cid:' + parentCid + ':children']);
|
||||
if (data.cloneFromCid && parseInt(data.cloneFromCid, 10)) {
|
||||
return Categories.copySettingsFrom(data.cloneFromCid, category.cid, !data.parentCid, next);
|
||||
}
|
||||
await db.setObject('category:' + category.cid, category);
|
||||
if (!category.descriptionParsed) {
|
||||
await Categories.parseDescription(category.cid, category.description);
|
||||
}
|
||||
await db.sortedSetsAdd(['categories:cid', 'cid:' + parentCid + ':children'], category.order, category.cid);
|
||||
await privileges.categories.give(defaultPrivileges, category.cid, 'registered-users');
|
||||
await privileges.categories.give(modPrivileges, category.cid, ['administrators', 'Global Moderators']);
|
||||
await privileges.categories.give(['find', 'read', 'topics:read'], category.cid, ['guests', 'spiders']);
|
||||
|
||||
next(null, category);
|
||||
},
|
||||
function (_category, next) {
|
||||
category = _category;
|
||||
if (data.cloneChildren) {
|
||||
return duplicateCategoriesChildren(category.cid, data.cloneFromCid, data.uid, next);
|
||||
}
|
||||
cache.del(['categories:cid', 'cid:' + parentCid + ':children']);
|
||||
if (data.cloneFromCid && parseInt(data.cloneFromCid, 10)) {
|
||||
category = await Categories.copySettingsFrom(data.cloneFromCid, category.cid, !data.parentCid);
|
||||
}
|
||||
|
||||
next();
|
||||
},
|
||||
function (next) {
|
||||
plugins.fireHook('action:category.create', { category: category });
|
||||
next(null, category);
|
||||
},
|
||||
], callback);
|
||||
if (data.cloneChildren) {
|
||||
await duplicateCategoriesChildren(category.cid, data.cloneFromCid, data.uid);
|
||||
}
|
||||
|
||||
plugins.fireHook('action:category.create', { category: category });
|
||||
return category;
|
||||
};
|
||||
|
||||
function duplicateCategoriesChildren(parentCid, cid, uid, callback) {
|
||||
Categories.getChildren([cid], uid, function (err, children) {
|
||||
if (err || !children.length) {
|
||||
return callback(err);
|
||||
}
|
||||
async function duplicateCategoriesChildren(parentCid, cid, uid) {
|
||||
let children = await Categories.getChildren([cid], uid);
|
||||
if (!children.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
children = children[0];
|
||||
children = children[0];
|
||||
|
||||
children.forEach(function (child) {
|
||||
child.parentCid = parentCid;
|
||||
child.cloneFromCid = child.cid;
|
||||
child.cloneChildren = true;
|
||||
child.name = utils.decodeHTMLEntities(child.name);
|
||||
child.description = utils.decodeHTMLEntities(child.description);
|
||||
child.uid = uid;
|
||||
});
|
||||
|
||||
async.each(children, Categories.create, callback);
|
||||
children.forEach(function (child) {
|
||||
child.parentCid = parentCid;
|
||||
child.cloneFromCid = child.cid;
|
||||
child.cloneChildren = true;
|
||||
child.name = utils.decodeHTMLEntities(child.name);
|
||||
child.description = utils.decodeHTMLEntities(child.description);
|
||||
child.uid = uid;
|
||||
});
|
||||
|
||||
await async.each(children, Categories.create);
|
||||
}
|
||||
|
||||
Categories.assignColours = function () {
|
||||
@@ -140,136 +117,89 @@ module.exports = function (Categories) {
|
||||
return [backgrounds[index], text[index]];
|
||||
};
|
||||
|
||||
Categories.copySettingsFrom = function (fromCid, toCid, copyParent, callback) {
|
||||
var destination;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
source: async.apply(db.getObject, 'category:' + fromCid),
|
||||
destination: async.apply(db.getObject, 'category:' + toCid),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
if (!results.source) {
|
||||
return next(new Error('[[error:invalid-cid]]'));
|
||||
}
|
||||
destination = results.destination;
|
||||
|
||||
var tasks = [];
|
||||
|
||||
const oldParent = parseInt(destination.parentCid, 10) || 0;
|
||||
const newParent = parseInt(results.source.parentCid, 10) || 0;
|
||||
if (copyParent && newParent !== parseInt(toCid, 10)) {
|
||||
tasks.push(async.apply(db.sortedSetRemove, 'cid:' + oldParent + ':children', toCid));
|
||||
tasks.push(async.apply(db.sortedSetAdd, 'cid:' + newParent + ':children', results.source.order, toCid));
|
||||
tasks.push(function (next) {
|
||||
cache.del(['cid:' + oldParent + ':children', 'cid:' + newParent + ':children']);
|
||||
setImmediate(next);
|
||||
});
|
||||
}
|
||||
|
||||
destination.description = results.source.description;
|
||||
destination.descriptionParsed = results.source.descriptionParsed;
|
||||
destination.icon = results.source.icon;
|
||||
destination.bgColor = results.source.bgColor;
|
||||
destination.color = results.source.color;
|
||||
destination.link = results.source.link;
|
||||
destination.numRecentReplies = results.source.numRecentReplies;
|
||||
destination.class = results.source.class;
|
||||
destination.image = results.source.image;
|
||||
destination.imageClass = results.source.imageClass;
|
||||
|
||||
if (copyParent) {
|
||||
destination.parentCid = results.source.parentCid || 0;
|
||||
}
|
||||
|
||||
tasks.push(async.apply(db.setObject, 'category:' + toCid, destination));
|
||||
|
||||
async.series(tasks, next);
|
||||
},
|
||||
function (results, next) {
|
||||
copyTagWhitelist(fromCid, toCid, next);
|
||||
},
|
||||
function (next) {
|
||||
Categories.copyPrivilegesFrom(fromCid, toCid, next);
|
||||
},
|
||||
], function (err) {
|
||||
callback(err, destination);
|
||||
});
|
||||
};
|
||||
|
||||
function copyTagWhitelist(fromCid, toCid, callback) {
|
||||
var data;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRangeWithScores('cid:' + fromCid + ':tag:whitelist', 0, -1, next);
|
||||
},
|
||||
function (_data, next) {
|
||||
data = _data;
|
||||
db.delete('cid:' + toCid + ':tag:whitelist', next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + toCid + ':tag:whitelist', data.map(item => item.score), data.map(item => item.value), next);
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
Categories.copyPrivilegesFrom = function (fromCid, toCid, group, callback) {
|
||||
if (typeof group === 'function') {
|
||||
callback = group;
|
||||
group = '';
|
||||
Categories.copySettingsFrom = async function (fromCid, toCid, copyParent) {
|
||||
const [source, destination] = await Promise.all([
|
||||
db.getObject('category:' + fromCid),
|
||||
db.getObject('category:' + toCid),
|
||||
]);
|
||||
if (!source) {
|
||||
throw new Error('[[error:invalid-cid]]');
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
plugins.fireHook('filter:categories.copyPrivilegesFrom', {
|
||||
privileges: group ? privileges.groupPrivilegeList.slice() : privileges.privilegeList.slice(),
|
||||
fromCid: fromCid,
|
||||
toCid: toCid,
|
||||
group: group,
|
||||
}, next);
|
||||
},
|
||||
function (data, next) {
|
||||
if (group) {
|
||||
copyPrivilegesByGroup(data.privileges, data.fromCid, data.toCid, group, next);
|
||||
} else {
|
||||
copyPrivileges(data.privileges, data.fromCid, data.toCid, next);
|
||||
}
|
||||
},
|
||||
], callback);
|
||||
const oldParent = parseInt(destination.parentCid, 10) || 0;
|
||||
const newParent = parseInt(source.parentCid, 10) || 0;
|
||||
if (copyParent && newParent !== parseInt(toCid, 10)) {
|
||||
await db.sortedSetRemove('cid:' + oldParent + ':children', toCid);
|
||||
await db.sortedSetAdd('cid:' + newParent + ':children', source.order, toCid);
|
||||
cache.del(['cid:' + oldParent + ':children', 'cid:' + newParent + ':children']);
|
||||
}
|
||||
|
||||
destination.description = source.description;
|
||||
destination.descriptionParsed = source.descriptionParsed;
|
||||
destination.icon = source.icon;
|
||||
destination.bgColor = source.bgColor;
|
||||
destination.color = source.color;
|
||||
destination.link = source.link;
|
||||
destination.numRecentReplies = source.numRecentReplies;
|
||||
destination.class = source.class;
|
||||
destination.image = source.image;
|
||||
destination.imageClass = source.imageClass;
|
||||
|
||||
if (copyParent) {
|
||||
destination.parentCid = source.parentCid || 0;
|
||||
}
|
||||
|
||||
await db.setObject('category:' + toCid, destination);
|
||||
|
||||
await copyTagWhitelist(fromCid, toCid);
|
||||
|
||||
await Categories.copyPrivilegesFrom(fromCid, toCid);
|
||||
|
||||
return destination;
|
||||
};
|
||||
|
||||
function copyPrivileges(privileges, fromCid, toCid, callback) {
|
||||
const toGroups = privileges.map(privilege => 'group:cid:' + toCid + ':privileges:' + privilege + ':members');
|
||||
const fromGroups = privileges.map(privilege => 'group:cid:' + fromCid + ':privileges:' + privilege + ':members');
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetsMembers(toGroups.concat(fromGroups), next);
|
||||
},
|
||||
function (currentMembers, next) {
|
||||
const copyGroups = _.uniq(_.flatten(currentMembers));
|
||||
async.each(copyGroups, function (group, next) {
|
||||
copyPrivilegesByGroup(privileges, fromCid, toCid, group, next);
|
||||
}, next);
|
||||
},
|
||||
], callback);
|
||||
async function copyTagWhitelist(fromCid, toCid) {
|
||||
const data = await db.getSortedSetRangeWithScores('cid:' + fromCid + ':tag:whitelist', 0, -1);
|
||||
await db.delete('cid:' + toCid + ':tag:whitelist');
|
||||
await db.sortedSetAdd('cid:' + toCid + ':tag:whitelist', data.map(item => item.score), data.map(item => item.value));
|
||||
cache.del('cid:' + toCid + ':tag:whitelist');
|
||||
}
|
||||
|
||||
function copyPrivilegesByGroup(privileges, fromCid, toCid, group, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
const leaveGroups = privileges.map(privilege => 'cid:' + toCid + ':privileges:' + privilege);
|
||||
groups.leave(leaveGroups, group, next);
|
||||
},
|
||||
function (next) {
|
||||
const checkGroups = privileges.map(privilege => 'group:cid:' + fromCid + ':privileges:' + privilege + ':members');
|
||||
db.isMemberOfSortedSets(checkGroups, group, next);
|
||||
},
|
||||
function (isMembers, next) {
|
||||
privileges = privileges.filter((priv, index) => isMembers[index]);
|
||||
const joinGroups = privileges.map(privilege => 'cid:' + toCid + ':privileges:' + privilege);
|
||||
groups.join(joinGroups, group, next);
|
||||
},
|
||||
], callback);
|
||||
Categories.copyPrivilegesFrom = async function (fromCid, toCid, group) {
|
||||
group = group || '';
|
||||
|
||||
const data = await plugins.fireHook('filter:categories.copyPrivilegesFrom', {
|
||||
privileges: group ? privileges.groupPrivilegeList.slice() : privileges.privilegeList.slice(),
|
||||
fromCid: fromCid,
|
||||
toCid: toCid,
|
||||
group: group,
|
||||
});
|
||||
if (group) {
|
||||
await copyPrivilegesByGroup(data.privileges, data.fromCid, data.toCid, group);
|
||||
} else {
|
||||
await copyPrivileges(data.privileges, data.fromCid, data.toCid);
|
||||
}
|
||||
};
|
||||
|
||||
async function copyPrivileges(privileges, fromCid, toCid) {
|
||||
const toGroups = privileges.map(privilege => 'group:cid:' + toCid + ':privileges:' + privilege + ':members');
|
||||
const fromGroups = privileges.map(privilege => 'group:cid:' + fromCid + ':privileges:' + privilege + ':members');
|
||||
|
||||
const currentMembers = await db.getSortedSetsMembers(toGroups.concat(fromGroups));
|
||||
const copyGroups = _.uniq(_.flatten(currentMembers));
|
||||
await async.each(copyGroups, async function (group) {
|
||||
await copyPrivilegesByGroup(privileges, fromCid, toCid, group);
|
||||
});
|
||||
}
|
||||
|
||||
async function copyPrivilegesByGroup(privileges, fromCid, toCid, group) {
|
||||
const leaveGroups = privileges.map(privilege => 'cid:' + toCid + ':privileges:' + privilege);
|
||||
await groups.leave(leaveGroups, group);
|
||||
|
||||
const checkGroups = privileges.map(privilege => 'group:cid:' + fromCid + ':privileges:' + privilege + ':members');
|
||||
const isMembers = await db.isMemberOfSortedSets(checkGroups, group);
|
||||
privileges = privileges.filter((priv, index) => isMembers[index]);
|
||||
const joinGroups = privileges.map(privilege => 'cid:' + toCid + ':privileges:' + privilege);
|
||||
await groups.join(joinGroups, group);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var validator = require('validator');
|
||||
|
||||
var db = require('../database');
|
||||
@@ -11,64 +10,51 @@ const intFields = [
|
||||
];
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.getCategoriesFields = function (cids, fields, callback) {
|
||||
Categories.getCategoriesFields = async function (cids, fields) {
|
||||
if (!Array.isArray(cids) || !cids.length) {
|
||||
return setImmediate(callback, null, []);
|
||||
return [];
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
const keys = cids.map(cid => 'category:' + cid);
|
||||
if (fields.length) {
|
||||
db.getObjectsFields(keys, fields, next);
|
||||
} else {
|
||||
db.getObjects(keys, next);
|
||||
}
|
||||
},
|
||||
function (categories, next) {
|
||||
categories.forEach(category => modifyCategory(category, fields));
|
||||
next(null, categories);
|
||||
},
|
||||
], callback);
|
||||
let categories;
|
||||
const keys = cids.map(cid => 'category:' + cid);
|
||||
if (fields.length) {
|
||||
categories = await db.getObjectsFields(keys, fields);
|
||||
} else {
|
||||
categories = await db.getObjects(keys);
|
||||
}
|
||||
categories.forEach(category => modifyCategory(category, fields));
|
||||
return categories;
|
||||
};
|
||||
|
||||
Categories.getCategoryData = function (cid, callback) {
|
||||
Categories.getCategoriesFields([cid], [], function (err, categories) {
|
||||
callback(err, categories && categories.length ? categories[0] : null);
|
||||
});
|
||||
Categories.getCategoryData = async function (cid) {
|
||||
const categories = await Categories.getCategoriesFields([cid], []);
|
||||
return categories && categories.length ? categories[0] : null;
|
||||
};
|
||||
|
||||
Categories.getCategoriesData = function (cids, callback) {
|
||||
Categories.getCategoriesFields(cids, [], callback);
|
||||
Categories.getCategoriesData = async function (cids) {
|
||||
return await Categories.getCategoriesFields(cids, []);
|
||||
};
|
||||
|
||||
Categories.getCategoryField = function (cid, field, callback) {
|
||||
Categories.getCategoryFields(cid, [field], function (err, category) {
|
||||
callback(err, category ? category[field] : null);
|
||||
});
|
||||
Categories.getCategoryField = async function (cid, field) {
|
||||
const category = await Categories.getCategoryFields(cid, [field]);
|
||||
return category ? category[field] : null;
|
||||
};
|
||||
|
||||
Categories.getCategoryFields = function (cid, fields, callback) {
|
||||
Categories.getCategoriesFields([cid], fields, function (err, categories) {
|
||||
callback(err, categories ? categories[0] : null);
|
||||
});
|
||||
Categories.getCategoryFields = async function (cid, fields) {
|
||||
const categories = await Categories.getCategoriesFields([cid], fields);
|
||||
return categories ? categories[0] : null;
|
||||
};
|
||||
|
||||
Categories.getAllCategoryFields = function (fields, callback) {
|
||||
async.waterfall([
|
||||
async.apply(Categories.getAllCidsFromSet, 'categories:cid'),
|
||||
function (cids, next) {
|
||||
Categories.getCategoriesFields(cids, fields, next);
|
||||
},
|
||||
], callback);
|
||||
Categories.getAllCategoryFields = async function (fields) {
|
||||
const cids = await Categories.getAllCidsFromSet('categories:cid');
|
||||
return await Categories.getCategoriesFields(cids, fields);
|
||||
};
|
||||
|
||||
Categories.setCategoryField = function (cid, field, value, callback) {
|
||||
db.setObjectField('category:' + cid, field, value, callback);
|
||||
Categories.setCategoryField = async function (cid, field, value) {
|
||||
await db.setObjectField('category:' + cid, field, value);
|
||||
};
|
||||
|
||||
Categories.incrementCategoryFieldBy = function (cid, field, value, callback) {
|
||||
db.incrObjectFieldBy('category:' + cid, field, value, callback);
|
||||
Categories.incrementCategoryFieldBy = async function (cid, field, value) {
|
||||
await db.incrObjectFieldBy('category:' + cid, field, value);
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
@@ -10,107 +10,62 @@ var privileges = require('../privileges');
|
||||
var cache = require('../cache');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.purge = function (cid, uid, callback) {
|
||||
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) {
|
||||
db.getSortedSetRevRange('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: cid, uid: uid });
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
Categories.purge = async function (cid, uid) {
|
||||
await batch.processSortedSet('cid:' + cid + ':tids', async function (tids) {
|
||||
await async.eachLimit(tids, 10, async function (tid) {
|
||||
await topics.purgePostsAndTopic(tid, uid);
|
||||
});
|
||||
}, { alwaysStartAt: 0 });
|
||||
|
||||
const pinnedTids = await db.getSortedSetRevRange('cid:' + cid + ':tids:pinned', 0, -1);
|
||||
await async.eachLimit(pinnedTids, 10, async function (tid) {
|
||||
await topics.purgePostsAndTopic(tid, uid);
|
||||
});
|
||||
await purgeCategory(cid);
|
||||
plugins.fireHook('action:category.delete', { cid: cid, uid: uid });
|
||||
};
|
||||
|
||||
function purgeCategory(cid, callback) {
|
||||
async.series([
|
||||
function (next) {
|
||||
db.sortedSetRemove('categories:cid', cid, next);
|
||||
},
|
||||
function (next) {
|
||||
removeFromParent(cid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.deleteAll([
|
||||
'cid:' + cid + ':tids',
|
||||
'cid:' + cid + ':tids:pinned',
|
||||
'cid:' + cid + ':tids:posts',
|
||||
'cid:' + cid + ':pids',
|
||||
'cid:' + cid + ':read_by_uid',
|
||||
'cid:' + cid + ':uid:watch:state',
|
||||
'cid:' + cid + ':children',
|
||||
'cid:' + cid + ':tag:whitelist',
|
||||
'category:' + cid,
|
||||
], next);
|
||||
},
|
||||
function (next) {
|
||||
groups.destroy(privileges.privilegeList.map(privilege => 'cid:' + cid + ':privileges:' + privilege), next);
|
||||
},
|
||||
], function (err) {
|
||||
callback(err);
|
||||
});
|
||||
async function purgeCategory(cid) {
|
||||
await db.sortedSetRemove('categories:cid', cid);
|
||||
await removeFromParent(cid);
|
||||
await db.deleteAll([
|
||||
'cid:' + cid + ':tids',
|
||||
'cid:' + cid + ':tids:pinned',
|
||||
'cid:' + cid + ':tids:posts',
|
||||
'cid:' + cid + ':pids',
|
||||
'cid:' + cid + ':read_by_uid',
|
||||
'cid:' + cid + ':uid:watch:state',
|
||||
'cid:' + cid + ':children',
|
||||
'cid:' + cid + ':tag:whitelist',
|
||||
'category:' + cid,
|
||||
]);
|
||||
await groups.destroy(privileges.privilegeList.map(privilege => 'cid:' + cid + ':privileges:' + privilege));
|
||||
}
|
||||
|
||||
function removeFromParent(cid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
parentCid: function (next) {
|
||||
Categories.getCategoryField(cid, 'parentCid', next);
|
||||
},
|
||||
children: function (next) {
|
||||
db.getSortedSetRange('cid:' + cid + ':children', 0, -1, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
async.parallel([
|
||||
function (next) {
|
||||
db.sortedSetRemove('cid:' + results.parentCid + ':children', cid, next);
|
||||
},
|
||||
function (next) {
|
||||
async.each(results.children, function (cid, next) {
|
||||
async.parallel([
|
||||
function (next) {
|
||||
db.setObjectField('category:' + cid, 'parentCid', 0, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:0:children', cid, cid, next);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
], function (err) {
|
||||
if (err) {
|
||||
return next(err);
|
||||
}
|
||||
cache.del([
|
||||
'categories:cid',
|
||||
'cid:0:children',
|
||||
'cid:' + results.parentCid + ':children',
|
||||
'cid:' + cid + ':children',
|
||||
'cid:' + cid + ':tag:whitelist',
|
||||
]);
|
||||
next();
|
||||
});
|
||||
},
|
||||
], function (err) {
|
||||
callback(err);
|
||||
async function removeFromParent(cid) {
|
||||
const [parentCid, children] = await Promise.all([
|
||||
Categories.getCategoryField(cid, 'parentCid'),
|
||||
db.getSortedSetRange('cid:' + cid + ':children', 0, -1),
|
||||
]);
|
||||
|
||||
const bulkAdd = [];
|
||||
const childrenKeys = children.map(function (cid) {
|
||||
bulkAdd.push(['cid:0:children', cid, cid]);
|
||||
return 'category:' + cid;
|
||||
});
|
||||
|
||||
await Promise.all([
|
||||
db.sortedSetRemove('cid:' + parentCid + ':children', cid),
|
||||
db.setObjectField(childrenKeys, 'parentCid', 0),
|
||||
db.sortedSetAddBulk(bulkAdd),
|
||||
]);
|
||||
|
||||
cache.del([
|
||||
'categories:cid',
|
||||
'cid:0:children',
|
||||
'cid:' + parentCid + ':children',
|
||||
'cid:' + cid + ':children',
|
||||
'cid:' + cid + ':tag:whitelist',
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var _ = require('lodash');
|
||||
|
||||
var db = require('../database');
|
||||
@@ -23,195 +22,128 @@ require('./recentreplies')(Categories);
|
||||
require('./update')(Categories);
|
||||
require('./watch')(Categories);
|
||||
|
||||
Categories.exists = function (cid, callback) {
|
||||
db.exists('category:' + cid, callback);
|
||||
Categories.exists = async function (cid) {
|
||||
return await db.exists('category:' + cid);
|
||||
};
|
||||
|
||||
Categories.getCategoryById = function (data, callback) {
|
||||
var category;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getCategories([data.cid], data.uid, next);
|
||||
},
|
||||
function (categories, next) {
|
||||
if (!categories[0]) {
|
||||
return callback(null, null);
|
||||
}
|
||||
category = categories[0];
|
||||
data.category = category;
|
||||
async.parallel({
|
||||
topics: function (next) {
|
||||
Categories.getCategoryTopics(data, next);
|
||||
},
|
||||
topicCount: function (next) {
|
||||
Categories.getTopicCount(data, next);
|
||||
},
|
||||
watchState: function (next) {
|
||||
Categories.getWatchState([data.cid], data.uid, next);
|
||||
},
|
||||
parent: function (next) {
|
||||
if (category.parentCid) {
|
||||
Categories.getCategoryData(category.parentCid, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
children: function (next) {
|
||||
getChildrenTree(category, data.uid, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
category.topics = results.topics.topics;
|
||||
category.nextStart = results.topics.nextStart;
|
||||
category.topic_count = results.topicCount;
|
||||
category.isWatched = results.watchState[0] === Categories.watchStates.watching;
|
||||
category.isNotWatched = results.watchState[0] === Categories.watchStates.notwatching;
|
||||
category.isIgnored = results.watchState[0] === Categories.watchStates.ignoring;
|
||||
category.parent = results.parent;
|
||||
Categories.getCategoryById = async function (data) {
|
||||
const categories = await Categories.getCategories([data.cid], data.uid);
|
||||
if (!categories[0]) {
|
||||
return null;
|
||||
}
|
||||
const category = categories[0];
|
||||
data.category = category;
|
||||
|
||||
calculateTopicPostCount(category);
|
||||
plugins.fireHook('filter:category.get', { category: category, uid: data.uid }, next);
|
||||
},
|
||||
function (data, next) {
|
||||
next(null, data.category);
|
||||
},
|
||||
], callback);
|
||||
const promises = [
|
||||
Categories.getCategoryTopics(data),
|
||||
Categories.getTopicCount(data),
|
||||
Categories.getWatchState([data.cid], data.uid),
|
||||
getChildrenTree(category, data.uid),
|
||||
];
|
||||
|
||||
if (category.parentCid) {
|
||||
promises.push(Categories.getCategoryData(category.parentCid));
|
||||
}
|
||||
const [topics, topicCount, watchState, , parent] = await Promise.all(promises);
|
||||
|
||||
category.topics = topics.topics;
|
||||
category.nextStart = topics.nextStart;
|
||||
category.topic_count = topicCount;
|
||||
category.isWatched = watchState[0] === Categories.watchStates.watching;
|
||||
category.isNotWatched = watchState[0] === Categories.watchStates.notwatching;
|
||||
category.isIgnored = watchState[0] === Categories.watchStates.ignoring;
|
||||
category.parent = parent;
|
||||
|
||||
|
||||
calculateTopicPostCount(category);
|
||||
const result = await plugins.fireHook('filter:category.get', { category: category, uid: data.uid });
|
||||
return result.category;
|
||||
};
|
||||
|
||||
Categories.getAllCidsFromSet = function (key, callback) {
|
||||
const cids = cache.get(key);
|
||||
Categories.getAllCidsFromSet = async function (key) {
|
||||
let cids = cache.get(key);
|
||||
if (cids) {
|
||||
return setImmediate(callback, null, cids.slice());
|
||||
return cids.slice();
|
||||
}
|
||||
|
||||
db.getSortedSetRange(key, 0, -1, function (err, cids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
cids = await db.getSortedSetRange(key, 0, -1);
|
||||
cache.set(key, cids);
|
||||
return cids.slice();
|
||||
};
|
||||
|
||||
Categories.getAllCategories = async function (uid) {
|
||||
const cids = await Categories.getAllCidsFromSet('categories:cid');
|
||||
return await Categories.getCategories(cids, uid);
|
||||
};
|
||||
|
||||
Categories.getCidsByPrivilege = async function (set, uid, privilege) {
|
||||
const cids = await Categories.getAllCidsFromSet('categories:cid');
|
||||
return await privileges.categories.filterCids(privilege, cids, uid);
|
||||
};
|
||||
|
||||
Categories.getCategoriesByPrivilege = async function (set, uid, privilege) {
|
||||
const cids = await Categories.getCidsByPrivilege(set, uid, privilege);
|
||||
return await Categories.getCategories(cids, uid);
|
||||
};
|
||||
|
||||
Categories.getModerators = async function (cid) {
|
||||
const uids = await Categories.getModeratorUids([cid]);
|
||||
return await user.getUsersFields(uids[0], ['uid', 'username', 'userslug', 'picture']);
|
||||
};
|
||||
|
||||
Categories.getModeratorUids = async function (cids) {
|
||||
const groupNames = cids.reduce(function (memo, cid) {
|
||||
memo.push('cid:' + cid + ':privileges:moderate');
|
||||
memo.push('cid:' + cid + ':privileges:groups:moderate');
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
const memberSets = await groups.getMembersOfGroups(groupNames);
|
||||
// Every other set is actually a list of user groups, not uids, so convert those to members
|
||||
const sets = memberSets.reduce(function (memo, set, idx) {
|
||||
if (idx % 2) {
|
||||
memo.groupNames.push(set);
|
||||
} else {
|
||||
memo.uids.push(set);
|
||||
}
|
||||
cache.set(key, cids);
|
||||
callback(null, cids.slice());
|
||||
|
||||
return memo;
|
||||
}, { groupNames: [], uids: [] });
|
||||
|
||||
const uniqGroups = _.uniq(_.flatten(sets.groupNames));
|
||||
const groupUids = await groups.getMembersOfGroups(uniqGroups);
|
||||
const map = _.zipObject(uniqGroups, groupUids);
|
||||
const moderatorUids = cids.map(function (cid, index) {
|
||||
return _.uniq(sets.uids[index].concat(_.flatten(sets.groupNames[index].map(g => map[g]))));
|
||||
});
|
||||
return moderatorUids;
|
||||
};
|
||||
|
||||
Categories.getAllCategories = function (uid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getAllCidsFromSet('categories:cid', next);
|
||||
},
|
||||
function (cids, next) {
|
||||
Categories.getCategories(cids, uid, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getCidsByPrivilege = function (set, uid, privilege, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getAllCidsFromSet(set, next);
|
||||
},
|
||||
function (cids, next) {
|
||||
privileges.categories.filterCids(privilege, cids, uid, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getCategoriesByPrivilege = function (set, uid, privilege, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getCidsByPrivilege(set, uid, privilege, next);
|
||||
},
|
||||
function (cids, next) {
|
||||
Categories.getCategories(cids, uid, next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getModerators = function (cid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getModeratorUids([cid], next);
|
||||
},
|
||||
function (uids, next) {
|
||||
user.getUsersFields(uids[0], ['uid', 'username', 'userslug', 'picture'], next);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getModeratorUids = function (cids, callback) {
|
||||
var sets;
|
||||
var uniqGroups;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
var groupNames = cids.reduce(function (memo, cid) {
|
||||
memo.push('cid:' + cid + ':privileges:moderate');
|
||||
memo.push('cid:' + cid + ':privileges:groups:moderate');
|
||||
return memo;
|
||||
}, []);
|
||||
|
||||
groups.getMembersOfGroups(groupNames, next);
|
||||
},
|
||||
function (memberSets, next) {
|
||||
// Every other set is actually a list of user groups, not uids, so convert those to members
|
||||
sets = memberSets.reduce(function (memo, set, idx) {
|
||||
if (idx % 2) {
|
||||
memo.groupNames.push(set);
|
||||
} else {
|
||||
memo.uids.push(set);
|
||||
}
|
||||
|
||||
return memo;
|
||||
}, { groupNames: [], uids: [] });
|
||||
|
||||
uniqGroups = _.uniq(_.flatten(sets.groupNames));
|
||||
groups.getMembersOfGroups(uniqGroups, next);
|
||||
},
|
||||
function (groupUids, next) {
|
||||
var map = _.zipObject(uniqGroups, groupUids);
|
||||
const moderatorUids = cids.map(function (cid, index) {
|
||||
return _.uniq(sets.uids[index].concat(_.flatten(sets.groupNames[index].map(g => map[g]))));
|
||||
});
|
||||
next(null, moderatorUids);
|
||||
},
|
||||
], callback);
|
||||
};
|
||||
|
||||
Categories.getCategories = function (cids, uid, callback) {
|
||||
Categories.getCategories = async function (cids, uid) {
|
||||
if (!Array.isArray(cids)) {
|
||||
return callback(new Error('[[error:invalid-cid]]'));
|
||||
throw new Error('[[error:invalid-cid]]');
|
||||
}
|
||||
|
||||
if (!cids.length) {
|
||||
return callback(null, []);
|
||||
return [];
|
||||
}
|
||||
uid = parseInt(uid, 10);
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
categories: function (next) {
|
||||
Categories.getCategoriesData(cids, next);
|
||||
},
|
||||
tagWhitelist: function (next) {
|
||||
Categories.getTagWhitelist(cids, next);
|
||||
},
|
||||
hasRead: function (next) {
|
||||
Categories.hasReadCategories(cids, uid, next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
results.categories.forEach(function (category, i) {
|
||||
if (category) {
|
||||
category.tagWhitelist = results.tagWhitelist[i];
|
||||
category['unread-class'] = (category.topic_count === 0 || (results.hasRead[i] && uid !== 0)) ? '' : 'unread';
|
||||
}
|
||||
});
|
||||
next(null, results.categories);
|
||||
},
|
||||
], callback);
|
||||
|
||||
const [categories, tagWhitelist, hasRead] = await Promise.all([
|
||||
Categories.getCategoriesData(cids),
|
||||
Categories.getTagWhitelist(cids),
|
||||
Categories.hasReadCategories(cids, uid),
|
||||
]);
|
||||
categories.forEach(function (category, i) {
|
||||
if (category) {
|
||||
category.tagWhitelist = tagWhitelist[i];
|
||||
category['unread-class'] = (category.topic_count === 0 || (hasRead[i] && uid !== 0)) ? '' : 'unread';
|
||||
}
|
||||
});
|
||||
return categories;
|
||||
};
|
||||
|
||||
Categories.getTagWhitelist = function (cids, callback) {
|
||||
Categories.getTagWhitelist = async function (cids) {
|
||||
const cachedData = {};
|
||||
|
||||
const nonCachedCids = cids.filter((cid) => {
|
||||
@@ -224,20 +156,17 @@ Categories.getTagWhitelist = function (cids, callback) {
|
||||
});
|
||||
|
||||
if (!nonCachedCids.length) {
|
||||
return setImmediate(callback, null, _.clone(cids.map(cid => cachedData[cid])));
|
||||
return _.clone(cids.map(cid => cachedData[cid]));
|
||||
}
|
||||
|
||||
const keys = nonCachedCids.map(cid => 'cid:' + cid + ':tag:whitelist');
|
||||
db.getSortedSetsMembers(keys, function (err, data) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
nonCachedCids.forEach((cid, index) => {
|
||||
cachedData[cid] = data[index];
|
||||
cache.set('cid:' + cid + ':tag:whitelist', data[index]);
|
||||
});
|
||||
callback(null, _.clone(cids.map(cid => cachedData[cid])));
|
||||
const data = await db.getSortedSetsMembers(keys);
|
||||
|
||||
nonCachedCids.forEach((cid, index) => {
|
||||
cachedData[cid] = data[index];
|
||||
cache.set('cid:' + cid + ':tag:whitelist', data[index]);
|
||||
});
|
||||
return _.clone(cids.map(cid => cachedData[cid]));
|
||||
};
|
||||
|
||||
function calculateTopicPostCount(category) {
|
||||
@@ -263,114 +192,65 @@ function calculateTopicPostCount(category) {
|
||||
category.totalTopicCount = topicCount;
|
||||
}
|
||||
|
||||
Categories.getParents = function (cids, callback) {
|
||||
var categoriesData;
|
||||
var parentCids;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getCategoriesFields(cids, ['parentCid'], next);
|
||||
},
|
||||
function (_categoriesData, next) {
|
||||
categoriesData = _categoriesData;
|
||||
|
||||
parentCids = categoriesData.filter(c => c && c.parentCid).map(c => c.parentCid);
|
||||
|
||||
if (!parentCids.length) {
|
||||
return callback(null, cids.map(() => null));
|
||||
}
|
||||
|
||||
Categories.getCategoriesData(parentCids, next);
|
||||
},
|
||||
function (parentData, next) {
|
||||
const cidToParent = _.zipObject(parentCids, parentData);
|
||||
parentData = categoriesData.map(category => cidToParent[category.parentCid]);
|
||||
next(null, parentData);
|
||||
},
|
||||
], callback);
|
||||
Categories.getParents = async function (cids) {
|
||||
const categoriesData = await Categories.getCategoriesFields(cids, ['parentCid']);
|
||||
const parentCids = categoriesData.filter(c => c && c.parentCid).map(c => c.parentCid);
|
||||
if (!parentCids.length) {
|
||||
return cids.map(() => null);
|
||||
}
|
||||
const parentData = await Categories.getCategoriesData(parentCids);
|
||||
const cidToParent = _.zipObject(parentCids, parentData);
|
||||
return categoriesData.map(category => cidToParent[category.parentCid]);
|
||||
};
|
||||
|
||||
Categories.getChildren = function (cids, uid, callback) {
|
||||
var categories;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getCategoriesFields(cids, ['parentCid'], next);
|
||||
},
|
||||
function (categoryData, next) {
|
||||
categories = categoryData.map((category, index) => ({ cid: cids[index], parentCid: category.parentCid }));
|
||||
async.each(categories, function (category, next) {
|
||||
getChildrenTree(category, uid, next);
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
next(null, categories.map(c => c && c.children));
|
||||
},
|
||||
], callback);
|
||||
Categories.getChildren = async function (cids, uid) {
|
||||
const categoryData = await Categories.getCategoriesFields(cids, ['parentCid']);
|
||||
const categories = categoryData.map((category, index) => ({ cid: cids[index], parentCid: category.parentCid }));
|
||||
await Promise.all(categories.map(c => getChildrenTree(c, uid)));
|
||||
return categories.map(c => c && c.children);
|
||||
};
|
||||
|
||||
function getChildrenTree(category, uid, callback) {
|
||||
let children;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getChildrenCids(category.cid, next);
|
||||
},
|
||||
function (children, next) {
|
||||
privileges.categories.filterCids('find', children, uid, next);
|
||||
},
|
||||
function (children, next) {
|
||||
children = children.filter(cid => parseInt(category.cid, 10) !== parseInt(cid, 10));
|
||||
if (!children.length) {
|
||||
category.children = [];
|
||||
return callback();
|
||||
}
|
||||
Categories.getCategoriesData(children, next);
|
||||
},
|
||||
function (_children, next) {
|
||||
children = _children.filter(Boolean);
|
||||
|
||||
const cids = children.map(child => child.cid);
|
||||
Categories.hasReadCategories(cids, uid, next);
|
||||
},
|
||||
function (hasRead, next) {
|
||||
hasRead.forEach(function (read, i) {
|
||||
const child = children[i];
|
||||
child['unread-class'] = (child.topic_count === 0 || (read && uid !== 0)) ? '' : 'unread';
|
||||
});
|
||||
Categories.getTree([category].concat(children), category.parentCid);
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
async function getChildrenTree(category, uid) {
|
||||
let childrenCids = await Categories.getChildrenCids(category.cid);
|
||||
childrenCids = await privileges.categories.filterCids('find', childrenCids, uid);
|
||||
childrenCids = childrenCids.filter(cid => parseInt(category.cid, 10) !== parseInt(cid, 10));
|
||||
if (!childrenCids.length) {
|
||||
category.children = [];
|
||||
return;
|
||||
}
|
||||
let childrenData = await Categories.getCategoriesData(childrenCids);
|
||||
childrenData = childrenData.filter(Boolean);
|
||||
childrenCids = childrenData.map(child => child.cid);
|
||||
const hasRead = await Categories.hasReadCategories(childrenCids, uid);
|
||||
childrenData.forEach(function (child, i) {
|
||||
child['unread-class'] = (child.topic_count === 0 || (hasRead[i] && uid !== 0)) ? '' : 'unread';
|
||||
});
|
||||
Categories.getTree([category].concat(childrenData), category.parentCid);
|
||||
}
|
||||
|
||||
Categories.getChildrenCids = function (rootCid, callback) {
|
||||
Categories.getChildrenCids = async function (rootCid) {
|
||||
let allCids = [];
|
||||
function recursive(keys, callback) {
|
||||
db.getSortedSetRange(keys, 0, -1, function (err, childrenCids) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
childrenCids = childrenCids.filter(cid => !allCids.includes(parseInt(cid, 10)));
|
||||
if (!childrenCids.length) {
|
||||
return callback();
|
||||
}
|
||||
const keys = childrenCids.map(cid => 'cid:' + cid + ':children');
|
||||
childrenCids.forEach(cid => allCids.push(parseInt(cid, 10)));
|
||||
recursive(keys, callback);
|
||||
});
|
||||
async function recursive(keys) {
|
||||
let childrenCids = await db.getSortedSetRange(keys, 0, -1);
|
||||
|
||||
childrenCids = childrenCids.filter(cid => !allCids.includes(parseInt(cid, 10)));
|
||||
if (!childrenCids.length) {
|
||||
return;
|
||||
}
|
||||
keys = childrenCids.map(cid => 'cid:' + cid + ':children');
|
||||
childrenCids.forEach(cid => allCids.push(parseInt(cid, 10)));
|
||||
recursive(keys);
|
||||
}
|
||||
const key = 'cid:' + rootCid + ':children';
|
||||
const childrenCids = cache.get(key);
|
||||
if (childrenCids) {
|
||||
return setImmediate(callback, null, childrenCids.slice());
|
||||
return childrenCids.slice();
|
||||
}
|
||||
|
||||
recursive(key, function (err) {
|
||||
if (err) {
|
||||
return callback(err);
|
||||
}
|
||||
allCids = _.uniq(allCids);
|
||||
cache.set(key, allCids);
|
||||
callback(null, allCids.slice());
|
||||
});
|
||||
await recursive(key);
|
||||
allCids = _.uniq(allCids);
|
||||
cache.set(key, allCids);
|
||||
return allCids.slice();
|
||||
};
|
||||
|
||||
Categories.flattenCategories = function (allCategories, categoryData) {
|
||||
@@ -440,19 +320,13 @@ Categories.getTree = function (categories, parentCid) {
|
||||
return tree;
|
||||
};
|
||||
|
||||
Categories.buildForSelect = function (uid, privilege, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getCategoriesByPrivilege('categories:cid', uid, privilege, next);
|
||||
},
|
||||
function (categories, next) {
|
||||
categories = Categories.getTree(categories);
|
||||
Categories.buildForSelectCategories(categories, next);
|
||||
},
|
||||
], callback);
|
||||
Categories.buildForSelect = async function (uid, privilege) {
|
||||
let categories = await Categories.getCategoriesByPrivilege('categories:cid', uid, privilege);
|
||||
categories = Categories.getTree(categories);
|
||||
return await Categories.buildForSelectCategories(categories);
|
||||
};
|
||||
|
||||
Categories.buildForSelectCategories = function (categories, callback) {
|
||||
Categories.buildForSelectCategories = async function (categories) {
|
||||
function recursive(category, categoriesData, level, depth) {
|
||||
var bullet = level ? '• ' : '';
|
||||
category.value = category.cid;
|
||||
@@ -474,7 +348,7 @@ Categories.buildForSelectCategories = function (categories, callback) {
|
||||
categories.forEach(function (category) {
|
||||
recursive(category, categoriesData, '', 0);
|
||||
});
|
||||
callback(null, categoriesData);
|
||||
return categoriesData;
|
||||
};
|
||||
|
||||
Categories.async = require('../promisify')(Categories);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var _ = require('lodash');
|
||||
|
||||
var db = require('../database');
|
||||
@@ -11,143 +10,88 @@ var privileges = require('../privileges');
|
||||
var batch = require('../batch');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.getRecentReplies = function (cid, uid, count, callback) {
|
||||
Categories.getRecentReplies = async function (cid, uid, count) {
|
||||
if (!parseInt(count, 10)) {
|
||||
return callback(null, []);
|
||||
return [];
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRevRange('cid:' + cid + ':pids', 0, count - 1, next);
|
||||
},
|
||||
function (pids, next) {
|
||||
privileges.posts.filter('topics:read', pids, uid, next);
|
||||
},
|
||||
function (pids, next) {
|
||||
posts.getPostSummaryByPids(pids, uid, { stripTags: true }, next);
|
||||
},
|
||||
], callback);
|
||||
let pids = await db.getSortedSetRevRange('cid:' + cid + ':pids', 0, count - 1);
|
||||
pids = await privileges.posts.filter('topics:read', pids, uid);
|
||||
return await posts.getPostSummaryByPids(pids, uid, { stripTags: true });
|
||||
};
|
||||
|
||||
Categories.updateRecentTid = function (cid, tid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
count: function (next) {
|
||||
db.sortedSetCard('cid:' + cid + ':recent_tids', next);
|
||||
},
|
||||
numRecentReplies: function (next) {
|
||||
db.getObjectField('category:' + cid, 'numRecentReplies', next);
|
||||
},
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
if (results.count < results.numRecentReplies) {
|
||||
return db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid, callback);
|
||||
}
|
||||
db.getSortedSetRangeWithScores('cid:' + cid + ':recent_tids', 0, results.count - results.numRecentReplies, next);
|
||||
},
|
||||
function (data, next) {
|
||||
if (!data.length) {
|
||||
return next();
|
||||
}
|
||||
db.sortedSetsRemoveRangeByScore(['cid:' + cid + ':recent_tids'], '-inf', data[data.length - 1].score, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid, next);
|
||||
},
|
||||
], callback);
|
||||
Categories.updateRecentTid = async function (cid, tid) {
|
||||
const [count, numRecentReplies] = await Promise.all([
|
||||
db.sortedSetCard('cid:' + cid + ':recent_tids'),
|
||||
db.getObjectField('category:' + cid, 'numRecentReplies'),
|
||||
]);
|
||||
|
||||
if (count < numRecentReplies) {
|
||||
return await db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid);
|
||||
}
|
||||
const data = await db.getSortedSetRangeWithScores('cid:' + cid + ':recent_tids', 0, count - numRecentReplies);
|
||||
if (data.length) {
|
||||
await db.sortedSetsRemoveRangeByScore(['cid:' + cid + ':recent_tids'], '-inf', data[data.length - 1].score);
|
||||
}
|
||||
await db.sortedSetAdd('cid:' + cid + ':recent_tids', Date.now(), tid);
|
||||
};
|
||||
|
||||
Categories.updateRecentTidForCid = function (cid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.getSortedSetRevRange('cid:' + cid + ':pids', 0, 0, next);
|
||||
},
|
||||
function (pid, next) {
|
||||
pid = pid[0];
|
||||
posts.getPostField(pid, 'tid', next);
|
||||
},
|
||||
function (tid, next) {
|
||||
if (!tid) {
|
||||
return next();
|
||||
}
|
||||
|
||||
Categories.updateRecentTid(cid, tid, next);
|
||||
},
|
||||
], callback);
|
||||
Categories.updateRecentTidForCid = async function (cid) {
|
||||
const pids = await db.getSortedSetRevRange('cid:' + cid + ':pids', 0, 0);
|
||||
if (!pids.length) {
|
||||
return;
|
||||
}
|
||||
const tid = await posts.getPostField(pids[0], 'tid');
|
||||
if (!tid) {
|
||||
return;
|
||||
}
|
||||
await Categories.updateRecentTid(cid, tid);
|
||||
};
|
||||
|
||||
Categories.getRecentTopicReplies = function (categoryData, uid, callback) {
|
||||
Categories.getRecentTopicReplies = async function (categoryData, uid) {
|
||||
if (!Array.isArray(categoryData) || !categoryData.length) {
|
||||
return callback();
|
||||
return;
|
||||
}
|
||||
const categoriesToLoad = categoryData.filter(category => category && category.numRecentReplies && parseInt(category.numRecentReplies, 10) > 0);
|
||||
const keys = categoriesToLoad.map(category => 'cid:' + category.cid + ':recent_tids');
|
||||
const results = await db.getSortedSetsMembers(keys);
|
||||
let tids = _.uniq(_.flatten(results).filter(Boolean));
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
const categoriesToLoad = categoryData.filter(category => category && category.numRecentReplies && parseInt(category.numRecentReplies, 10) > 0);
|
||||
const keys = categoriesToLoad.map(category => 'cid:' + category.cid + ':recent_tids');
|
||||
db.getSortedSetsMembers(keys, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var tids = _.uniq(_.flatten(results).filter(Boolean));
|
||||
tids = await privileges.topics.filterTids('topics:read', tids, uid);
|
||||
const topics = await getTopics(tids, uid);
|
||||
assignTopicsToCategories(categoryData, topics);
|
||||
|
||||
privileges.topics.filterTids('topics:read', tids, uid, next);
|
||||
},
|
||||
function (tids, next) {
|
||||
getTopics(tids, uid, next);
|
||||
},
|
||||
function (topics, next) {
|
||||
assignTopicsToCategories(categoryData, topics);
|
||||
|
||||
bubbleUpChildrenPosts(categoryData);
|
||||
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
bubbleUpChildrenPosts(categoryData);
|
||||
};
|
||||
|
||||
function getTopics(tids, uid, callback) {
|
||||
var topicData;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
topics.getTopicsFields(tids, ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount'], next);
|
||||
},
|
||||
function (_topicData, next) {
|
||||
topicData = _topicData;
|
||||
topicData.forEach(function (topic) {
|
||||
if (topic) {
|
||||
topic.teaserPid = topic.teaserPid || topic.mainPid;
|
||||
}
|
||||
});
|
||||
var cids = _.uniq(topicData.map(topic => topic && topic.cid).filter(cid => parseInt(cid, 10)));
|
||||
|
||||
async.parallel({
|
||||
categoryData: async.apply(Categories.getCategoriesFields, cids, ['cid', 'parentCid']),
|
||||
teasers: async.apply(topics.getTeasers, _topicData, uid),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var parentCids = {};
|
||||
results.categoryData.forEach(function (category) {
|
||||
parentCids[category.cid] = category.parentCid;
|
||||
});
|
||||
results.teasers.forEach(function (teaser, index) {
|
||||
if (teaser) {
|
||||
teaser.cid = topicData[index].cid;
|
||||
teaser.parentCid = parseInt(parentCids[teaser.cid], 10) || 0;
|
||||
teaser.tid = undefined;
|
||||
teaser.uid = undefined;
|
||||
teaser.topic = {
|
||||
slug: topicData[index].slug,
|
||||
title: topicData[index].title,
|
||||
};
|
||||
}
|
||||
});
|
||||
results.teasers = results.teasers.filter(Boolean);
|
||||
next(null, results.teasers);
|
||||
},
|
||||
], callback);
|
||||
async function getTopics(tids, uid) {
|
||||
const topicData = await topics.getTopicsFields(tids, ['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount']);
|
||||
topicData.forEach(function (topic) {
|
||||
if (topic) {
|
||||
topic.teaserPid = topic.teaserPid || topic.mainPid;
|
||||
}
|
||||
});
|
||||
var cids = _.uniq(topicData.map(topic => topic && topic.cid).filter(cid => parseInt(cid, 10)));
|
||||
const [categoryData, teasers] = await Promise.all([
|
||||
Categories.getCategoriesFields(cids, ['cid', 'parentCid']),
|
||||
topics.getTeasers(topicData, uid),
|
||||
]);
|
||||
var parentCids = {};
|
||||
categoryData.forEach(function (category) {
|
||||
parentCids[category.cid] = category.parentCid;
|
||||
});
|
||||
teasers.forEach(function (teaser, index) {
|
||||
if (teaser) {
|
||||
teaser.cid = topicData[index].cid;
|
||||
teaser.parentCid = parseInt(parentCids[teaser.cid], 10) || 0;
|
||||
teaser.tid = undefined;
|
||||
teaser.uid = undefined;
|
||||
teaser.topic = {
|
||||
slug: topicData[index].slug,
|
||||
title: topicData[index].title,
|
||||
};
|
||||
}
|
||||
});
|
||||
return teasers.filter(Boolean);
|
||||
}
|
||||
|
||||
function assignTopicsToCategories(categories, topics) {
|
||||
@@ -188,80 +132,43 @@ module.exports = function (Categories) {
|
||||
getPostsRecursive(child, posts);
|
||||
});
|
||||
}
|
||||
// terrible name, should be topics.moveTopicPosts
|
||||
Categories.moveRecentReplies = async function (tid, oldCid, cid) {
|
||||
await updatePostCount(tid, oldCid, cid);
|
||||
const pids = await topics.getPids(tid);
|
||||
|
||||
Categories.moveRecentReplies = function (tid, oldCid, cid, callback) {
|
||||
callback = callback || function () {};
|
||||
await batch.processArray(pids, async function (pids) {
|
||||
const postData = await posts.getPostsFields(pids, ['pid', 'uid', 'timestamp', 'upvotes', 'downvotes']);
|
||||
const timestamps = postData.map(p => p && p.timestamp);
|
||||
const bulkRemove = [];
|
||||
const bulkAdd = [];
|
||||
postData.forEach((post) => {
|
||||
bulkRemove.push(['cid:' + oldCid + ':uid:' + post.uid + ':pids', post.pid]);
|
||||
bulkRemove.push(['cid:' + oldCid + ':uid:' + post.uid + ':pids:votes', post.pid]);
|
||||
bulkAdd.push(['cid:' + cid + ':uid:' + post.uid + ':pids', post.timestamp, post.pid]);
|
||||
if (post.votes > 0) {
|
||||
bulkAdd.push(['cid:' + cid + ':uid:' + post.uid + ':pids:votes', post.votes, post.pid]);
|
||||
}
|
||||
});
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
updatePostCount(tid, oldCid, cid, next);
|
||||
},
|
||||
function (next) {
|
||||
topics.getPids(tid, next);
|
||||
},
|
||||
function (pids, next) {
|
||||
batch.processArray(pids, function (pids, next) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
posts.getPostsFields(pids, ['pid', 'uid', 'timestamp', 'upvotes', 'downvotes'], next);
|
||||
},
|
||||
function (postData, next) {
|
||||
var timestamps = postData.map(p => p && p.timestamp);
|
||||
|
||||
async.parallel([
|
||||
function (next) {
|
||||
db.sortedSetRemove('cid:' + oldCid + ':pids', pids, next);
|
||||
},
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + cid + ':pids', timestamps, pids, next);
|
||||
},
|
||||
function (next) {
|
||||
async.each(postData, function (post, next) {
|
||||
db.sortedSetRemove([
|
||||
'cid:' + oldCid + ':uid:' + post.uid + ':pids',
|
||||
'cid:' + oldCid + ':uid:' + post.uid + ':pids:votes',
|
||||
], post.pid, next);
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
async.each(postData, function (post, next) {
|
||||
const keys = ['cid:' + cid + ':uid:' + post.uid + ':pids'];
|
||||
const scores = [post.timestamp];
|
||||
if (post.votes > 0) {
|
||||
keys.push('cid:' + cid + ':uid:' + post.uid + ':pids:votes');
|
||||
scores.push(post.votes);
|
||||
}
|
||||
db.sortedSetsAdd(keys, scores, post.pid, next);
|
||||
}, next);
|
||||
},
|
||||
], next);
|
||||
},
|
||||
], next);
|
||||
}, next);
|
||||
},
|
||||
], callback);
|
||||
await Promise.all([
|
||||
db.sortedSetRemove('cid:' + oldCid + ':pids', pids),
|
||||
db.sortedSetAdd('cid:' + cid + ':pids', timestamps, pids),
|
||||
db.sortedSetRemoveBulk(bulkRemove),
|
||||
db.sortedSetAddBulk(bulkAdd),
|
||||
]);
|
||||
}, { batch: 500 });
|
||||
};
|
||||
|
||||
function updatePostCount(tid, oldCid, newCid, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
topics.getTopicField(tid, 'postcount', next);
|
||||
},
|
||||
function (postCount, next) {
|
||||
if (!postCount) {
|
||||
return callback();
|
||||
}
|
||||
async.parallel([
|
||||
function (next) {
|
||||
db.incrObjectFieldBy('category:' + oldCid, 'post_count', -postCount, next);
|
||||
},
|
||||
function (next) {
|
||||
db.incrObjectFieldBy('category:' + newCid, 'post_count', postCount, next);
|
||||
},
|
||||
], function (err) {
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
], callback);
|
||||
async function updatePostCount(tid, oldCid, newCid) {
|
||||
const postCount = await topics.getTopicField(tid, 'postcount');
|
||||
if (!postCount) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
db.incrObjectFieldBy('category:' + oldCid, 'post_count', -postCount),
|
||||
db.incrObjectFieldBy('category:' + newCid, 'post_count', postCount),
|
||||
]);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
var _ = require('lodash');
|
||||
|
||||
var db = require('../database');
|
||||
@@ -10,123 +9,89 @@ var meta = require('../meta');
|
||||
var user = require('../user');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.getCategoryTopics = function (data, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
plugins.fireHook('filter:category.topics.prepare', data, next);
|
||||
},
|
||||
function (data, next) {
|
||||
Categories.getTopicIds(data, next);
|
||||
},
|
||||
function (tids, next) {
|
||||
topics.getTopicsByTids(tids, data.uid, next);
|
||||
},
|
||||
async.apply(user.blocks.filter, data.uid),
|
||||
function (topicsData, next) {
|
||||
if (!topicsData.length) {
|
||||
return next(null, { topics: [], uid: data.uid });
|
||||
}
|
||||
topics.calculateTopicIndices(topicsData, data.start);
|
||||
Categories.getCategoryTopics = async function (data) {
|
||||
let results = await plugins.fireHook('filter:category.topics.prepare', data);
|
||||
const tids = await Categories.getTopicIds(results);
|
||||
let topicsData = await topics.getTopicsByTids(tids, data.uid);
|
||||
topicsData = await user.blocks.filter(data.uid, topicsData);
|
||||
|
||||
plugins.fireHook('filter:category.topics.get', { cid: data.cid, topics: topicsData, uid: data.uid }, next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, { topics: results.topics, nextStart: data.stop + 1 });
|
||||
},
|
||||
], callback);
|
||||
if (!topicsData.length) {
|
||||
return { topics: [], uid: data.uid };
|
||||
}
|
||||
topics.calculateTopicIndices(topicsData, data.start);
|
||||
|
||||
results = await plugins.fireHook('filter:category.topics.get', { cid: data.cid, topics: topicsData, uid: data.uid });
|
||||
return { topics: results.topics, nextStart: data.stop + 1 };
|
||||
};
|
||||
|
||||
Categories.getTopicIds = function (data, callback) {
|
||||
var pinnedTids;
|
||||
Categories.getTopicIds = async function (data) {
|
||||
const dataForPinned = _.cloneDeep(data);
|
||||
dataForPinned.start = 0;
|
||||
dataForPinned.stop = -1;
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
var dataForPinned = _.cloneDeep(data);
|
||||
dataForPinned.start = 0;
|
||||
dataForPinned.stop = -1;
|
||||
const [pinnedTids, set, direction] = await Promise.all([
|
||||
Categories.getPinnedTids(dataForPinned),
|
||||
Categories.buildTopicsSortedSet(data),
|
||||
Categories.getSortedSetRangeDirection(data.sort),
|
||||
]);
|
||||
|
||||
async.parallel({
|
||||
pinnedTids: async.apply(Categories.getPinnedTids, dataForPinned),
|
||||
set: async.apply(Categories.buildTopicsSortedSet, data),
|
||||
direction: async.apply(Categories.getSortedSetRangeDirection, data.sort),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
var totalPinnedCount = results.pinnedTids.length;
|
||||
const totalPinnedCount = pinnedTids.length;
|
||||
const pinnedTidsOnPage = pinnedTids.slice(data.start, data.stop !== -1 ? data.stop + 1 : undefined);
|
||||
const pinnedCountOnPage = pinnedTidsOnPage.length;
|
||||
const topicsPerPage = data.stop - data.start + 1;
|
||||
const normalTidsToGet = Math.max(0, topicsPerPage - pinnedCountOnPage);
|
||||
|
||||
pinnedTids = results.pinnedTids.slice(data.start, data.stop !== -1 ? data.stop + 1 : undefined);
|
||||
if (!normalTidsToGet && data.stop !== -1) {
|
||||
return pinnedTidsOnPage;
|
||||
}
|
||||
|
||||
var pinnedCount = pinnedTids.length;
|
||||
if (plugins.hasListeners('filter:categories.getTopicIds')) {
|
||||
const result = await plugins.fireHook('filter:categories.getTopicIds', {
|
||||
tids: [],
|
||||
data: data,
|
||||
pinnedTids: pinnedTidsOnPage,
|
||||
allPinnedTids: pinnedTids,
|
||||
totalPinnedCount: totalPinnedCount,
|
||||
normalTidsToGet: normalTidsToGet,
|
||||
});
|
||||
return result && result.tids;
|
||||
}
|
||||
|
||||
var topicsPerPage = data.stop - data.start + 1;
|
||||
let start = data.start;
|
||||
if (start > 0 && totalPinnedCount) {
|
||||
start -= totalPinnedCount - pinnedCountOnPage;
|
||||
}
|
||||
|
||||
var normalTidsToGet = Math.max(0, topicsPerPage - pinnedCount);
|
||||
const stop = data.stop === -1 ? data.stop : start + normalTidsToGet - 1;
|
||||
let normalTids;
|
||||
const reverse = direction === 'highest-to-lowest';
|
||||
if (Array.isArray(set)) {
|
||||
const weights = set.map((s, index) => (index ? 0 : 1));
|
||||
normalTids = await db[reverse ? 'getSortedSetRevIntersect' : 'getSortedSetIntersect']({ sets: set, start: start, stop: stop, weights: weights });
|
||||
} else {
|
||||
normalTids = await db[reverse ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop);
|
||||
}
|
||||
normalTids = normalTids.filter(tid => !pinnedTids.includes(tid));
|
||||
|
||||
if (!normalTidsToGet && data.stop !== -1) {
|
||||
return next(null, []);
|
||||
}
|
||||
|
||||
if (plugins.hasListeners('filter:categories.getTopicIds')) {
|
||||
return plugins.fireHook('filter:categories.getTopicIds', {
|
||||
tids: [],
|
||||
data: data,
|
||||
pinnedTids: pinnedTids,
|
||||
allPinnedTids: results.pinnedTids,
|
||||
totalPinnedCount: totalPinnedCount,
|
||||
normalTidsToGet: normalTidsToGet,
|
||||
}, function (err, data) {
|
||||
callback(err, data && data.tids);
|
||||
});
|
||||
}
|
||||
|
||||
var set = results.set;
|
||||
var direction = results.direction;
|
||||
var start = data.start;
|
||||
if (start > 0 && totalPinnedCount) {
|
||||
start -= totalPinnedCount - pinnedCount;
|
||||
}
|
||||
|
||||
var stop = data.stop === -1 ? data.stop : start + normalTidsToGet - 1;
|
||||
|
||||
if (Array.isArray(set)) {
|
||||
const weights = set.map((s, index) => (index ? 0 : 1));
|
||||
db[direction === 'highest-to-lowest' ? 'getSortedSetRevIntersect' : 'getSortedSetIntersect']({ sets: set, start: start, stop: stop, weights: weights }, next);
|
||||
} else {
|
||||
db[direction === 'highest-to-lowest' ? 'getSortedSetRevRange' : 'getSortedSetRange'](set, start, stop, next);
|
||||
}
|
||||
},
|
||||
function (normalTids, next) {
|
||||
normalTids = normalTids.filter(tid => !pinnedTids.includes(tid));
|
||||
|
||||
next(null, pinnedTids.concat(normalTids));
|
||||
},
|
||||
], callback);
|
||||
return pinnedTids.concat(normalTids);
|
||||
};
|
||||
|
||||
Categories.getTopicCount = function (data, callback) {
|
||||
Categories.getTopicCount = async function (data) {
|
||||
if (plugins.hasListeners('filter:categories.getTopicCount')) {
|
||||
return plugins.fireHook('filter:categories.getTopicCount', {
|
||||
const result = await plugins.fireHook('filter:categories.getTopicCount', {
|
||||
topicCount: data.category.topic_count,
|
||||
data: data,
|
||||
}, function (err, data) {
|
||||
callback(err, data && data.topicCount);
|
||||
});
|
||||
return result && result.topicCount;
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.buildTopicsSortedSet(data, next);
|
||||
},
|
||||
function (set, next) {
|
||||
if (Array.isArray(set)) {
|
||||
db.sortedSetIntersectCard(set, next);
|
||||
} else {
|
||||
next(null, data.category.topic_count);
|
||||
}
|
||||
},
|
||||
], callback);
|
||||
const set = await Categories.buildTopicsSortedSet(data);
|
||||
if (Array.isArray(set)) {
|
||||
return await db.sortedSetIntersectCard(set);
|
||||
}
|
||||
return data.category.topic_count;
|
||||
};
|
||||
|
||||
Categories.buildTopicsSortedSet = function (data, callback) {
|
||||
Categories.buildTopicsSortedSet = async function (data) {
|
||||
var cid = data.cid;
|
||||
var set = 'cid:' + cid + ':tids';
|
||||
var sort = data.sort || (data.settings && data.settings.categoryTopicSort) || meta.config.categoryTopicSort || 'newest_to_oldest';
|
||||
@@ -148,40 +113,37 @@ module.exports = function (Categories) {
|
||||
set = [set, 'tag:' + data.tag + ':topics'];
|
||||
}
|
||||
}
|
||||
plugins.fireHook('filter:categories.buildTopicsSortedSet', {
|
||||
const result = await plugins.fireHook('filter:categories.buildTopicsSortedSet', {
|
||||
set: set,
|
||||
data: data,
|
||||
}, function (err, data) {
|
||||
callback(err, data && data.set);
|
||||
});
|
||||
return result && result.set;
|
||||
};
|
||||
|
||||
Categories.getSortedSetRangeDirection = function (sort, callback) {
|
||||
Categories.getSortedSetRangeDirection = async function (sort) {
|
||||
sort = sort || 'newest_to_oldest';
|
||||
var direction = sort === 'newest_to_oldest' || sort === 'most_posts' || sort === 'most_votes' ? 'highest-to-lowest' : 'lowest-to-highest';
|
||||
plugins.fireHook('filter:categories.getSortedSetRangeDirection', {
|
||||
const result = await plugins.fireHook('filter:categories.getSortedSetRangeDirection', {
|
||||
sort: sort,
|
||||
direction: direction,
|
||||
}, function (err, data) {
|
||||
callback(err, data && data.direction);
|
||||
});
|
||||
return result && result.direction;
|
||||
};
|
||||
|
||||
Categories.getAllTopicIds = function (cid, start, stop, callback) {
|
||||
db.getSortedSetRange(['cid:' + cid + ':tids:pinned', 'cid:' + cid + ':tids'], start, stop, callback);
|
||||
Categories.getAllTopicIds = async function (cid, start, stop) {
|
||||
return await db.getSortedSetRange(['cid:' + cid + ':tids:pinned', 'cid:' + cid + ':tids'], start, stop);
|
||||
};
|
||||
|
||||
Categories.getPinnedTids = function (data, callback) {
|
||||
Categories.getPinnedTids = async function (data) {
|
||||
if (plugins.hasListeners('filter:categories.getPinnedTids')) {
|
||||
return plugins.fireHook('filter:categories.getPinnedTids', {
|
||||
const result = await plugins.fireHook('filter:categories.getPinnedTids', {
|
||||
pinnedTids: [],
|
||||
data: data,
|
||||
}, function (err, data) {
|
||||
callback(err, data && data.pinnedTids);
|
||||
});
|
||||
return result && result.pinnedTids;
|
||||
}
|
||||
|
||||
db.getSortedSetRevRange('cid:' + data.cid + ':tids:pinned', data.start, data.stop, callback);
|
||||
return await db.getSortedSetRevRange('cid:' + data.cid + ':tids:pinned', data.start, data.stop);
|
||||
};
|
||||
|
||||
Categories.modifyTopicsByPrivilege = function (topics, privileges) {
|
||||
@@ -200,27 +162,18 @@ module.exports = function (Categories) {
|
||||
});
|
||||
};
|
||||
|
||||
Categories.onNewPostMade = function (cid, pinned, postData, callback) {
|
||||
Categories.onNewPostMade = async function (cid, pinned, postData) {
|
||||
if (!cid || !postData) {
|
||||
return setImmediate(callback);
|
||||
return;
|
||||
}
|
||||
|
||||
async.parallel([
|
||||
function (next) {
|
||||
db.sortedSetAdd('cid:' + cid + ':pids', postData.timestamp, postData.pid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.incrObjectField('category:' + cid, 'post_count', next);
|
||||
},
|
||||
function (next) {
|
||||
if (pinned) {
|
||||
return setImmediate(next);
|
||||
}
|
||||
db.sortedSetIncrBy('cid:' + cid + ':tids:posts', 1, postData.tid, err => next(err));
|
||||
},
|
||||
function (next) {
|
||||
Categories.updateRecentTid(cid, postData.tid, next);
|
||||
},
|
||||
], callback);
|
||||
const promises = [
|
||||
db.sortedSetAdd('cid:' + cid + ':pids', postData.timestamp, postData.pid),
|
||||
db.incrObjectField('category:' + cid, 'post_count'),
|
||||
Categories.updateRecentTid(cid, postData.tid),
|
||||
];
|
||||
if (!pinned) {
|
||||
promises.push(db.sortedSetIncrBy('cid:' + cid + ':tids:posts', 1, postData.tid));
|
||||
}
|
||||
await Promise.all(promises);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,50 +1,38 @@
|
||||
'use strict';
|
||||
|
||||
var async = require('async');
|
||||
|
||||
var db = require('../database');
|
||||
const db = require('../database');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.markAsRead = function (cids, uid, callback) {
|
||||
callback = callback || function () {};
|
||||
Categories.markAsRead = async function (cids, uid) {
|
||||
if (!Array.isArray(cids) || !cids.length || parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback);
|
||||
return;
|
||||
}
|
||||
var keys = cids.map(cid => 'cid:' + cid + ':read_by_uid');
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.isMemberOfSets(keys, uid, next);
|
||||
},
|
||||
function (hasRead, next) {
|
||||
keys = keys.filter((key, index) => !hasRead[index]);
|
||||
|
||||
db.setsAdd(keys, uid, next);
|
||||
},
|
||||
], callback);
|
||||
let keys = cids.map(cid => 'cid:' + cid + ':read_by_uid');
|
||||
const hasRead = await db.isMemberOfSets(keys, uid);
|
||||
keys = keys.filter((key, index) => !hasRead[index]);
|
||||
await db.setsAdd(keys, uid);
|
||||
};
|
||||
|
||||
Categories.markAsUnreadForAll = function (cid, callback) {
|
||||
Categories.markAsUnreadForAll = async function (cid) {
|
||||
if (!parseInt(cid, 10)) {
|
||||
return callback();
|
||||
return;
|
||||
}
|
||||
callback = callback || function () {};
|
||||
db.delete('cid:' + cid + ':read_by_uid', callback);
|
||||
await db.delete('cid:' + cid + ':read_by_uid');
|
||||
};
|
||||
|
||||
Categories.hasReadCategories = function (cids, uid, callback) {
|
||||
Categories.hasReadCategories = async function (cids, uid) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback, null, cids.map(() => false));
|
||||
return cids.map(() => false);
|
||||
}
|
||||
|
||||
const sets = cids.map(cid => 'cid:' + cid + ':read_by_uid');
|
||||
db.isMemberOfSets(sets, uid, callback);
|
||||
return await db.isMemberOfSets(sets, uid);
|
||||
};
|
||||
|
||||
Categories.hasReadCategory = function (cid, uid, callback) {
|
||||
Categories.hasReadCategory = async function (cid, uid) {
|
||||
if (parseInt(uid, 10) <= 0) {
|
||||
return setImmediate(callback, null, false);
|
||||
return false;
|
||||
}
|
||||
db.isSetMember('cid:' + cid + ':read_by_uid', uid, callback);
|
||||
return await db.isSetMember('cid:' + cid + ':read_by_uid', uid);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -10,162 +10,88 @@ var plugins = require('../plugins');
|
||||
var cache = require('../cache');
|
||||
|
||||
module.exports = function (Categories) {
|
||||
Categories.update = function (modified, callback) {
|
||||
Categories.update = async function (modified) {
|
||||
var cids = Object.keys(modified);
|
||||
|
||||
async.each(cids, function (cid, next) {
|
||||
updateCategory(cid, modified[cid], next);
|
||||
}, function (err) {
|
||||
callback(err, cids);
|
||||
});
|
||||
await Promise.all(cids.map(cid => updateCategory(cid, modified[cid])));
|
||||
return cids;
|
||||
};
|
||||
|
||||
function updateCategory(cid, modifiedFields, callback) {
|
||||
var category;
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.exists(cid, next);
|
||||
},
|
||||
function (exists, next) {
|
||||
if (!exists) {
|
||||
return callback();
|
||||
}
|
||||
|
||||
if (modifiedFields.hasOwnProperty('name')) {
|
||||
translator.translate(modifiedFields.name, function (translated) {
|
||||
modifiedFields.slug = cid + '/' + utils.slugify(translated);
|
||||
next();
|
||||
});
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
function (next) {
|
||||
plugins.fireHook('filter:category.update', { cid: cid, category: modifiedFields }, next);
|
||||
},
|
||||
function (categoryData, next) {
|
||||
category = categoryData.category;
|
||||
var fields = Object.keys(category);
|
||||
// move parent to front, so its updated first
|
||||
var parentCidIndex = fields.indexOf('parentCid');
|
||||
if (parentCidIndex !== -1 && fields.length > 1) {
|
||||
fields.splice(0, 0, fields.splice(parentCidIndex, 1)[0]);
|
||||
}
|
||||
|
||||
async.eachSeries(fields, function (key, next) {
|
||||
updateCategoryField(cid, key, category[key], next);
|
||||
}, next);
|
||||
},
|
||||
function (next) {
|
||||
plugins.fireHook('action:category.update', { cid: cid, modified: category });
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function updateCategoryField(cid, key, value, callback) {
|
||||
if (key === 'parentCid') {
|
||||
return updateParent(cid, value, callback);
|
||||
} else if (key === 'tagWhitelist') {
|
||||
return updateTagWhitelist(cid, value, callback);
|
||||
async function updateCategory(cid, modifiedFields) {
|
||||
const exists = await Categories.exists(cid);
|
||||
if (!exists) {
|
||||
return;
|
||||
}
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.setObjectField('category:' + cid, key, value, next);
|
||||
},
|
||||
function (next) {
|
||||
if (key === 'order') {
|
||||
updateOrder(cid, value, next);
|
||||
} else if (key === 'description') {
|
||||
Categories.parseDescription(cid, value, next);
|
||||
} else {
|
||||
next();
|
||||
}
|
||||
},
|
||||
], callback);
|
||||
}
|
||||
|
||||
function updateParent(cid, newParent, callback) {
|
||||
if (parseInt(cid, 10) === parseInt(newParent, 10)) {
|
||||
return callback(new Error('[[error:cant-set-self-as-parent]]'));
|
||||
if (modifiedFields.hasOwnProperty('name')) {
|
||||
const translated = await translator.translate(modifiedFields.name);
|
||||
modifiedFields.slug = cid + '/' + utils.slugify(translated);
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getChildrenCids(cid, next);
|
||||
},
|
||||
function (childrenCids, next) {
|
||||
if (childrenCids.includes(parseInt(newParent, 10))) {
|
||||
return next(new Error('[[error:cant-set-child-as-parent]]'));
|
||||
}
|
||||
Categories.getCategoryField(cid, 'parentCid', next);
|
||||
},
|
||||
function (oldParent, next) {
|
||||
async.series([
|
||||
function (next) {
|
||||
db.sortedSetRemove('cid:' + oldParent + ':children', cid, next);
|
||||
},
|
||||
function (next) {
|
||||
newParent = parseInt(newParent, 10) || 0;
|
||||
db.sortedSetAdd('cid:' + newParent + ':children', cid, cid, next);
|
||||
},
|
||||
function (next) {
|
||||
db.setObjectField('category:' + cid, 'parentCid', newParent, next);
|
||||
},
|
||||
function (next) {
|
||||
cache.del(['cid:' + oldParent + ':children', 'cid:' + newParent + ':children']);
|
||||
next();
|
||||
},
|
||||
], next);
|
||||
},
|
||||
], function (err) {
|
||||
callback(err);
|
||||
const result = await plugins.fireHook('filter:category.update', { cid: cid, category: modifiedFields });
|
||||
|
||||
const category = result.category;
|
||||
var fields = Object.keys(category);
|
||||
// move parent to front, so its updated first
|
||||
var parentCidIndex = fields.indexOf('parentCid');
|
||||
if (parentCidIndex !== -1 && fields.length > 1) {
|
||||
fields.splice(0, 0, fields.splice(parentCidIndex, 1)[0]);
|
||||
}
|
||||
|
||||
await async.eachSeries(fields, async function (key) {
|
||||
await updateCategoryField(cid, key, category[key]);
|
||||
});
|
||||
plugins.fireHook('action:category.update', { cid: cid, modified: category });
|
||||
}
|
||||
|
||||
function updateTagWhitelist(cid, tags, callback) {
|
||||
tags = tags.split(',');
|
||||
tags = tags.map(function (tag) {
|
||||
return utils.cleanUpTag(tag, meta.config.maximumTagLength);
|
||||
}).filter(Boolean);
|
||||
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
db.delete('cid:' + cid + ':tag:whitelist', next);
|
||||
},
|
||||
function (next) {
|
||||
var scores = tags.map((tag, index) => index);
|
||||
db.sortedSetAdd('cid:' + cid + ':tag:whitelist', scores, tags, next);
|
||||
},
|
||||
function (next) {
|
||||
cache.del('cid:' + cid + ':tag:whitelist');
|
||||
next();
|
||||
},
|
||||
], callback);
|
||||
async function updateCategoryField(cid, key, value) {
|
||||
if (key === 'parentCid') {
|
||||
return await updateParent(cid, value);
|
||||
} else if (key === 'tagWhitelist') {
|
||||
return await updateTagWhitelist(cid, value);
|
||||
}
|
||||
await db.setObjectField('category:' + cid, key, value);
|
||||
if (key === 'order') {
|
||||
await updateOrder(cid, value);
|
||||
} else if (key === 'description') {
|
||||
await Categories.parseDescription(cid, value);
|
||||
}
|
||||
}
|
||||
|
||||
function updateOrder(cid, order, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getCategoryField(cid, 'parentCid', next);
|
||||
},
|
||||
function (parentCid, next) {
|
||||
db.sortedSetsAdd(['categories:cid', 'cid:' + parentCid + ':children'], order, cid, function (err) {
|
||||
cache.del(['categories:cid', 'cid:' + parentCid + ':children']);
|
||||
next(err);
|
||||
});
|
||||
},
|
||||
], err => callback(err));
|
||||
async function updateParent(cid, newParent) {
|
||||
newParent = parseInt(newParent, 10) || 0;
|
||||
if (parseInt(cid, 10) === newParent) {
|
||||
throw new Error('[[error:cant-set-self-as-parent]]');
|
||||
}
|
||||
const childrenCids = await Categories.getChildrenCids(cid);
|
||||
if (childrenCids.includes(newParent)) {
|
||||
throw new Error('[[error:cant-set-child-as-parent]]');
|
||||
}
|
||||
const oldParent = await Categories.getCategoryField(cid, 'parentCid');
|
||||
await Promise.all([
|
||||
db.sortedSetRemove('cid:' + oldParent + ':children', cid),
|
||||
db.sortedSetAdd('cid:' + newParent + ':children', cid, cid),
|
||||
db.setObjectField('category:' + cid, 'parentCid', newParent),
|
||||
]);
|
||||
|
||||
cache.del(['cid:' + oldParent + ':children', 'cid:' + newParent + ':children']);
|
||||
}
|
||||
|
||||
Categories.parseDescription = function (cid, description, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
plugins.fireHook('filter:parse.raw', description, next);
|
||||
},
|
||||
function (parsedDescription, next) {
|
||||
Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription, next);
|
||||
},
|
||||
], callback);
|
||||
async function updateTagWhitelist(cid, tags) {
|
||||
tags = tags.split(',').map(tag => utils.cleanUpTag(tag, meta.config.maximumTagLength))
|
||||
.filter(Boolean);
|
||||
await db.delete('cid:' + cid + ':tag:whitelist');
|
||||
const scores = tags.map((tag, index) => index);
|
||||
await db.sortedSetAdd('cid:' + cid + ':tag:whitelist', scores, tags);
|
||||
cache.del('cid:' + cid + ':tag:whitelist');
|
||||
}
|
||||
|
||||
async function updateOrder(cid, order) {
|
||||
const parentCid = await Categories.getCategoryField(cid, 'parentCid');
|
||||
await db.sortedSetsAdd(['categories:cid', 'cid:' + parentCid + ':children'], order, cid);
|
||||
cache.del(['categories:cid', 'cid:' + parentCid + ':children']);
|
||||
}
|
||||
|
||||
Categories.parseDescription = async function (cid, description) {
|
||||
const parsedDescription = await plugins.fireHook('filter:parse.raw', description);
|
||||
await Categories.setCategoryField(cid, 'descriptionParsed', parsedDescription);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
'use strict';
|
||||
|
||||
const async = require('async');
|
||||
|
||||
const db = require('../database');
|
||||
const user = require('../user');
|
||||
|
||||
@@ -12,69 +10,45 @@ module.exports = function (Categories) {
|
||||
watching: 3,
|
||||
};
|
||||
|
||||
Categories.isIgnored = function (cids, uid, callback) {
|
||||
Categories.isIgnored = async function (cids, uid) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return setImmediate(callback, null, cids.map(() => false));
|
||||
return cids.map(() => false);
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getWatchState(cids, uid, next);
|
||||
},
|
||||
function (states, next) {
|
||||
next(null, states.map(state => state === Categories.watchStates.ignoring));
|
||||
},
|
||||
], callback);
|
||||
const states = await Categories.getWatchState(cids, uid);
|
||||
return states.map(state => state === Categories.watchStates.ignoring);
|
||||
};
|
||||
|
||||
Categories.getWatchState = function (cids, uid, callback) {
|
||||
Categories.getWatchState = async function (cids, uid) {
|
||||
if (!(parseInt(uid, 10) > 0)) {
|
||||
return setImmediate(callback, null, cids.map(() => Categories.watchStates.notwatching));
|
||||
return cids.map(() => Categories.watchStates.notwatching);
|
||||
}
|
||||
if (!Array.isArray(cids) || !cids.length) {
|
||||
return setImmediate(callback, null, []);
|
||||
return [];
|
||||
}
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
const keys = cids.map(cid => 'cid:' + cid + ':uid:watch:state');
|
||||
async.parallel({
|
||||
userSettings: async.apply(user.getSettings, uid),
|
||||
states: async.apply(db.sortedSetsScore, keys, uid),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, results.states.map(state => state || Categories.watchStates[results.userSettings.categoryWatchState]));
|
||||
},
|
||||
], callback);
|
||||
const keys = cids.map(cid => 'cid:' + cid + ':uid:watch:state');
|
||||
const [userSettings, states] = await Promise.all([
|
||||
user.getSettings(uid),
|
||||
db.sortedSetsScore(keys, uid),
|
||||
]);
|
||||
return states.map(state => state || Categories.watchStates[userSettings.categoryWatchState]);
|
||||
};
|
||||
|
||||
Categories.getIgnorers = function (cid, start, stop, callback) {
|
||||
Categories.getIgnorers = async function (cid, start, stop) {
|
||||
const count = (stop === -1) ? -1 : (stop - start + 1);
|
||||
db.getSortedSetRevRangeByScore('cid:' + cid + ':uid:watch:state', start, count, Categories.watchStates.ignoring, Categories.watchStates.ignoring, callback);
|
||||
return await db.getSortedSetRevRangeByScore('cid:' + cid + ':uid:watch:state', start, count, Categories.watchStates.ignoring, Categories.watchStates.ignoring);
|
||||
};
|
||||
|
||||
Categories.filterIgnoringUids = function (cid, uids, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
Categories.getUidsWatchStates(cid, uids, next);
|
||||
},
|
||||
function (states, next) {
|
||||
const readingUids = uids.filter((uid, index) => uid && states[index] !== Categories.watchStates.ignoring);
|
||||
next(null, readingUids);
|
||||
},
|
||||
], callback);
|
||||
Categories.filterIgnoringUids = async function (cid, uids) {
|
||||
const states = await Categories.getUidsWatchStates(cid, uids);
|
||||
const readingUids = uids.filter((uid, index) => uid && states[index] !== Categories.watchStates.ignoring);
|
||||
return readingUids;
|
||||
};
|
||||
|
||||
Categories.getUidsWatchStates = function (cid, uids, callback) {
|
||||
async.waterfall([
|
||||
function (next) {
|
||||
async.parallel({
|
||||
userSettings: async.apply(user.getMultipleUserSettings, uids),
|
||||
states: async.apply(db.sortedSetScores, 'cid:' + cid + ':uid:watch:state', uids),
|
||||
}, next);
|
||||
},
|
||||
function (results, next) {
|
||||
next(null, results.states.map((state, index) => state || Categories.watchStates[results.userSettings[index].categoryWatchState]));
|
||||
},
|
||||
], callback);
|
||||
Categories.getUidsWatchStates = async function (cid, uids) {
|
||||
const [userSettings, states] = await Promise.all([
|
||||
user.getMultipleUserSettings(uids),
|
||||
db.sortedSetScores('cid:' + cid + ':uid:watch:state', uids),
|
||||
]);
|
||||
return states.map((state, index) => state || Categories.watchStates[userSettings[index].categoryWatchState]);
|
||||
};
|
||||
};
|
||||
|
||||
@@ -38,7 +38,9 @@ module.exports = function (db, module) {
|
||||
return;
|
||||
}
|
||||
var query = { _key: { $in: keys } };
|
||||
|
||||
if (keys.length === 1) {
|
||||
query._key = keys[0];
|
||||
}
|
||||
if (min !== '-inf') {
|
||||
query.score = { $gte: parseFloat(min) };
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@ var PubSub = function () {
|
||||
var subClient = db.connect();
|
||||
this.pubClient = db.connect();
|
||||
|
||||
channelName = 'db:' + nconf.get('redis:database') + 'pubsub_channel';
|
||||
channelName = 'db:' + nconf.get('redis:database') + ':pubsub_channel';
|
||||
subClient.subscribe(channelName);
|
||||
|
||||
subClient.on('message', function (channel, message) {
|
||||
|
||||
@@ -123,7 +123,7 @@ function copyPrivilegesToChildrenRecursive(parentCid, category, group, callback)
|
||||
},
|
||||
function (next) {
|
||||
async.eachSeries(category.children, function (child, next) {
|
||||
copyPrivilegesToChildrenRecursive(parentCid, child, next);
|
||||
copyPrivilegesToChildrenRecursive(parentCid, child, group, next);
|
||||
}, next);
|
||||
},
|
||||
], callback);
|
||||
|
||||
Reference in New Issue
Block a user