From 5c271e89589b013e71cf43425b4a9344f9ebb927 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20U=C5=9Fakl=C4=B1?= Date: Thu, 5 Mar 2026 16:01:03 -0500 Subject: [PATCH 1/4] scan. glob to regex (#14063) * scan. glob to regex * lint --- src/database/helpers.js | 16 ++++++++++++++++ src/database/mongo/helpers.js | 18 ------------------ src/database/mongo/main.js | 4 ++-- src/database/mongo/sorted.js | 2 +- src/database/postgres/main.js | 13 ++++--------- test/database/keys.js | 21 +++++++++++++++++++++ 6 files changed, 44 insertions(+), 30 deletions(-) diff --git a/src/database/helpers.js b/src/database/helpers.js index 598c1d8812..eb8206f783 100644 --- a/src/database/helpers.js +++ b/src/database/helpers.js @@ -26,3 +26,19 @@ helpers.mergeBatch = function (batchData, start, stop, sort) { } while (item && (result.length < (stop - start + 1) || stop === -1)); return result; }; + +helpers.globToRegex = function (match) { + if (!match) { + return '^.*$'; + } + let _match = match.replace(/[.+^${}()|[\]\\]/g, '\\$&'); + _match = _match.replace(/\*/g, '.*').replace(/\?/g, '.'); + + if (!match.startsWith('*')) { + _match = '^' + _match; + } + if (!match.endsWith('*')) { + _match = _match + '$'; + } + return _match; +}; diff --git a/src/database/mongo/helpers.js b/src/database/mongo/helpers.js index 46ef8b39cb..a087da025b 100644 --- a/src/database/mongo/helpers.js +++ b/src/database/mongo/helpers.js @@ -1,7 +1,6 @@ 'use strict'; const helpers = module.exports; -const utils = require('../../utils'); helpers.noop = function () {}; @@ -50,20 +49,3 @@ helpers.valueToString = function (value) { return String(value); }; -helpers.buildMatchQuery = function (match) { - let _match = match; - if (match.startsWith('*')) { - _match = _match.substring(1); - } - if (match.endsWith('*')) { - _match = _match.substring(0, _match.length - 1); - } - _match = utils.escapeRegexChars(_match); - if (!match.startsWith('*')) { - _match = `^${_match}`; - } - if (!match.endsWith('*')) { - _match += '$'; - } - return _match; -}; diff --git a/src/database/mongo/main.js b/src/database/mongo/main.js index 884be9d2f7..67ff7ac8fa 100644 --- a/src/database/mongo/main.js +++ b/src/database/mongo/main.js @@ -1,7 +1,7 @@ 'use strict'; module.exports = function (module) { - const helpers = require('./helpers'); + const dbHelpers = require('../helpers'); module.flushdb = async function () { await module.client.dropDatabase(); }; @@ -39,7 +39,7 @@ module.exports = function (module) { }; module.scan = async function (params) { - const match = helpers.buildMatchQuery(params.match); + const match = dbHelpers.globToRegex(params.match); return await module.client.collection('objects').distinct( '_key', { _key: { $regex: new RegExp(match) } } ); diff --git a/src/database/mongo/sorted.js b/src/database/mongo/sorted.js index b24007e98a..eceea5e342 100644 --- a/src/database/mongo/sorted.js +++ b/src/database/mongo/sorted.js @@ -543,7 +543,7 @@ module.exports = function (module) { project.score = 1; } - const match = helpers.buildMatchQuery(params.match); + const match = dbHelpers.globToRegex(params.match); let regex; try { regex = new RegExp(match); diff --git a/src/database/postgres/main.js b/src/database/postgres/main.js index f082521a1d..6c397b8879 100644 --- a/src/database/postgres/main.js +++ b/src/database/postgres/main.js @@ -2,6 +2,7 @@ module.exports = function (module) { const helpers = require('./helpers'); + const dbHelpers = require('../helpers'); module.flushdb = async function () { await module.pool.query(`DROP SCHEMA "public" CASCADE`); @@ -84,20 +85,14 @@ module.exports = function (module) { }; module.scan = async function (params) { - let { match } = params; - if (match.startsWith('*')) { - match = `%${match.substring(1)}`; - } - if (match.endsWith('*')) { - match = `${match.substring(0, match.length - 1)}%`; - } + const regex = dbHelpers.globToRegex(params.match); const res = await module.pool.query({ text: ` SELECT o."_key" FROM "legacy_object_live" o - WHERE o."_key" LIKE $1`, - values: [match], + WHERE o."_key" ~ $1`, + values: [regex], }); return res.rows.map(r => r._key); diff --git a/test/database/keys.js b/test/database/keys.js index 71f2aa9baa..673d083f98 100644 --- a/test/database/keys.js +++ b/test/database/keys.js @@ -86,6 +86,27 @@ describe('Key methods', () => { assert(data.includes('ip:124:uid')); assert(data.includes('ip:1:uid')); }); + + it('should scan keys for specific glob pattern', async () => { + const keys = [ + 'tid:00000b86-a333-45ee-ba88-17e78aa6c814:recipients', + 'tid:00001fa5-61e1-4ba5-ac22-572ab98eb76f:recipients', + 'tid:00002ce3-203d-4b14-a201-b3d021f462a5:recipients', + 'other:123:stuff', + 'tid:999:sender', + ]; + await db.sortedSetAddBulk(keys.map(key => [key, 1, 'a'])); + + const data = await db.scan({ match: 'tid:*:recipients' }); + + assert.equal(data.length, 3, 'Should have found exactly 3 recipient keys'); + assert(data.includes('tid:00000b86-a333-45ee-ba88-17e78aa6c814:recipients')); + assert(data.includes('tid:00001fa5-61e1-4ba5-ac22-572ab98eb76f:recipients')); + assert(data.includes('tid:00002ce3-203d-4b14-a201-b3d021f462a5:recipients')); + + assert(!data.includes('other:123:stuff'), 'Should not include unrelated keys'); + assert(!data.includes('tid:999:sender'), 'Should not include sender keys'); + }); }); it('should delete a key without error', (done) => { From 8f960ff45bc64f282f1e5160a926f68024dbfe50 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Thu, 5 Mar 2026 16:01:36 -0500 Subject: [PATCH 2/4] fix(deps): update dependency multer to v2.1.1 (#14050) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- install/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/install/package.json b/install/package.json index cb5641217d..59db10e8b9 100644 --- a/install/package.json +++ b/install/package.json @@ -94,7 +94,7 @@ "mongodb": "7.1.0", "morgan": "1.10.1", "mousetrap": "1.6.5", - "multer": "2.0.2", + "multer": "2.1.1", "nconf": "0.13.0", "nodebb-plugin-2factor": "7.6.1", "nodebb-plugin-composer-default": "10.3.23", From 4534fad833357e902c9bceb9dd100ff729dc6065 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 5 Mar 2026 19:59:46 -0500 Subject: [PATCH 3/4] thumb fix --- src/views/partials/feed/item.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/views/partials/feed/item.tpl b/src/views/partials/feed/item.tpl index f46b9e87c5..f1d7dbc12c 100644 --- a/src/views/partials/feed/item.tpl +++ b/src/views/partials/feed/item.tpl @@ -10,7 +10,7 @@
{{{ each ./topic.thumbs }}} {{{ if (@index != 0) }}} - + {{{ end }}} {{{ end }}} {{{ if greaterthan(./topic.thumbs.length, "4") }}} From 75f52cadafe11d55b15f12e036f32665e049b588 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Thu, 5 Mar 2026 20:09:55 -0500 Subject: [PATCH 4/4] refactor: update upgrade script to scan for missing tid::recipients --- .../4.9.2/clean_tid_recipients_zsets.js | 31 +++++++++++++++++-- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/src/upgrades/4.9.2/clean_tid_recipients_zsets.js b/src/upgrades/4.9.2/clean_tid_recipients_zsets.js index 2206e1c040..eab3a8f612 100644 --- a/src/upgrades/4.9.2/clean_tid_recipients_zsets.js +++ b/src/upgrades/4.9.2/clean_tid_recipients_zsets.js @@ -17,21 +17,46 @@ module.exports = { ); const bulkRemove = []; - const deleteTids = new Set(); uids.forEach((uid, index) => { const userTids = userInboxes[index]; userTids.forEach((tid, tidIndex) => { if (!exists[index][tidIndex]) { bulkRemove.push([`uid:${uid}:inbox`, tid]); - deleteTids.add(tid); } }); }); await db.sortedSetRemoveBulk(bulkRemove); - await db.deleteAll(Array.from(deleteTids).map(tid => `tid:${tid}:recipients`)); + progress.incr(uids.length); }, { batch: 500, }); + + + const tidKeys = await db.scan({ match: 'tid:*:recipients' }); + progress.total = tidKeys.length; + progress.current = 0; + progress.counter = 0; + await batch.processArray(tidKeys, async (keys) => { + const tids = []; + keys.forEach((key) => { + const tid = key.split(':')[1]; + if (tid) { + tids.push(tid); + } + }); + const exists = await db.exists(tids.map(tid => `topic:${tid}`)); + const bulkDelete = []; + tids.forEach((tid, index) => { + if (!exists[index]) { + bulkDelete.push(`tid:${tid}:recipients`); + } + }); + await db.deleteAll(bulkDelete); + + progress.incr(keys.length); + }, { + batch: 500, + }); }, };