From 5b87af4389b8c3dc0495cc51f86edc176b086c75 Mon Sep 17 00:00:00 2001 From: barisusakli Date: Tue, 18 Aug 2015 14:17:16 -0400 Subject: [PATCH] closes #3447 recursively get all children calculate topic/post count from children new sorted set `cid::children` fix search query params --- public/src/app.js | 2 +- src/categories.js | 72 +++++++++++++++++++++++-------- src/categories/create.js | 5 ++- src/categories/delete.js | 17 ++++++++ src/categories/update.js | 48 ++++++++++++++++++++- src/controllers/categories.js | 17 ++------ src/controllers/search.js | 2 +- src/search.js | 11 +++-- src/sitemap.js | 2 +- src/socket.io/admin/categories.js | 2 +- src/socket.io/categories.js | 4 +- src/upgrade.js | 42 +++++++++++++++++- 12 files changed, 181 insertions(+), 43 deletions(-) diff --git a/public/src/app.js b/public/src/app.js index 5bcbb8201d..2e9b479ba6 100644 --- a/public/src/app.js +++ b/public/src/app.js @@ -503,7 +503,7 @@ app.cacheBuster = null; app.load = function() { $('document').ready(function () { - var url = ajaxify.start(window.location.pathname.slice(1), true, window.location.search); + var url = ajaxify.start(window.location.pathname.slice(1) + window.location.search, true); ajaxify.end(url, app.template); handleStatusChange(); diff --git a/src/categories.js b/src/categories.js index d30cd0c47a..f1a5ab23dd 100644 --- a/src/categories.js +++ b/src/categories.js @@ -103,10 +103,10 @@ var async = require('async'), }); }; - Categories.getCategoriesByPrivilege = function(uid, privilege, callback) { + Categories.getCategoriesByPrivilege = function(set, uid, privilege, callback) { async.waterfall([ function(next) { - db.getSortedSetRange('categories:cid', 0, -1, next); + db.getSortedSetRange(set, 0, -1, next); }, function(cids, next) { privileges.categories.filterCids(privilege, cids, uid, next); @@ -273,6 +273,7 @@ var async = require('async'), if (!category) { return; } + var postCount = parseInt(category.post_count, 10) || 0; var topicCount = parseInt(category.topic_count, 10) || 0; if (!Array.isArray(category.children) || !category.children.length) { @@ -282,9 +283,11 @@ var async = require('async'), } category.children.forEach(function(child) { - postCount += parseInt(child.post_count, 10) || 0; - topicCount += parseInt(child.topic_count, 10) || 0; + calculateTopicPostCount(child); + postCount += parseInt(child.totalPostCount, 10) || 0; + topicCount += parseInt(child.totalTopicCount, 10) || 0; }); + category.totalPostCount = postCount; category.totalTopicCount = topicCount; } @@ -308,23 +311,58 @@ var async = require('async'), }; Categories.getChildren = function(cids, uid, callback) { + var categories = cids.map(function(cid) { + return {cid: cid}; + }); + + async.each(categories, function(category, next) { + getChildrenRecursive(category, category.cid, uid, next); + }, function (err) { + callback(err, categories.map(function(c) { + return c && c.children; + })); + }); + }; + + function getChildrenRecursive(category, parentCid, uid, callback) { async.waterfall([ - async.apply(db.getSortedSetRange, 'categories:cid', 0, -1), - function(cids, next) { - privileges.categories.filterCids('find', cids, uid, next); + function (next) { + db.getSortedSetRange('cid:' + parentCid + ':children', 0, -1, next); }, - function (cids, next) { - Categories.getCategoriesData(cids, next); + function (children, next) { + privileges.categories.filterCids('find', children, uid, next); + }, + function (children, next) { + if (!children.length) { + category.children = []; + return callback(); + } + Categories.getCategoriesData(children, next); }, - function (categories, next) { - async.map(cids, function(cid, next) { - next(null, categories.filter(function(category) { - return category && parseInt(category.parentCid, 10) === parseInt(cid, 10); - })); + function (childrenData, next) { + category.children = childrenData; + async.each(category.children, function(child, next) { + getChildrenRecursive(child, child.cid, uid, next); }, next); } ], callback); - }; + } + + Categories.flattenCategories = function(allCategories, categoryData) { + categoryData.forEach(function(category) { + if (!category) { + return; + } + + if (!category.parent) { + allCategories.push(category); + } + + if (Array.isArray(category.children) && category.children.length) { + Categories.flattenCategories(allCategories, category.children); + } + }); + } /** * Recursively build tree @@ -335,13 +373,13 @@ var async = require('async'), Categories.getTree = function(categories, parentCid) { var tree = [], i = 0, len = categories.length, category; - for(i; i < len; ++i) { + for (i; i < len; ++i) { category = categories[i]; if (!category.hasOwnProperty('parentCid')) { category.parentCid = 0; } - if(category.parentCid == parentCid){ + if (category.parentCid == parentCid){ tree.push(category); category.children = Categories.getTree(categories, category.cid); } diff --git a/src/categories/create.js b/src/categories/create.js index 71eef4e72e..73a2591714 100644 --- a/src/categories/create.js +++ b/src/categories/create.js @@ -10,6 +10,8 @@ module.exports = function(Categories) { Categories.create = function(data, callback) { var category; + var parentCid = data.parentCid ? data.parentCid : 0; + async.waterfall([ function(next) { db.incrObjectField('global', 'nextCid', next); @@ -27,7 +29,7 @@ module.exports = function(Categories) { bgColor: data.bgColor || colours[0], color: data.color || colours[1], slug: slug, - parentCid: ( data.parentCid ? data.parentCid : 0 ), + parentCid: parentCid, topic_count: 0, post_count: 0, disabled: 0, @@ -48,6 +50,7 @@ module.exports = function(Categories) { async.series([ async.apply(db.setObject, 'category:' + category.cid, category), async.apply(db.sortedSetAdd, 'categories:cid', category.order, category.cid), + async.apply(db.sortedSetAdd, 'cid:' + parentCid + ':children', category.order, category.cid), async.apply(privileges.categories.give, defaultPrivileges, category.cid, 'administrators'), async.apply(privileges.categories.give, defaultPrivileges, category.cid, 'registered-users'), async.apply(privileges.categories.give, ['find', 'read'], category.cid, 'guests') diff --git a/src/categories/delete.js b/src/categories/delete.js index 1679ab6731..8483efaf72 100644 --- a/src/categories/delete.js +++ b/src/categories/delete.js @@ -30,15 +30,32 @@ module.exports = function(Categories) { function(next) { db.sortedSetRemove('categories:cid', cid, next); }, + function(next) { + removeFromParent(cid, next); + }, function(next) { db.deleteAll([ 'cid:' + cid + ':tids', 'cid:' + cid + ':tids:posts', 'cid:' + cid + ':pids', 'cid:' + cid + ':read_by_uid', + 'cid:' + cid + ':children', 'category:' + cid ], next); } ], callback); } + + function removeFromParent(cid, callback) { + async.waterfall([ + function(next) { + Categories.getCategoryField(cid, 'parentCid', next); + }, + function(parentCid, next) { + parentCid = parseInt(parentCid, 10) || 0; + db.sortedSetRemove('cid:' + parentCid + ':children', cid, next); + } + ], callback); + + } }; \ No newline at end of file diff --git a/src/categories/update.js b/src/categories/update.js index fb5cec5054..036b405c96 100644 --- a/src/categories/update.js +++ b/src/categories/update.js @@ -50,17 +50,63 @@ module.exports = function(Categories) { }; function updateCategoryField(cid, key, value, callback) { + if (key === 'parentCid') { + return updateParent(cid, value, callback); + } + db.setObjectField('category:' + cid, key, value, function(err) { if (err) { return callback(err); } if (key === 'order') { - db.sortedSetAdd('categories:cid', value, cid, callback); + updateOrder(cid, value, callback); } else { callback(); } }); } + function updateParent(cid, newParent, callback) { + Categories.getCategoryField(cid, 'parentCid', function(err, oldParent) { + if (err) { + return callback(err); + } + + async.series([ + function (next) { + oldParent = parseInt(oldParent, 10) || 0; + 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(err, results) { + callback(err); + }); + }); + } + + function updateOrder(cid, order, callback) { + Categories.getCategoryField(cid, 'parentCid', function(err, parentCid) { + if (err) { + return callback(err); + } + + async.parallel([ + function (next) { + db.sortedSetAdd('categories:cid', order, cid, next); + }, + function (next) { + parentCid = parseInt(parentCid, 10) || 0; + db.sortedSetAdd('cid:' + parentCid + ':children', order, cid, next); + } + ], callback); + }); + } + }; diff --git a/src/controllers/categories.js b/src/controllers/categories.js index bbffcf1a86..bae16adc3b 100644 --- a/src/controllers/categories.js +++ b/src/controllers/categories.js @@ -33,7 +33,7 @@ categoriesController.list = function(req, res, next) { content: 'website' }]; - if(meta.config['brand:logo']) { + if (meta.config['brand:logo']) { res.locals.metaTags.push({ property: 'og:image', content: meta.config['brand:logo'] @@ -46,22 +46,13 @@ categoriesController.list = function(req, res, next) { var categoryData; async.waterfall([ function(next) { - categories.getCategoriesByPrivilege(req.uid, 'find', next); + categories.getCategoriesByPrivilege('cid:0:children', req.uid, 'find', next); }, function(_categoryData, next) { categoryData = _categoryData; - var allCategories = []; - - categoryData = categoryData.filter(function(category) { - if (!category.parent) { - allCategories.push(category); - } - if (Array.isArray(category.children) && category.children.length) { - allCategories.push.apply(allCategories, category.children); - } - return category && !category.parent; - }); + var allCategories = []; + categories.flattenCategories(allCategories, categoryData); categories.getRecentTopicReplies(allCategories, req.uid, next); } diff --git a/src/controllers/search.js b/src/controllers/search.js index 6637acbbc3..2148e6644b 100644 --- a/src/controllers/search.js +++ b/src/controllers/search.js @@ -17,7 +17,7 @@ searchController.search = function(req, res, next) { var breadcrumbs = helpers.buildBreadcrumbs([{text: '[[global:search]]'}]); - categories.getCategoriesByPrivilege(req.uid, 'read', function(err, categories) { + categories.getCategoriesByPrivilege('categories:cid', req.uid, 'read', function(err, categories) { if (err) { return next(err); } diff --git a/src/search.js b/src/search.js index 09e22a7a44..127588a5cd 100644 --- a/src/search.js +++ b/src/search.js @@ -417,11 +417,14 @@ function getChildrenCids(cids, uid, callback) { } var childrenCids = []; + var allCategories = []; + childrenCategories.forEach(function(childrens) { - childrenCids = childrenCids.concat(childrens.map(function(category) { - return category && category.cid; - })); - }); + categories.flattenCategories(allCategories, childrens); + childrenCids = childrenCids.concat(allCategories.map(function(category) { + return category && category.cid; + })); + }); callback(null, childrenCids); }); diff --git a/src/sitemap.js b/src/sitemap.js index eaf7686acd..a1fc3731d9 100644 --- a/src/sitemap.js +++ b/src/sitemap.js @@ -61,7 +61,7 @@ sitemap.getDynamicUrls = function(callback) { async.parallel({ categoryUrls: function(next) { var categoryUrls = []; - categories.getCategoriesByPrivilege(0, 'find', function(err, categoriesData) { + categories.getCategoriesByPrivilege('categories:cid', 0, 'find', function(err, categoriesData) { if (err) { return next(err); } diff --git a/src/socket.io/admin/categories.js b/src/socket.io/admin/categories.js index ea7ad27780..e1e72c5ec8 100644 --- a/src/socket.io/admin/categories.js +++ b/src/socket.io/admin/categories.js @@ -20,7 +20,7 @@ Categories.create = function(socket, data, callback) { Categories.getAll = function(socket, data, callback) { async.waterfall([ - async.apply(db.getSortedSetRangeByScore, 'categories:cid', 0, -1, 0, Date.now()), + async.apply(db.getSortedSetRange, 'categories:cid', 0, -1), async.apply(categories.getCategoriesData), function(categories, next) { //Hook changes, there is no req, and res diff --git a/src/socket.io/categories.js b/src/socket.io/categories.js index 0c1af11388..b437349ded 100644 --- a/src/socket.io/categories.js +++ b/src/socket.io/categories.js @@ -15,7 +15,7 @@ SocketCategories.getRecentReplies = function(socket, cid, callback) { }; SocketCategories.get = function(socket, data, callback) { - categories.getCategoriesByPrivilege(socket.uid, 'find', callback); + categories.getCategoriesByPrivilege('categories:cid', socket.uid, 'find', callback); }; SocketCategories.getWatchedCategories = function(socket, data, callback) { @@ -117,7 +117,7 @@ SocketCategories.getUsersInCategory = function(socket, cid, callback) { }; SocketCategories.getCategoriesByPrivilege = function(socket, privilege, callback) { - categories.getCategoriesByPrivilege(socket.uid, privilege, callback); + categories.getCategoriesByPrivilege('categories:cid', socket.uid, privilege, callback); }; SocketCategories.watch = function(socket, cid, callback) { diff --git a/src/upgrade.js b/src/upgrade.js index 6d985264b9..01c3da455c 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -21,7 +21,7 @@ var db = require('./database'), schemaDate, thisSchemaDate, // IMPORTANT: REMEMBER TO UPDATE VALUE OF latestSchema - latestSchema = Date.UTC(2015, 6, 3); + latestSchema = Date.UTC(2015, 7, 18); Upgrade.check = function(callback) { db.get('schemaDate', function(err, value) { @@ -446,6 +446,46 @@ Upgrade.upgrade = function(callback) { winston.info('[2015/07/03] Enabling default composer plugin skipped'); next(); } + }, + function(next) { + thisSchemaDate = Date.UTC(2015, 7, 18); + if (schemaDate < thisSchemaDate) { + updatesMade = true; + winston.info('[2015/08/18] Creating children category sorted sets'); + + db.getSortedSetRange('categories:cid', 0, -1, function(err, cids) { + if (err) { + return next(err); + } + + async.each(cids, function(cid, next) { + db.getObjectFields('category:' + cid, ['parentCid', 'order'], function(err, category) { + if (err) { + return next(err); + } + if (!category) { + return next(); + } + if (parseInt(category.parentCid, 10)) { + db.sortedSetAdd('cid:' + category.parentCid + ':children', parseInt(category.order, 10), cid, next); + } else { + db.sortedSetAdd('cid:0:children', parseInt(category.order, 10), cid, next); + } + }); + }, function(err) { + if (err) { + return next(err); + } + + winston.info('[2015/08/18] Creating children category sorted sets done'); + Upgrade.update(thisSchemaDate, next); + }); + }); + + } else { + winston.info('[2015/08/18] Creating children category sorted sets skipped'); + next(); + } }