mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-04-09 22:20:00 +02:00
* refactor: allow passing an array to topics.purge and topics.purgePostsAndTopic deprecate action:topic.purge, add action:topics.purge with array of deleted topics update usage of topics.purge to pass in an array fix an issue in posts/delete where cids were passed to parseInt, caused AP cids to get saved into category:NaN * lint * refactor: change style * use array of tids
182 lines
5.8 KiB
JavaScript
182 lines
5.8 KiB
JavaScript
'use strict';
|
|
|
|
const winston = require('winston');
|
|
const _ = require('lodash');
|
|
const db = require('../database');
|
|
const topics = require('.');
|
|
const user = require('../user');
|
|
const categories = require('../categories');
|
|
const posts = require('../posts');
|
|
const privileges = require('../privileges');
|
|
const activitypub = require('../activitypub');
|
|
const utils = require('../utils');
|
|
|
|
const Crossposts = module.exports;
|
|
|
|
Crossposts.get = async function (tids) {
|
|
const isArray = Array.isArray(tids);
|
|
if (!isArray) {
|
|
tids = [tids];
|
|
}
|
|
|
|
const crosspostIds = await db.getSortedSetsMembers(tids.map(tid => `tid:${tid}:crossposts`));
|
|
const allCrosspostIds = crosspostIds.flat();
|
|
const allCrossposts = await db.getObjects(allCrosspostIds.map(id => `crosspost:${id}`));
|
|
|
|
const categoriesData = await categories.getCategoriesFields(
|
|
_.uniq(allCrossposts.map(c => c.cid)), ['cid', 'name', 'icon', 'bgColor', 'color', 'slug']
|
|
);
|
|
|
|
const categoriesMap = categoriesData.reduce((map, category) => {
|
|
map.set(parseInt(category.cid, 10), category);
|
|
return map;
|
|
}, new Map());
|
|
|
|
const crosspostMap = allCrossposts.reduce((map, crosspost, index) => {
|
|
const id = allCrosspostIds[index];
|
|
if (id && crosspost) {
|
|
map.set(id, crosspost);
|
|
crosspost.id = id;
|
|
crosspost.category = categoriesMap.get(parseInt(crosspost.cid, 10));
|
|
crosspost.uid = utils.isNumber(crosspost.uid) ? parseInt(crosspost.uid, 10) : crosspost.uid;
|
|
crosspost.cid = utils.isNumber(crosspost.cid) ? parseInt(crosspost.cid, 10) : crosspost.cid;
|
|
}
|
|
return map;
|
|
}, new Map());
|
|
|
|
const crossposts = crosspostIds.map(ids => ids.map(id => crosspostMap.get(id)));
|
|
return isArray ? crossposts : crossposts[0];
|
|
};
|
|
|
|
Crossposts.add = async function (tid, cid, uid) {
|
|
/**
|
|
* NOTE: If uid is 0, the assumption is that it is a "system" crosspost, not a guest!
|
|
* (Normally guest uid is 0)
|
|
*/
|
|
|
|
// Target cid must exist
|
|
if (!utils.isNumber(cid)) {
|
|
await activitypub.actors.assert(cid);
|
|
}
|
|
const [exists, allowed] = await Promise.all([
|
|
categories.exists(cid),
|
|
uid === 0 || privileges.categories.can('topics:crosspost', cid, uid),
|
|
]);
|
|
if (!exists) {
|
|
throw new Error('[[error:invalid-cid]]');
|
|
}
|
|
if (!allowed) {
|
|
throw new Error('[[error:not-allowed]]');
|
|
}
|
|
if (uid < 0) {
|
|
throw new Error('[[error:invalid-uid]]');
|
|
}
|
|
|
|
const crossposts = await Crossposts.get(tid);
|
|
const crosspostedCids = crossposts.map(crosspost => String(crosspost.cid));
|
|
const now = Date.now();
|
|
const crosspostId = utils.generateUUID();
|
|
if (!crosspostedCids.includes(String(cid))) {
|
|
const [topicData, pids] = await Promise.all([
|
|
topics.getTopicFields(tid, ['uid', 'cid', 'timestamp']),
|
|
topics.getPids(tid),
|
|
]);
|
|
let pidTimestamps = await posts.getPostsFields(pids, ['timestamp']);
|
|
pidTimestamps = pidTimestamps.map(({ timestamp }) => timestamp);
|
|
|
|
if (cid === topicData.cid) {
|
|
throw new Error('[[error:invalid-cid]]');
|
|
}
|
|
const zsets = [
|
|
`cid:${topicData.cid}:tids`,
|
|
`cid:${topicData.cid}:tids:create`,
|
|
`cid:${topicData.cid}:tids:lastposttime`,
|
|
`cid:${topicData.cid}:uid:${topicData.uid}:tids`,
|
|
`cid:${topicData.cid}:tids:votes`,
|
|
`cid:${topicData.cid}:tids:posts`,
|
|
`cid:${topicData.cid}:tids:views`,
|
|
];
|
|
const scores = await db.sortedSetsScore(zsets, tid);
|
|
const bulkAdd = zsets.map((zset, idx) => {
|
|
return [zset.replace(`cid:${topicData.cid}`, `cid:${cid}`), scores[idx], tid];
|
|
});
|
|
await Promise.all([
|
|
db.sortedSetAddBulk(bulkAdd),
|
|
db.sortedSetAdd(`cid:${cid}:pids`, pidTimestamps, pids),
|
|
db.setObject(`crosspost:${crosspostId}`, { uid, tid, cid, timestamp: now }),
|
|
db.sortedSetAdd(`tid:${tid}:crossposts`, now, crosspostId),
|
|
uid > 0 ? db.sortedSetAdd(`uid:${uid}:crossposts`, now, crosspostId) : false,
|
|
topics.events.log(tid, { uid, type: 'crosspost', toCid: cid }),
|
|
]);
|
|
await categories.onTopicsMoved([cid]); // must be done after
|
|
} else {
|
|
throw new Error('[[error:topic-already-crossposted]]');
|
|
}
|
|
|
|
return [...crossposts, { id: crosspostId, uid, tid, cid, timestamp: now }];
|
|
};
|
|
|
|
Crossposts.remove = async function (tid, cid, uid) {
|
|
let crossposts = await Crossposts.get(tid);
|
|
const [isPrivileged, isMod] = await Promise.all([
|
|
user.isAdminOrGlobalMod(uid),
|
|
user.isModerator(uid, cid),
|
|
]);
|
|
const crosspostId = crossposts.reduce((id, { id: _id, cid: _cid, uid: _uid }) => {
|
|
if (String(cid) === String(_cid) && (isPrivileged || isMod || String(uid) === String(_uid))) {
|
|
id = _id;
|
|
}
|
|
|
|
return id;
|
|
}, null);
|
|
if (!crosspostId) {
|
|
throw new Error('[[error:invalid-data]]');
|
|
}
|
|
|
|
const [author, pids] = await Promise.all([
|
|
topics.getTopicField(tid, 'uid'),
|
|
topics.getPids(tid),
|
|
]);
|
|
let bulkRemove = [
|
|
`cid:${cid}:tids`,
|
|
`cid:${cid}:tids:create`,
|
|
`cid:${cid}:tids:lastposttime`,
|
|
`cid:${cid}:uid:${author}:tids`,
|
|
`cid:${cid}:tids:votes`,
|
|
`cid:${cid}:tids:posts`,
|
|
`cid:${cid}:tids:views`,
|
|
];
|
|
bulkRemove = bulkRemove.map(zset => [zset, tid]);
|
|
|
|
await Promise.all([
|
|
db.sortedSetRemoveBulk(bulkRemove),
|
|
db.delete(`crosspost:${crosspostId}`),
|
|
db.sortedSetRemove(`tid:${tid}:crossposts`, crosspostId),
|
|
db.sortedSetRemove(`cid:${cid}:pids`, pids),
|
|
uid > 0 ? db.sortedSetRemove(`uid:${uid}:crossposts`, crosspostId) : false,
|
|
]);
|
|
await categories.onTopicsMoved([cid]);
|
|
|
|
topics.events.find(tid, { uid, toCid: cid, type: 'crosspost' }).then((eventIds) => {
|
|
topics.events.purge(tid, eventIds);
|
|
}).catch(err => winston.error(err));
|
|
|
|
crossposts = await Crossposts.get(tid);
|
|
return crossposts;
|
|
};
|
|
|
|
Crossposts.removeAll = async function (tids) {
|
|
if (!Array.isArray(tids)) {
|
|
tids = [tids];
|
|
}
|
|
const allCrosspostIds = (await db.getSortedSetsMembers(
|
|
tids.map(tid => `tid:${tid}:crossposts`)
|
|
)).flat();
|
|
const crossposts = (await db.getObjects(
|
|
allCrosspostIds.map(id => `crosspost:${id}`)
|
|
)).filter(Boolean);
|
|
|
|
await Promise.all(
|
|
crossposts.map(({ tid, cid, uid }) => Crossposts.remove(tid, cid, uid))
|
|
);
|
|
}; |