You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
198 lines
6.4 KiB
JavaScript
198 lines
6.4 KiB
JavaScript
|
|
'use strict';
|
|
|
|
const winston = require('winston');
|
|
const _ = require('lodash');
|
|
|
|
const db = require('../database');
|
|
const posts = require('../posts');
|
|
const topics = require('../topics');
|
|
const privileges = require('../privileges');
|
|
const plugins = require('../plugins');
|
|
const batch = require('../batch');
|
|
|
|
module.exports = function (Categories) {
|
|
Categories.getRecentReplies = async function (cid, uid, start, stop) {
|
|
// backwards compatibility, treat start as count
|
|
if (stop === undefined && start > 0) {
|
|
winston.warn('[Categories.getRecentReplies] 3 params deprecated please use Categories.getRecentReplies(cid, uid, start, stop)');
|
|
stop = start - 1;
|
|
start = 0;
|
|
}
|
|
let pids = await db.getSortedSetRevRange(`cid:${cid}:pids`, start, stop);
|
|
pids = await privileges.posts.filter('topics:read', pids, uid);
|
|
return await posts.getPostSummaryByPids(pids, uid, { stripTags: true });
|
|
};
|
|
|
|
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) {
|
|
const data = await db.getSortedSetRangeWithScores(`cid:${cid}:recent_tids`, 0, count - numRecentReplies);
|
|
const shouldRemove = !(data.length === 1 && count === 1 && data[0].value === String(tid));
|
|
if (data.length && shouldRemove) {
|
|
await db.sortedSetsRemoveRangeByScore([`cid:${cid}:recent_tids`], '-inf', data[data.length - 1].score);
|
|
}
|
|
}
|
|
if (numRecentReplies > 0) {
|
|
await db.sortedSetAdd(`cid:${cid}:recent_tids`, Date.now(), tid);
|
|
}
|
|
await plugins.hooks.fire('action:categories.updateRecentTid', { cid: cid, tid: tid });
|
|
};
|
|
|
|
Categories.updateRecentTidForCid = async function (cid) {
|
|
let postData;
|
|
let topicData;
|
|
let index = 0;
|
|
do {
|
|
/* eslint-disable no-await-in-loop */
|
|
const pids = await db.getSortedSetRevRange(`cid:${cid}:pids`, index, index);
|
|
if (!pids.length) {
|
|
return;
|
|
}
|
|
postData = await posts.getPostFields(pids[0], ['tid', 'deleted']);
|
|
|
|
if (postData && postData.tid && !postData.deleted) {
|
|
topicData = await topics.getTopicData(postData.tid);
|
|
}
|
|
index += 1;
|
|
} while (!topicData || topicData.deleted || topicData.scheduled);
|
|
|
|
if (postData && postData.tid) {
|
|
await Categories.updateRecentTid(cid, postData.tid);
|
|
}
|
|
};
|
|
|
|
Categories.getRecentTopicReplies = async function (categoryData, uid, query) {
|
|
if (!Array.isArray(categoryData) || !categoryData.length) {
|
|
return;
|
|
}
|
|
const categoriesToLoad = categoryData.filter(c => c && c.numRecentReplies && parseInt(c.numRecentReplies, 10) > 0);
|
|
let keys = [];
|
|
if (plugins.hooks.hasListeners('filter:categories.getRecentTopicReplies')) {
|
|
const result = await plugins.hooks.fire('filter:categories.getRecentTopicReplies', {
|
|
categories: categoriesToLoad,
|
|
uid: uid,
|
|
query: query,
|
|
keys: [],
|
|
});
|
|
keys = result.keys;
|
|
} else {
|
|
keys = categoriesToLoad.map(c => `cid:${c.cid}:recent_tids`);
|
|
}
|
|
|
|
const results = await db.getSortedSetsMembers(keys);
|
|
let tids = _.uniq(_.flatten(results).filter(Boolean));
|
|
|
|
tids = await privileges.topics.filterTids('topics:read', tids, uid);
|
|
const topics = await getTopics(tids, uid);
|
|
assignTopicsToCategories(categoryData, topics);
|
|
|
|
bubbleUpChildrenPosts(categoryData);
|
|
};
|
|
|
|
async function getTopics(tids, uid) {
|
|
const topicData = await topics.getTopicsFields(
|
|
tids,
|
|
['tid', 'mainPid', 'slug', 'title', 'teaserPid', 'cid', 'postcount']
|
|
);
|
|
topicData.forEach((topic) => {
|
|
if (topic) {
|
|
topic.teaserPid = topic.teaserPid || topic.mainPid;
|
|
}
|
|
});
|
|
const cids = _.uniq(topicData.map(t => t && t.cid).filter(cid => parseInt(cid, 10)));
|
|
const getToRoot = async () => await Promise.all(cids.map(Categories.getParentCids));
|
|
const [toRoot, teasers] = await Promise.all([
|
|
getToRoot(),
|
|
topics.getTeasers(topicData, uid),
|
|
]);
|
|
const cidToRoot = _.zipObject(cids, toRoot);
|
|
|
|
teasers.forEach((teaser, index) => {
|
|
if (teaser) {
|
|
teaser.cid = topicData[index].cid;
|
|
teaser.parentCids = cidToRoot[teaser.cid];
|
|
teaser.tid = undefined;
|
|
teaser.uid = undefined;
|
|
teaser.topic = {
|
|
slug: topicData[index].slug,
|
|
title: topicData[index].title,
|
|
};
|
|
}
|
|
});
|
|
return teasers.filter(Boolean);
|
|
}
|
|
|
|
function assignTopicsToCategories(categories, topics) {
|
|
categories.forEach((category) => {
|
|
if (category) {
|
|
category.posts = topics.filter(t => t.cid && (t.cid === category.cid || t.parentCids.includes(category.cid)))
|
|
.sort((a, b) => b.timestamp - a.timestamp)
|
|
.slice(0, parseInt(category.numRecentReplies, 10));
|
|
}
|
|
});
|
|
topics.forEach((t) => { t.parentCids = undefined; });
|
|
}
|
|
|
|
function bubbleUpChildrenPosts(categoryData) {
|
|
categoryData.forEach((category) => {
|
|
if (category) {
|
|
if (category.posts.length) {
|
|
return;
|
|
}
|
|
const posts = [];
|
|
getPostsRecursive(category, posts);
|
|
|
|
posts.sort((a, b) => b.timestamp - a.timestamp);
|
|
if (posts.length) {
|
|
category.posts = [posts[0]];
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function getPostsRecursive(category, posts) {
|
|
if (Array.isArray(category.posts)) {
|
|
category.posts.forEach(p => posts.push(p));
|
|
}
|
|
|
|
category.children.forEach(child => getPostsRecursive(child, posts));
|
|
}
|
|
|
|
// terrible name, should be topics.moveTopicPosts
|
|
Categories.moveRecentReplies = async function (tid, oldCid, cid) {
|
|
const [pids, topicDeleted] = await Promise.all([
|
|
topics.getPids(tid),
|
|
topics.getTopicField(tid, 'deleted'),
|
|
]);
|
|
|
|
await batch.processArray(pids, async (pids) => {
|
|
const postData = await posts.getPostsFields(pids, ['pid', 'deleted', 'uid', 'timestamp', 'upvotes', 'downvotes']);
|
|
|
|
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 || post.votes < 0) {
|
|
bulkAdd.push([`cid:${cid}:uid:${post.uid}:pids:votes`, post.votes, post.pid]);
|
|
}
|
|
});
|
|
|
|
const postsToReAdd = postData.filter(p => !p.deleted && !topicDeleted);
|
|
const timestamps = postsToReAdd.map(p => p && p.timestamp);
|
|
await Promise.all([
|
|
db.sortedSetRemove(`cid:${oldCid}:pids`, pids),
|
|
db.sortedSetAdd(`cid:${cid}:pids`, timestamps, postsToReAdd.map(p => p.pid)),
|
|
db.sortedSetRemoveBulk(bulkRemove),
|
|
db.sortedSetAddBulk(bulkAdd),
|
|
]);
|
|
}, { batch: 500 });
|
|
};
|
|
};
|