refactor: generate a topic id when asserting a new topic

This commit is contained in:
Julian Lam
2024-01-19 11:31:04 -05:00
parent 981b4f146d
commit 2ff70fdde2
9 changed files with 39 additions and 34 deletions

View File

@@ -73,14 +73,17 @@ Notes.getParentChain = async (uid, input) => {
return chain; return chain;
}; };
Notes.assertParentChain = async (chain) => { Notes.assertParentChain = async (chain, tid) => {
const data = []; const data = [];
chain.reduce((child, parent) => { chain.reduce((child, parent) => {
data.push([`pid:${parent.pid}:replies`, child.timestamp, child.pid]); data.push([`pid:${parent.pid}:replies`, child.timestamp, child.pid]);
return parent; return parent;
}); });
await db.sortedSetAddBulk(data); await Promise.all([
db.sortedSetAddBulk(data),
db.setObjectBulk(chain.map(post => [`post:${post.pid}`, { tid }])),
]);
}; };
Notes.assertTopic = async (uid, id) => { Notes.assertTopic = async (uid, id) => {
@@ -92,22 +95,22 @@ Notes.assertTopic = async (uid, id) => {
*/ */
const chain = Array.from(await Notes.getParentChain(uid, id)); const chain = Array.from(await Notes.getParentChain(uid, id));
const { pid: tid, uid: authorId, timestamp, name, content } = chain[chain.length - 1]; let { pid: mainPid, tid, uid: authorId, timestamp, name, content } = chain[chain.length - 1];
const members = await db.isSortedSetMembers(`tidRemote:${tid}:posts`, chain.map(p => p.pid)); const members = await db.isSortedSetMembers(`tidRemote:${tid}:posts`, chain.map(p => p.pid));
if (members.every(Boolean)) { if (tid && members.every(Boolean)) {
// All cached, return early. // All cached, return early.
winston.info('[notes/assertTopic] No new notes to process.'); winston.info('[notes/assertTopic] No new notes to process.');
return tid; return tid;
} }
let title = name || utils.stripHTMLTags(content); tid = tid || utils.generateUUID();
if (title.length > 256) {
title = `${title.slice(0, 256)}...`;
}
const cid = await topics.getTopicField(tid, 'cid'); const cid = await topics.getTopicField(tid, 'cid');
let title = name || utils.decodeHTMLEntities(utils.stripHTMLTags(content));
if (title.length > 64) {
title = `${title.slice(0, 64)}...`;
}
const unprocessed = chain.filter((p, idx) => !members[idx]); const unprocessed = chain.filter((p, idx) => !members[idx]);
winston.info(`[notes/assertTopic] ${unprocessed.length} new note(s) found.`); winston.info(`[notes/assertTopic] ${unprocessed.length} new note(s) found.`);
@@ -121,16 +124,16 @@ Notes.assertTopic = async (uid, id) => {
tid, tid,
uid: authorId, uid: authorId,
cid: cid || -1, cid: cid || -1,
mainPid: tid, mainPid,
title, title,
slug: `remote?resource=${encodeURIComponent(tid)}`, slug: `../world/${tid}`,
timestamp, timestamp,
}), }),
db.sortedSetAdd(`tidRemote:${tid}:posts`, timestamps, ids), db.sortedSetAdd(`tidRemote:${tid}:posts`, timestamps, ids),
Notes.assert(uid, unprocessed), Notes.assert(uid, unprocessed),
]); ]);
await Promise.all([ // must be done after .assert() await Promise.all([ // must be done after .assert()
Notes.assertParentChain(chain), Notes.assertParentChain(chain, tid),
Notes.updateTopicCounts(tid), Notes.updateTopicCounts(tid),
topics.updateLastPostTimeFromLastPid(tid), topics.updateLastPostTimeFromLastPid(tid),
topics.updateTeaser(tid), topics.updateTeaser(tid),

View File

@@ -44,14 +44,13 @@ controller.get = async function (req, res, next) {
* Ideally we would use the existing topicsController.get... * Ideally we would use the existing topicsController.get...
* this controller may be a stopgap towards that end goal. * this controller may be a stopgap towards that end goal.
*/ */
const pid = await notes.resolveId(req.uid, req.query.resource); // const pid = await notes.resolveId(req.uid, req.query.resource);
if (pid !== req.query.resource) { // if (pid !== req.query.resource) {
return helpers.redirect(res, `/topic/remote?resource=${pid}`, true); // return helpers.redirect(res, `/topic/remote?resource=${pid}`, true);
} // }
const tid = await notes.assertTopic(req.uid, req.query.resource); const tid = req.params.topic_id;
let postIndex = parseInt(req.params.post_index, 10) || 1;
let postIndex = Math.max(1, await db.sortedSetRank(`tidRemote:${tid}:posts`, req.query.resource));
const [ const [
userPrivileges, userPrivileges,
settings, settings,

View File

@@ -61,6 +61,7 @@ function modifyPost(post, fields) {
if (activitypub.helpers.isUri(post.pid)) { if (activitypub.helpers.isUri(post.pid)) {
intFields.splice(intFields.indexOf('pid'), 1); intFields.splice(intFields.indexOf('pid'), 1);
intFields.splice(intFields.indexOf('uid'), 1); intFields.splice(intFields.indexOf('uid'), 1);
intFields.splice(intFields.indexOf('tid'), 1);
} }
db.parseIntFields(post, intFields, fields); db.parseIntFields(post, intFields, fields);
if (post.hasOwnProperty('upvotes') && post.hasOwnProperty('downvotes')) { if (post.hasOwnProperty('upvotes') && post.hasOwnProperty('downvotes')) {

View File

@@ -3,7 +3,8 @@
const helpers = require('./helpers'); const helpers = require('./helpers');
module.exports = function (app, middleware, controllers) { module.exports = function (app, middleware, controllers) {
helpers.setupPageRoute(app, '/world/:view?', [middleware.activitypub.enabled], controllers.activitypub.topics.list); helpers.setupPageRoute(app, '/world', [middleware.activitypub.enabled], controllers.activitypub.topics.list);
helpers.setupPageRoute(app, '/world/:topic_id/:post_index?', [middleware.activitypub.enabled], controllers.activitypub.topics.get);
/** /**
* These controllers only respond if the sender is making an json+activitypub style call (i.e. S2S-only) * These controllers only respond if the sender is making an json+activitypub style call (i.e. S2S-only)

View File

@@ -1,11 +1,12 @@
'use strict'; 'use strict';
const validator = require('validator');
const topics = require('../../topics'); const topics = require('../../topics');
const privileges = require('../../privileges'); const privileges = require('../../privileges');
const meta = require('../../meta'); const meta = require('../../meta');
const utils = require('../../utils'); const utils = require('../../utils');
const social = require('../../social'); const social = require('../../social');
const activitypub = require('../../activitypub');
module.exports = function (SocketTopics) { module.exports = function (SocketTopics) {
SocketTopics.loadMore = async function (socket, data) { SocketTopics.loadMore = async function (socket, data) {
@@ -22,7 +23,7 @@ module.exports = function (SocketTopics) {
throw new Error('[[error:no-privileges]]'); throw new Error('[[error:no-privileges]]');
} }
const setPrefix = activitypub.helpers.isUri(data.tid) ? 'tidRemote' : 'tid'; const setPrefix = validator.isUUID(String(data.tid)) ? 'tidRemote' : 'tid';
const set = data.topicPostSort === 'most_votes' ? `${setPrefix}:${data.tid}:posts:votes` : `${setPrefix}:${data.tid}:posts`; const set = data.topicPostSort === 'most_votes' ? `${setPrefix}:${data.tid}:posts:votes` : `${setPrefix}:${data.tid}:posts`;
const reverse = data.topicPostSort === 'newest_to_oldest' || data.topicPostSort === 'most_votes'; const reverse = data.topicPostSort === 'newest_to_oldest' || data.topicPostSort === 'most_votes';
let start = Math.max(0, parseInt(data.after, 10)); let start = Math.max(0, parseInt(data.after, 10));

View File

@@ -7,7 +7,6 @@ const categories = require('../categories');
const utils = require('../utils'); const utils = require('../utils');
const translator = require('../translator'); const translator = require('../translator');
const plugins = require('../plugins'); const plugins = require('../plugins');
const activitypub = require('../activitypub');
const intFields = [ const intFields = [
'tid', 'cid', 'uid', 'mainPid', 'postcount', 'tid', 'cid', 'uid', 'mainPid', 'postcount',
@@ -27,7 +26,7 @@ module.exports = function (Topics) {
fields.push('timestamp'); fields.push('timestamp');
} }
const keys = tids.map(tid => `${activitypub.helpers.isUri(tid) ? 'topicRemote' : 'topic'}:${tid}`); const keys = tids.map(tid => `${validator.isUUID(String(tid)) ? 'topicRemote' : 'topic'}:${tid}`);
const topics = await db.getObjects(keys, fields); const topics = await db.getObjects(keys, fields);
const result = await plugins.hooks.fire('filter:topic.getFields', { const result = await plugins.hooks.fire('filter:topic.getFields', {
tids: tids, tids: tids,
@@ -64,12 +63,12 @@ module.exports = function (Topics) {
}; };
Topics.setTopicField = async function (tid, field, value) { Topics.setTopicField = async function (tid, field, value) {
const setPrefix = activitypub.helpers.isUri(tid) ? 'topicRemote' : 'topic'; const setPrefix = validator.isUUID(String(tid)) ? 'topicRemote' : 'topic';
await db.setObjectField(`${setPrefix}:${tid}`, field, value); await db.setObjectField(`${setPrefix}:${tid}`, field, value);
}; };
Topics.setTopicFields = async function (tid, data) { Topics.setTopicFields = async function (tid, data) {
const setPrefix = activitypub.helpers.isUri(tid) ? 'topicRemote' : 'topic'; const setPrefix = validator.isUUID(String(tid)) ? 'topicRemote' : 'topic';
await db.setObject(`${setPrefix}:${tid}`, data); await db.setObject(`${setPrefix}:${tid}`, data);
}; };
@@ -98,7 +97,7 @@ function modifyTopic(topic, fields) {
return; return;
} }
if (activitypub.helpers.isUri(topic.tid)) { if (validator.isUUID(String(topic.tid))) {
intFields.splice(intFields.indexOf('uid'), 1); intFields.splice(intFields.indexOf('uid'), 1);
intFields.splice(intFields.indexOf('tid'), 1); intFields.splice(intFields.indexOf('tid'), 1);
} }

View File

@@ -72,10 +72,10 @@ Topics.getTopicsByTids = async function (tids, options) {
const topics = await Topics.getTopicsData(tids); const topics = await Topics.getTopicsData(tids);
const uids = _.uniq(topics const uids = _.uniq(topics
.map(t => t && t.uid && t.uid.toString()) .map(t => t && t.uid && t.uid.toString())
.filter(v => utils.isNumber(v) || activitypub.helpers.isUri(v))); .filter(v => utils.isNumber(v) || validator.isUUID(String(v))));
const cids = _.uniq(topics const cids = _.uniq(topics
.map(t => t && t.cid && t.cid.toString()) .map(t => t && t.cid && t.cid.toString())
.filter(v => utils.isNumber(v) || activitypub.helpers.isUri(v))); .filter(v => utils.isNumber(v) || validator.isUUID(String(v))));
const guestTopics = topics.filter(t => t && t.uid === 0); const guestTopics = topics.filter(t => t && t.uid === 0);
async function loadGuestHandles() { async function loadGuestHandles() {

View File

@@ -231,7 +231,7 @@ module.exports = function (Topics) {
Topics.getLatestUndeletedReply = async function (tid) { Topics.getLatestUndeletedReply = async function (tid) {
let isDeleted = false; let isDeleted = false;
let index = 0; let index = 0;
const setPrefix = activitypub.helpers.isUri(tid) ? 'tidRemote' : 'tid'; const setPrefix = validator.isUUID(String(tid)) ? 'tidRemote' : 'tid';
do { do {
/* eslint-disable no-await-in-loop */ /* eslint-disable no-await-in-loop */
const pids = await db.getSortedSetRevRange(`${setPrefix}:${tid}:posts`, index, index); const pids = await db.getSortedSetRevRange(`${setPrefix}:${tid}:posts`, index, index);
@@ -312,7 +312,7 @@ module.exports = function (Topics) {
}; };
async function incrementFieldAndUpdateSortedSet(tid, field, by, set) { async function incrementFieldAndUpdateSortedSet(tid, field, by, set) {
const value = await db.incrObjectFieldBy(`${activitypub.helpers.isUri(tid) ? 'topicRemote' : 'topic'}:${tid}`, field, by); const value = await db.incrObjectFieldBy(`${validator.isUUID(String(tid)) ? 'topicRemote' : 'topic'}:${tid}`, field, by);
await db[Array.isArray(set) ? 'sortedSetsAdd' : 'sortedSetAdd'](set, value, tid); await db[Array.isArray(set) ? 'sortedSetsAdd' : 'sortedSetAdd'](set, value, tid);
} }

View File

@@ -1,10 +1,11 @@
'use strict'; 'use strict';
const validator = require('validator');
const db = require('../database'); const db = require('../database');
const plugins = require('../plugins'); const plugins = require('../plugins');
const posts = require('../posts'); const posts = require('../posts');
const activitypub = require('../activitypub');
module.exports = function (Topics) { module.exports = function (Topics) {
const terms = { const terms = {
@@ -74,7 +75,7 @@ module.exports = function (Topics) {
data = await plugins.hooks.fire('filter:topics.updateRecent', { tid: tid, timestamp: timestamp }); data = await plugins.hooks.fire('filter:topics.updateRecent', { tid: tid, timestamp: timestamp });
} }
if (data && data.tid && data.timestamp) { if (data && data.tid && data.timestamp) {
const setPrefix = activitypub.helpers.isUri(data.tid) ? 'topicsRemote' : 'topics'; const setPrefix = validator.isUUID(String(data.tid)) ? 'topicsRemote' : 'topics';
await db.sortedSetAdd(`${setPrefix}:recent`, data.timestamp, data.tid); await db.sortedSetAdd(`${setPrefix}:recent`, data.timestamp, data.tid);
} }
}; };