fix: #14147, dont create wrong backlinks

syncBacklinks expects raw post content, html was passed to it from onNewPost since it was switched to use getPostSummary
backlinkRegex was matching  ${nconf.get('url')}/topic/1aef954c-d0dc-45cf-acf2-e3a59f6cc134/foo and return tid=1 instead of the uuid

remove useless if check in syncBacklinks
fixed parseInts on tids
current - remove are both arrays use .length
This commit is contained in:
Barış Soner Uşaklı
2026-04-03 21:54:05 -04:00
parent 072b1e864d
commit 0568ef4310
3 changed files with 28 additions and 23 deletions

View File

@@ -259,14 +259,14 @@ module.exports = function (Topics) {
return postData; return postData;
}; };
async function onNewPost({ pid, tid, uid: postOwner }, { uid, handle }) { async function onNewPost({ pid, tid, content, uid: postOwner }, { uid, handle }) {
const [[postData], [userInfo]] = await Promise.all([ const [[postData], [userInfo]] = await Promise.all([
posts.getPostSummaryByPids([pid], uid, { extraFields: ['attachments'] }), posts.getPostSummaryByPids([pid], uid, { extraFields: ['attachments'] }),
posts.getUserInfoForPosts([postOwner], uid), posts.getUserInfoForPosts([postOwner], uid),
]); ]);
await Promise.all([ await Promise.all([
Topics.addParentPosts([postData], uid), Topics.addParentPosts([postData], uid),
Topics.syncBacklinks(postData), Topics.syncBacklinks({ ...postData, content }),
Topics.markAsRead([tid], uid), Topics.markAsRead([tid], uid),
]); ]);
if (utils.isNumber(postOwner) && postData.category.cid === -1) { if (utils.isNumber(postOwner) && postData.category.cid === -1) {

View File

@@ -14,7 +14,7 @@ const plugins = require('../plugins');
const utils = require('../utils'); const utils = require('../utils');
const privileges = require('../privileges'); const privileges = require('../privileges');
const backlinkRegex = new RegExp(`(?:${nconf.get('url').replace('/', '\\/')}|\b|\\s)\\/topic\\/(\\d+)(?:\\/\\w+)?`, 'g'); const backlinkRegex = new RegExp(`(?:${nconf.get('url').replace('/', '\\/')}|\b|\\s)\\/topic\\/([a-fA-F0-9-]+)(?=\\/|$|\\s)?`, 'g');
module.exports = function (Topics) { module.exports = function (Topics) {
Topics.onNewPostMade = async function (postData) { Topics.onNewPostMade = async function (postData) {
@@ -447,29 +447,27 @@ module.exports = function (Topics) {
throw new Error('[[error:invalid-data]]'); throw new Error('[[error:invalid-data]]');
} }
let { pid, uid, content } = postData;
let { content } = postData;
// ignore lines that start with `>` // ignore lines that start with `>`
content = (content || '').split('\n').filter(line => !line.trim().startsWith('>')).join('\n'); content = (content || '').split('\n').filter(line => !line.trim().startsWith('>')).join('\n');
// Scan post content for topic links // Scan post content for topic links
const matches = [...content.matchAll(backlinkRegex)]; const matches = [...content.matchAll(backlinkRegex)];
if (!matches) {
return 0;
}
const { pid, uid, tid } = postData; let add = _.uniq(matches.map(match => match[1]));
let add = _.uniq(matches.map(match => match[1]).map(tid => parseInt(tid, 10)));
const now = Date.now(); const [topicsExist, current] = await Promise.all([
const topicsExist = await Topics.exists(add); Topics.exists(add),
const current = (await db.getSortedSetMembers(`pid:${pid}:backlinks`)).map(tid => parseInt(tid, 10)); db.getSortedSetMembers(`pid:${pid}:backlinks`),
]);
const remove = current.filter(tid => !add.includes(tid)); const remove = current.filter(tid => !add.includes(tid));
add = add.filter((_tid, idx) => topicsExist[idx] && !current.includes(_tid) && tid !== _tid); const postTid = String(postData.tid);
add = add.filter((_tid, idx) => topicsExist[idx] && !current.includes(_tid) && postTid !== _tid);
// Remove old backlinks // Remove old backlinks
await db.sortedSetRemove(`pid:${pid}:backlinks`, remove); await db.sortedSetRemove(`pid:${pid}:backlinks`, remove);
// Add new backlinks // Add new backlinks
const now = Date.now();
await db.sortedSetAdd(`pid:${pid}:backlinks`, add.map(() => now), add); await db.sortedSetAdd(`pid:${pid}:backlinks`, add.map(() => now), add);
await Promise.all(add.map(async (tid) => { await Promise.all(add.map(async (tid) => {
await Topics.events.log(tid, { await Topics.events.log(tid, {
@@ -479,6 +477,6 @@ module.exports = function (Topics) {
}); });
})); }));
return add.length + (current - remove); return add.length + (current.length - remove.length);
}; };
}; };

View File

@@ -1152,12 +1152,10 @@ describe('Post\'s', () => {
describe('.syncBacklinks()', () => { describe('.syncBacklinks()', () => {
it('should error on invalid data', async () => { it('should error on invalid data', async () => {
try { await assert.rejects(
await topics.syncBacklinks(); topics.syncBacklinks(),
} catch (e) { { message: '[[error:invalid-data]]' },
assert(e); );
assert.strictEqual(e.message, '[[error:invalid-data]]');
}
}); });
it('should do nothing if the post does not contain a link to a topic', async () => { it('should do nothing if the post does not contain a link to a topic', async () => {
@@ -1192,9 +1190,7 @@ describe('Post\'s', () => {
const backlinks = await db.getSortedSetMembers('pid:2:backlinks'); const backlinks = await db.getSortedSetMembers('pid:2:backlinks');
assert.strictEqual(count, 0); assert.strictEqual(count, 0);
assert(events);
assert.strictEqual(events.length, 1); assert.strictEqual(events.length, 1);
assert(backlinks);
assert.strictEqual(backlinks.length, 0); assert.strictEqual(backlinks.length, 0);
}); });
@@ -1219,6 +1215,17 @@ describe('Post\'s', () => {
assert(backlinks); assert(backlinks);
assert.strictEqual(backlinks.length, 0); assert.strictEqual(backlinks.length, 0);
}); });
it('should not create a wrong backlink to topic/1 with AP topic url', async () => {
const { postData } = await topics.post({
uid: 1,
cid,
title: 'Topic backlink testing - topic 2',
content: `testing ${nconf.get('url')}/topic/1aef954c-d0dc-45cf-acf2-e3a59f6cc134/foo`,
});
const backlinks = await db.getSortedSetMembers(`pid:${postData.pid}:backlinks`);
assert.strictEqual(backlinks.length, 0);
});
}); });
describe('integration tests', () => { describe('integration tests', () => {