diff --git a/install/package.json b/install/package.json index 55e47bb9e4..15e29df711 100644 --- a/install/package.json +++ b/install/package.json @@ -107,10 +107,10 @@ "nodebb-plugin-spam-be-gone": "2.3.1", "nodebb-plugin-web-push": "0.7.3", "nodebb-rewards-essentials": "1.0.1", - "nodebb-theme-harmony": "2.1.6", + "nodebb-theme-harmony": "2.1.7", "nodebb-theme-lavender": "7.1.18", - "nodebb-theme-peace": "2.2.39", - "nodebb-theme-persona": "14.1.5", + "nodebb-theme-peace": "2.2.40", + "nodebb-theme-persona": "14.1.6", "nodebb-widget-essentials": "7.0.36", "nodemailer": "6.10.0", "nprogress": "0.2.0", diff --git a/public/language/en-GB/global.json b/public/language/en-GB/global.json index 4ca7816332..7e66953d0a 100644 --- a/public/language/en-GB/global.json +++ b/public/language/en-GB/global.json @@ -95,6 +95,7 @@ "downvoted": "Downvoted", "views": "Views", "posters": "Posters", + "watching": "Watching", "reputation": "Reputation", "lastpost": "Last post", "firstpost": "First post", diff --git a/public/openapi/components/schemas/TopicObject.yaml b/public/openapi/components/schemas/TopicObject.yaml index 3789aa9518..b26623072e 100644 --- a/public/openapi/components/schemas/TopicObject.yaml +++ b/public/openapi/components/schemas/TopicObject.yaml @@ -218,6 +218,8 @@ TopicObjectSlim: type: number postercount: type: number + followercount: + type: number scheduled: type: number deleted: diff --git a/src/topics/data.js b/src/topics/data.js index d411cbf329..76c027121d 100644 --- a/src/topics/data.js +++ b/src/topics/data.js @@ -10,9 +10,10 @@ const plugins = require('../plugins'); const intFields = [ 'tid', 'cid', 'uid', 'mainPid', 'postcount', - 'viewcount', 'postercount', 'deleted', 'locked', 'pinned', - 'pinExpiry', 'timestamp', 'upvotes', 'downvotes', 'lastposttime', - 'deleterUid', + 'viewcount', 'postercount', 'followercount', + 'deleted', 'locked', 'pinned', 'pinExpiry', + 'timestamp', 'upvotes', 'downvotes', + 'lastposttime', 'deleterUid', ]; module.exports = function (Topics) { diff --git a/src/topics/follow.js b/src/topics/follow.js index a89dfa57c5..d1afb7025c 100644 --- a/src/topics/follow.js +++ b/src/topics/follow.js @@ -48,29 +48,30 @@ module.exports = function (Topics) { } async function follow(tid, uid) { - await addToSets(`tid:${tid}:followers`, `uid:${uid}:followed_tids`, tid, uid); + await db.setAdd(`tid:${tid}:followers`, uid); + await db.sortedSetAdd(`uid:${uid}:followed_tids`, Date.now(), tid); + await updateFollowerCount(tid); } async function unfollow(tid, uid) { - await removeFromSets(`tid:${tid}:followers`, `uid:${uid}:followed_tids`, tid, uid); + await db.setRemove(`tid:${tid}:followers`, uid); + await db.sortedSetRemove(`uid:${uid}:followed_tids`, tid); + await updateFollowerCount(tid); } async function ignore(tid, uid) { - await addToSets(`tid:${tid}:ignorers`, `uid:${uid}:ignored_tids`, tid, uid); + await db.setAdd(`tid:${tid}:ignorers`, uid); + await db.sortedSetAdd(`uid:${uid}:ignored_tids`, Date.now(), tid); } async function unignore(tid, uid) { - await removeFromSets(`tid:${tid}:ignorers`, `uid:${uid}:ignored_tids`, tid, uid); + await db.setRemove(`tid:${tid}:ignorers`, uid); + await db.sortedSetRemove(`uid:${uid}:ignored_tids`, tid); } - async function addToSets(set1, set2, tid, uid) { - await db.setAdd(set1, uid); - await db.sortedSetAdd(set2, Date.now(), tid); - } - - async function removeFromSets(set1, set2, tid, uid) { - await db.setRemove(set1, uid); - await db.sortedSetRemove(set2, tid); + async function updateFollowerCount(tid) { + const count = await db.setCount(`tid:${tid}:followers`); + await Topics.setTopicField(tid, 'followercount', count); } Topics.isFollowing = async function (tids, uid) { diff --git a/src/upgrades/4.3.0/normalize_thumbs_uploads.js b/src/upgrades/4.3.0/normalize_thumbs_uploads.js index d5fc15820a..ef12d54f81 100644 --- a/src/upgrades/4.3.0/normalize_thumbs_uploads.js +++ b/src/upgrades/4.3.0/normalize_thumbs_uploads.js @@ -7,7 +7,7 @@ const crypto = require('crypto'); module.exports = { name: 'Normalize topic thumbnails, post & user uploads to same format', - timestamp: Date.UTC(2021, 1, 7), + timestamp: Date.UTC(2025, 3, 4), method: async function () { const { progress } = this; diff --git a/src/upgrades/4.3.0/topic_follower_counts.js b/src/upgrades/4.3.0/topic_follower_counts.js new file mode 100644 index 0000000000..81beff21fe --- /dev/null +++ b/src/upgrades/4.3.0/topic_follower_counts.js @@ -0,0 +1,35 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + + +module.exports = { + name: 'Set "followercount" on each topic object', + timestamp: Date.UTC(2025, 3, 15), + method: async function () { + const { progress } = this; + + progress.total = await db.sortedSetCard('topics:tid'); + + await batch.processSortedSet('topics:tid', async (tids) => { + const keys = tids.map(tid => `tid:${tid}:followers`); + const followerCounts = await db.setsCount(keys); + + const bulkSet = []; + + followerCounts.forEach((count, idx) => { + const tid = tids[idx]; + if (count > 0) { + bulkSet.push([`topic:${tid}`, {followercount: count}]); + } + }); + + await db.setObjectBulk(bulkSet); + + progress.incr(tids.length); + }, { + batch: 500, + }); + }, +};