fix: ordering nested categories

if a category is nested beyond one level, the cache for the categories needs to be cleared all the way to the root.
This commit is contained in:
Barış Soner Uşaklı
2026-03-01 11:46:47 -05:00
parent 643991ab03
commit a1b77fa033
5 changed files with 28 additions and 31 deletions

View File

@@ -79,7 +79,11 @@ async function xhr(options) {
if (!res.ok) { if (!res.ok) {
if (response) { if (response) {
throw new Error(isJSON ? response.status.message : response); const jsonError = isJSON && (response.status?.message || response.error || '');
throw new Error(isJSON && jsonError ?
jsonError :
response
);
} }
throw new Error(res.statusText); throw new Error(res.statusText);
} }

View File

@@ -101,7 +101,7 @@ module.exports = function (Categories) {
await privileges.categories.give(result.guestPrivileges, category.cid, ['guests', 'spiders']); await privileges.categories.give(result.guestPrivileges, category.cid, ['guests', 'spiders']);
cache.del('categories:cid'); cache.del('categories:cid');
await clearParentCategoryCache(parentCid); await Categories.clearParentCategoryCache(parentCid);
if (data.cloneFromCid && parseInt(data.cloneFromCid, 10)) { if (data.cloneFromCid && parseInt(data.cloneFromCid, 10)) {
category = await Categories.copySettingsFrom(data.cloneFromCid, category.cid, !data.parentCid); category = await Categories.copySettingsFrom(data.cloneFromCid, category.cid, !data.parentCid);
@@ -115,8 +115,8 @@ module.exports = function (Categories) {
return category; return category;
}; };
async function clearParentCategoryCache(parentCid) { Categories.clearParentCategoryCache = async function (parentCid) {
while (parseInt(parentCid, 10) >= 0) { while (parentCid || parseInt(parentCid, 10) === 0) {
cache.del([ cache.del([
`cid:${parentCid}:children`, `cid:${parentCid}:children`,
`cid:${parentCid}:children:all`, `cid:${parentCid}:children:all`,
@@ -129,7 +129,7 @@ module.exports = function (Categories) {
// eslint-disable-next-line no-await-in-loop // eslint-disable-next-line no-await-in-loop
parentCid = await Categories.getCategoryField(parentCid, 'parentCid'); parentCid = await Categories.getCategoryField(parentCid, 'parentCid');
} }
} };
async function duplicateCategoriesChildren(parentCid, cid, uid) { async function duplicateCategoriesChildren(parentCid, cid, uid) {
let children = await Categories.getChildren([cid], uid); let children = await Categories.getChildren([cid], uid);
@@ -188,17 +188,13 @@ module.exports = function (Categories) {
throw new Error('[[error:invalid-cid]]'); throw new Error('[[error:invalid-cid]]');
} }
const oldParent = parseInt(destination.parentCid, 10) || 0; const oldParent = String(destination.parentCid || 0);
const newParent = parseInt(source.parentCid, 10) || 0; const newParent = String(source.parentCid || 0);
if (copyParent && newParent !== parseInt(toCid, 10)) { if (copyParent && newParent !== String(toCid)) {
await db.sortedSetRemove(`cid:${oldParent}:children`, toCid); await db.sortedSetRemove(`cid:${oldParent}:children`, toCid);
await db.sortedSetAdd(`cid:${newParent}:children`, source.order, toCid); await db.sortedSetAdd(`cid:${newParent}:children`, source.order, toCid);
cache.del([ await Categories.clearParentCategoryCache(oldParent);
`cid:${oldParent}:children`, await Categories.clearParentCategoryCache(newParent);
`cid:${oldParent}:children:all`,
`cid:${newParent}:children`,
`cid:${newParent}:children:all`,
]);
} }
destination.description = source.description; destination.description = source.description;

View File

@@ -79,12 +79,13 @@ module.exports = function (Categories) {
cache.del([ cache.del([
'categories:cid', 'categories:cid',
'cid:0:children', 'cid:0:children',
`cid:${parentCid}:children`, 'cid:0:children:all',
`cid:${parentCid}:children:all`,
`cid:${cid}:children`,
`cid:${cid}:children:all`,
`cid:${cid}:tag:whitelist`, `cid:${cid}:tag:whitelist`,
]); ]);
await Promise.all([
Categories.clearParentCategoryCache(parentCid),
Categories.clearParentCategoryCache(cid),
]);
} }
async function deleteTags(cid) { async function deleteTags(cid) {

View File

@@ -85,12 +85,9 @@ module.exports = function (Categories) {
db.sortedSetAdd(`cid:${newParent}:children`, categoryData.order, cid), db.sortedSetAdd(`cid:${newParent}:children`, categoryData.order, cid),
db.setObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, 'parentCid', newParent), db.setObjectField(`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, 'parentCid', newParent),
]); ]);
await Promise.all([
cache.del([ Categories.clearParentCategoryCache(oldParent),
`cid:${oldParent}:children`, Categories.clearParentCategoryCache(newParent),
`cid:${newParent}:children`,
`cid:${oldParent}:children:all`,
`cid:${newParent}:children:all`,
]); ]);
} }
@@ -134,11 +131,9 @@ module.exports = function (Categories) {
await db.setObjectBulk( await db.setObjectBulk(
childrenCids.map((cid, index) => [`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, { order: index + 1 }]) childrenCids.map((cid, index) => [`${utils.isNumber(cid) ? 'category' : 'categoryRemote'}:${cid}`, { order: index + 1 }])
); );
await Categories.clearParentCategoryCache(parentCid);
cache.del([ cache.del([
'categories:cid', 'categories:cid',
`cid:${parentCid}:children`,
`cid:${parentCid}:children:all`,
]); ]);
} }

View File

@@ -202,13 +202,14 @@ categoriesController.addRemote = async function (req, res) {
return res.sendStatus(404); return res.sendStatus(404);
} }
const score = await db.sortedSetCard('cid:0:children'); const lastItem = await db.getSortedSetRevRangeWithScores('cid:0:children', 0, 0);
const order = score + 1; // order is 1-based lol const order = lastItem.length ? lastItem[0].score + 1 : 1;
await Promise.all([ await Promise.all([
db.sortedSetAdd('cid:0:children', order, id), db.sortedSetAdd('cid:0:children', order, id),
categories.setCategoryField(id, 'order', order), categories.setCategoryField(id, 'order', order),
]); ]);
cache.del('cid:0:children'); cache.del('cid:0:children');
cache.del('cid:0:children:all');
res.sendStatus(200); res.sendStatus(200);
}; };
@@ -231,7 +232,7 @@ categoriesController.removeRemote = async function (req, res) {
const parentCid = await categories.getCategoryField(req.params.cid, 'parentCid'); const parentCid = await categories.getCategoryField(req.params.cid, 'parentCid');
await db.sortedSetRemove(`cid:${parentCid || 0}:children`, req.params.cid); await db.sortedSetRemove(`cid:${parentCid || 0}:children`, req.params.cid);
cache.del(`cid:${parentCid || 0}:children`); await categories.clearParentCategoryCache(parentCid || 0);
await categories.setCategoryField(req.params.cid, 'parentCid', 0);
res.sendStatus(200); res.sendStatus(200);
}; };