From b04976ed3553aac681d2462c3d67902dbdcd0c8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 28 Mar 2026 13:35:47 -0400 Subject: [PATCH 1/4] test: dont create users parallel --- test/socket.io.js | 18 ++++++------------ 1 file changed, 6 insertions(+), 12 deletions(-) diff --git a/test/socket.io.js b/test/socket.io.js index 45ffb43dd8..209f8dc056 100644 --- a/test/socket.io.js +++ b/test/socket.io.js @@ -30,20 +30,14 @@ describe('socket.io', () => { let regularUid; before(async () => { - const data = await Promise.all([ - user.create({ username: 'admin', password: 'adminpwd' }), - user.create({ username: 'regular', password: 'regularpwd', email: 'regular@test.com' }, { emailVerification: 'verify' }), - categories.create({ - name: 'Test Category', - description: 'Test category created by testing script', - }), - ]); - adminUid = data[0]; + adminUid = await user.create({ username: 'admin', password: 'adminpwd' }); await groups.join('administrators', adminUid); + regularUid = await user.create({ username: 'regular', password: 'regularpwd', email: 'regular@test.com' }, { emailVerification: 'verify' }); + ({ cid } = await categories.create({ + name: 'Test Category', + description: 'Test category created by testing script', + })); - regularUid = data[1]; - - cid = data[2].cid; await topics.post({ uid: adminUid, cid: cid, From 991e9778130d5b114b9c7955296d7b71eb03c5cf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 28 Mar 2026 14:32:04 -0400 Subject: [PATCH 2/4] fix: try upsert type if it fails --- src/database/postgres/helpers.js | 36 ++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 11 deletions(-) diff --git a/src/database/postgres/helpers.js b/src/database/postgres/helpers.js index 8b92d3fe50..35c41012de 100644 --- a/src/database/postgres/helpers.js +++ b/src/database/postgres/helpers.js @@ -27,14 +27,14 @@ DELETE FROM "legacy_object" AND "expireAt" <= CURRENT_TIMESTAMP`, }); - const res = await db.query({ + const res = await tryUpsert(db, { name: 'ensureLegacyObjectType_upsert', text: ` -INSERT INTO "legacy_object" ("_key", "type") -VALUES ($1::TEXT, $2::TEXT::LEGACY_OBJECT_TYPE) - ON CONFLICT ("_key") - DO UPDATE SET "type" = "legacy_object"."type" - RETURNING "type"`, + INSERT INTO "legacy_object" ("_key", "type") + VALUES ($1::TEXT, $2::TEXT::LEGACY_OBJECT_TYPE) + ON CONFLICT ("_key") + DO UPDATE SET "type" = "legacy_object"."type" + RETURNING "type"`, values: [key, type], }); @@ -54,15 +54,15 @@ DELETE FROM "legacy_object" AND "expireAt" <= CURRENT_TIMESTAMP`, }); - const res = await db.query({ + const res = await tryUpsert(db, { name: 'ensureLegacyObjectsType_upsert', text: ` INSERT INTO "legacy_object" ("_key", "type") SELECT k, $2::TEXT::LEGACY_OBJECT_TYPE - FROM UNNEST($1::TEXT[]) k - ON CONFLICT ("_key") - DO UPDATE SET "type" = "legacy_object"."type" - RETURNING "_key", "type"`, +FROM UNNEST($1::TEXT[]) k + ON CONFLICT ("_key") + DO UPDATE SET "type" = "legacy_object"."type" + RETURNING "_key", "type"`, values: [keys, type], }); @@ -74,4 +74,18 @@ SELECT k, $2::TEXT::LEGACY_OBJECT_TYPE } }; +async function tryUpsert(db, queryConfig) { + let res; + try { + res = await db.query(queryConfig); + } catch (err) { + if (err.code === '23505') { // retry if failed due to error: unique constraint + res = await db.query(queryConfig); + } else { + throw err; + } + } + return res; +} + helpers.noop = function () {}; From 203f4cc7ff412dd8df5b806bb7fd572cc55904ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Sat, 28 Mar 2026 15:09:03 -0400 Subject: [PATCH 3/4] fix: try a save point in retry --- src/database/postgres/helpers.js | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/database/postgres/helpers.js b/src/database/postgres/helpers.js index 35c41012de..e3df40bad5 100644 --- a/src/database/postgres/helpers.js +++ b/src/database/postgres/helpers.js @@ -45,7 +45,9 @@ DELETE FROM "legacy_object" helpers.ensureLegacyObjectsType = async function (db, keys, type) { keys = [...new Set(keys)]; - + if (!keys.length) { + return; + } await db.query({ name: 'ensureLegacyObjectTypeBefore', text: ` @@ -76,11 +78,18 @@ FROM UNNEST($1::TEXT[]) k async function tryUpsert(db, queryConfig) { let res; + const savepoint = `upsert_${Math.random().toString(36).substring(7)}`; try { + await db.query(`SAVEPOINT ${savepoint}`); res = await db.query(queryConfig); + await db.query(`RELEASE SAVEPOINT ${savepoint}`); } catch (err) { if (err.code === '23505') { // retry if failed due to error: unique constraint + // Roll back to the savepoint to prevent + // error: current transaction is aborted, commands ignored until end of transaction block + await db.query(`ROLLBACK TO SAVEPOINT ${savepoint}`); res = await db.query(queryConfig); + await db.query(`RELEASE SAVEPOINT ${savepoint}`); } else { throw err; } From af0e3d96898eb4ca51917f49d94bf894bac1a3fe Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bar=C4=B1=C5=9F=20Soner=20U=C5=9Fakl=C4=B1?= Date: Mon, 30 Mar 2026 09:45:07 -0400 Subject: [PATCH 4/4] fix: closes #14133, don't modify displayName for system groups added a helper to just modify it for front end --- install/package.json | 6 +++--- public/src/modules/helpers.common.js | 5 +++++ src/groups/data.js | 3 --- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/install/package.json b/install/package.json index 42a5dda294..01011ea150 100644 --- a/install/package.json +++ b/install/package.json @@ -108,10 +108,10 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.7", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.2.62", + "nodebb-theme-harmony": "2.2.63", "nodebb-theme-lavender": "7.1.21", - "nodebb-theme-peace": "2.2.57", - "nodebb-theme-persona": "14.2.33", + "nodebb-theme-peace": "2.2.58", + "nodebb-theme-persona": "14.2.34", "nodebb-widget-essentials": "7.0.43", "nodemailer": "8.0.3", "nprogress": "0.2.0", diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index 4893cea2fd..94a2d25307 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -17,6 +17,7 @@ module.exports = function (utils, Benchpress, relative_path) { generateCategoryBackground, generateChildrenCategories, generateTopicClass, + generateGroupDisplayName, membershipBtn, spawnPrivilegeStates, localeToHTML, @@ -167,6 +168,10 @@ module.exports = function (utils, Benchpress, relative_path) { return fields.filter(field => !!topic[field]).join(' '); } + function generateGroupDisplayName(group) { + return group.system ? group.displayName.replace(/-/g, ' ') : group.displayName; + } + // Groups helpers function membershipBtn(groupObj, btnClass = '') { if (groupObj.isMember && groupObj.name !== 'administrators') { diff --git a/src/groups/data.js b/src/groups/data.js index bef4d82fec..e585b653b5 100644 --- a/src/groups/data.js +++ b/src/groups/data.js @@ -133,9 +133,6 @@ module.exports = function (Groups) { if (hasField('name')) { group.nameEncoded = encodeURIComponent(group.name); group.displayName = validator.escape(String(group.name)); - if (Groups.systemGroups.includes(group.name)) { - group.displayName = group.displayName.replace(/-/g, ' '); - } } if (hasField('description')) { group.description = validator.escape(String(group.description || ''));