diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index e74e58c07b..d72d49e5c2 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -26,9 +26,6 @@ jobs: - os: ubuntu-24.04-arm platforms: linux/arm64 required: true - - os: ubuntu-24.04-arm - platforms: linux/arm/v7 - required: false continue-on-error: ${{ !matrix.required }} runs-on: ${{ matrix.os }} steps: @@ -131,4 +128,4 @@ jobs: $(printf '${{ env.IMAGE }}@sha256:%s ' *) - name: Inspect image run: | - docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }} \ No newline at end of file + docker buildx imagetools inspect ${{ env.IMAGE }}:${{ steps.meta.outputs.version }} diff --git a/CHANGELOG.md b/CHANGELOG.md index 47192ee9f1..e7364985fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,188 @@ +#### v4.7.0 (2025-11-26) + +##### Chores + +* incrementing version number - v4.6.3 (9fc5b0f3) +* update changelog for v4.6.3 (3fd193e3) +* incrementing version number - v4.6.2 (f98747db) +* up dbsearch (dfe53d29) +* up harmony, closes #13753 (4e33c1df) +* up express-useragent (b5ea2089) +* up ttlcache to 2.x (a0a10c8b) +* up themes (52c56bc5) +* incrementing version number - v4.6.1 (f47aa678) +* incrementing version number - v4.6.0 (ee395bc5) +* incrementing version number - v4.5.2 (ad2da639) +* incrementing version number - v4.5.1 (69f4b61f) +* incrementing version number - v4.5.0 (f05c5d06) +* incrementing version number - v4.4.6 (074043ad) +* incrementing version number - v4.4.5 (6f106923) +* incrementing version number - v4.4.4 (d323af44) +* incrementing version number - v4.4.3 (d354c2eb) +* incrementing version number - v4.4.2 (55c510ae) +* incrementing version number - v4.4.1 (5ae79b4e) +* incrementing version number - v4.4.0 (0a75eee3) +* incrementing version number - v4.3.2 (b92b5d80) +* incrementing version number - v4.3.1 (308e6b9f) +* incrementing version number - v4.3.0 (bff291db) +* incrementing version number - v4.2.2 (17fecc24) +* incrementing version number - v4.2.1 (852a270c) +* incrementing version number - v4.2.0 (87581958) +* incrementing version number - v4.1.1 (b2afbb16) +* incrementing version number - v4.1.0 (36c80850) +* incrementing version number - v4.0.6 (4a52fb2e) +* incrementing version number - v4.0.5 (1792a62b) +* incrementing version number - v4.0.4 (b1125cce) +* incrementing version number - v4.0.3 (2b65c735) +* incrementing version number - v4.0.2 (73fe5fcf) +* incrementing version number - v4.0.1 (a461b758) +* incrementing version number - v4.0.0 (c1eaee45) +* **deps:** + * update dependency @stylistic/eslint-plugin to v5.6.1 (#13778) (894f1988) + * update redis docker tag to v8.4.0 (#13782) (e24d8c17) + * update postgres docker tag to v18.1 (#13771) (3ea029bd) + * update dependency jsdom to v27.2.0 (#13770) (899414f4) + * update dependency smtp-server to v3.16.1 (#13755) (bc64d27f) + * update dependency mocha to v11.7.5 (#13754) (e1bf80dc) + * update redis docker tag to v8.2.3 (#13750) (4c5f7f60) + * update github artifact actions (#13730) (13c23fdd) + * update dependency @eslint/js to v9.39.1 (#13747) (4e7867a9) + * update dependency sass-embedded to v1.93.3 (#13745) (cb96701b) + * update dependency jsdom to v27.1.0 (#13743) (4ce4e773) + * update mongo docker tag to v8.2 (#13738) (97e5aa1d) + * update dependency smtp-server to v3.16.0 (#13737) (07d169d2) + * update dependency lint-staged to v16.2.6 (#13725) (e3c55f76) + * update dependency lint-staged to v16.2.5 (#13721) (83a172c9) + * update dependency @stylistic/eslint-plugin to v5.5.0 (#13717) (93d46c84) + * update dependency jsdom to v27.0.1 (#13718) (9d2b83f5) + * update dependency @eslint/js to v9.38.0 (#13716) (7fd9e894) + * update actions/setup-node action to v6 (#13708) (febe0ae0) + * update dependency smtp-server to v3.15.0 (#13702) (238600a0) + * update dependency lint-staged to v16.2.4 (#13699) (f608c7c7) + * update postgres docker tag to v18 (#13679) (923ddbc1) + * update dependency @eslint/js to v9.37.0 (#13693) (d73892ae) + * update redis docker tag to v8.2.2 (#13692) (4640a63e) + * update dependency mocha to v11.7.4 (#13685) (c7696667) + * update dependency @commitlint/cli to v20.1.0 (#13686) (eb06bda8) +* **i18n:** + * fallback strings for new resources: nodebb.admin-manage-categories (49567c72) + * fallback strings for new resources: nodebb.admin-settings-uploads (e7498e8f) + +##### New Features + +* federate out undo(announce) when moving topics (832477f8) +* native image appending for remote private notes (822f4edc) +* add isNumber to client-side helpers (172aabcb) +* handle Move(Context) activity (8ca52c7e) +* update Remove(Context) to use target instead of origin, federate out Move(Context) on topic move between local cids (d02e188a) +* context removal logic (aka moving topics to uncategorized, and federating this to other NodeBBs) (34e95e6d) +* add new setting to control posts uploads being shown as thumbs (97e59fbe) +* handle Delete(Context) as a move to cid -1 if the remote context still exists (f98a7216) +* handle incoming Announce(Delete), closes #13712 (4d5005b9) +* execute 1b12 rebroadcast logic on all tids even if not posted to a local cid (9583f0d4) +* auto-enable link-preview plugin on new installations (b153941c) +* bundle link-preview plugin (e7bdf6bc) +* federate topic deletion on topic deletion as well as purge (4d24309a) +* federate Delete on post delete as well as purge, topic deletion federates Announce(Delete(Object)) (93b6cb59) + +##### Bug Fixes + +* **deps:** + * bump mentions to fix #13637 (e3ac9ccf) + * update dependency rimraf to v6.1.2 (#13784) (5ab8f877) + * update dependency @isaacs/ttlcache to v2.1.2 (#13780) (cecc0fee) + * update dependency workerpool to v10.0.1 (#13781) (bfffb4b9) + * update dependency webpack to v5.103.0 (#13783) (5acfd184) + * update dependency sass to v1.94.1 (#13777) (b0c9bb1e) + * update dependency mongodb to v6.21.0 (#13772) (111ae163) + * update dependency sass to v1.94.0 (#13773) (c95bfcbf) + * update dependency validator to v13.15.23 (#13769) (93c69f9d) + * update dependency express-useragent to v2.0.2 (#13767) (e14d3ac1) + * update dependency autoprefixer to v10.4.22 (#13768) (9271e267) + * update dependency @isaacs/ttlcache to v2.1.1 (#13763) (f24bb090) + * update dependency esbuild to v0.27.0 (#13766) (63789ebb) + * update dependency cron to v4.3.4 (#13762) (6ad93cd3) + * update dependency sharp to v0.34.5 (#13758) (5be0a630) + * update dependency bcryptjs to v3.0.3 (#13751) (a34284df) + * update dependency sitemap to v9 (#13752) (1921ccaa) + * update dependency esbuild to v0.25.12 (#13748) (090eb088) + * update dependency rimraf to v6.1.0 (#13744) (a36d89fc) + * update dependency sass to v1.93.3 (#13746) (ba123073) + * update dependency sitemap to v8.0.2 (#13736) (b5c1e8e7) + * update mentions (5c3b1261) + * update dependency validator to v13.15.20 (#13733) (6f448ce2) + * bump mentions to 4.8.0 (964a5388) + * update dependency commander to v14.0.2 (#13731) (a49efe49) + * update dependency redis to v5.9.0 (#13727) (418717fd) + * update dependency nodemailer to v7.0.10 (#13726) (c1f6e52b) + * update dependency workerpool to v10 (#13723) (5a6c2097) + * update dependency sitemap to v8.0.1 (#13720) (1d9d7fc5) + * update dependency ace-builds to v1.43.4 (#13714) (27a0dc73) + * bump dbsearch (c25c6290) + * update dependency esbuild to v0.25.11 (#13710) (41b7a91d) + * update dependency chart.js to v4.5.1 (#13704) (bf37c7bd) + * update dependency nodebb-theme-persona to v14.1.15 (#13701) (fa18287d) + * update dependency nodebb-theme-harmony to v2.1.21 (#13700) (49a29325) + * update dependency nodemailer to v7.0.9 (#13695) (5d3709f0) + * update dependency semver to v7.7.3 (#13697) (a2892f60) + * update dependency webpack to v5.102.1 (#13698) (bb7b65ea) + * update dependency nodemailer to v7.0.7 (#13694) (5dc9f2c5) + * update dependency redis to v5.8.3 (#13691) (9b6e9b2a) + * update dependency winston to v3.18.3 (#13687) (19dc1025) +* null check on attachments property in assertPrivate (9d83a3d0) +* update announce and undo(announce) so that their IDs don't use timestamps (24e17683) +* incorrect topic event added when topic moved out of cid -1 (used to be a share by the user; since removed.) (2b733e4a) +* #13654, improper OrderedCollectionPage ID (aa7e078f) +* IS logic when body.height < window.height (bdb45248) +* update markdown and web-push to latest versions (c51b7b65) +* bump mentions to 4.8.2 (2ce691cb) +* rename activitypub.out.announce.category, federate out Delete on topic move to cid -1 (9bb8a955) +* bump harmony and persona for #13756 (c616e657) +* renderOverride to not clobber url if already set in template data (2066727f) +* bump themes for cross-post support, #13396 (9d3e8179) +* add replies in parallel during note assertion (4858abe1) +* logic error in context generation (748cc5ee) +* relax toPid assertion checks so that it only checks that it is a number or uri (30b1212a) +* update logic so that purging a post does not remove toPid fields from children, updated addParentPosts so that post existence is checked (f6219d00) +* update category mock to save full handle (524df6e5) +* logic error in out.remove.context (ab9154aa) +* cross-check remove(context) target prop against cid (194cedb4) +* update logic re: federating out topic moves (4f2f872b) +* bad var (22868d3f) +* call api.topics method on topic move during note assertion, have category announce new topic on note assertion (3df4970c) +* do not include image or icon props if they are falsy values (603068ae) +* rebroadcasting logic should only execute for local tids if the remote cid is not addressed already (1d529473) +* move Announce(Delete) out of topics.move and into topics API method (fadac616) +* do not include actor from reflected activity when rebroadcasting remote cid (3fa74d4c) +* broken category urls in to, cc (d4695f10) +* update getPrivateKey to send application actor key when cid 0 (a45f6f9c) +* update targets in 1b12 rebroadcast when cid is remote (58a9e1c4) +* update 1b12 rebroadcast logic to send as application actor if post is in remote cid (79d08853) +* regression caused by d3b3720915f5846e8f5a8e0bee9c17b3ff233902 (af5efbd7) +* crash in tests (6c210068) +* add attachments to retrieved post data onNewPost (07bed55e) + +##### Other Changes + +* //github.com/NodeBB/NodeBB/issues/13713 (2425f3b6) + +##### Refactors + +* deleteOrRestore internal method to federate out a Delete on delete, not just purge; better adheres to FEP 4f05 (e6911be3) +* get rid of post.exists check, if post doesnt exist content is falsy (17944037) +* move all methods in src/api/activitypub.js to src/activitypub.out.js (3ede64d8) +* user announces no longer occur on topic move. Instead, the new category announces. Only occurs when topic moved to local categories. (e09bb8b6) +* inbox announce(delete) handling to also handle context deletion, #13712 (2b2028e4) +* move post attachment handling directly into posts.create (d3b37209) + +##### Reverts + +* remove `federatedDescription` category field, closes #13757 (ed83bc5b) + +##### Tests + +* update test for toPid logic to reflect that toPid stays even if parent is purged (98a1101d) + #### v4.6.3 (2025-11-20) ##### Chores diff --git a/install/package.json b/install/package.json index 50f27f95a2..19d9192df2 100644 --- a/install/package.json +++ b/install/package.json @@ -107,11 +107,11 @@ "nodebb-plugin-spam-be-gone": "2.3.2", "nodebb-plugin-web-push": "0.7.6", "nodebb-rewards-essentials": "1.0.2", - "nodebb-theme-harmony": "2.1.25", + "nodebb-theme-harmony": "2.1.26", "nodebb-theme-lavender": "7.1.19", "nodebb-theme-peace": "2.2.49", "nodebb-theme-persona": "14.1.18", - "nodebb-widget-essentials": "7.0.40", + "nodebb-widget-essentials": "7.0.41", "nodemailer": "7.0.10", "nprogress": "0.2.0", "passport": "0.7.0", diff --git a/public/src/client/account/settings.js b/public/src/client/account/settings.js index 9445fcb340..2e220fb080 100644 --- a/public/src/client/account/settings.js +++ b/public/src/client/account/settings.js @@ -102,7 +102,7 @@ define('forum/account/settings', [ if (languageChanged && parseInt(app.user.uid, 10) === parseInt(ajaxify.data.theirid, 10)) { window.location.reload(); } - }); + }).catch(alerts.error); } function toggleCustomRoute() { diff --git a/public/src/client/category/tools.js b/public/src/client/category/tools.js index b372d7a2f6..f298cbe85d 100644 --- a/public/src/client/category/tools.js +++ b/public/src/client/category/tools.js @@ -284,8 +284,20 @@ define('forum/category/tools', [ topic.find('[component="topic/locked"]').toggleClass('hidden', !data.isLocked); } - function onTopicMoved(data) { - getTopicEl(data.tid).remove(); + async function onTopicMoved(data) { + if (ajaxify.data.template.category || String(data.toCid) === '-1') { + getTopicEl(data.tid).remove(); + } else { + const category = await api.get(`/categories/${data.toCid}`); + const html = await app.parseAndTranslate('partials/topics_list', { + topics: [{ + ...data, + category, + }], + }); + const categoryLabelSelector = `[component="category/topic"][data-tid="${data.tid}"] [component="topic/category"]`; + $(categoryLabelSelector).replaceWith(html.find(categoryLabelSelector)); + } } function onTopicPurged(data) { diff --git a/public/src/modules/helpers.common.js b/public/src/modules/helpers.common.js index aa1825adda..a33e7af8c7 100644 --- a/public/src/modules/helpers.common.js +++ b/public/src/modules/helpers.common.js @@ -77,10 +77,12 @@ module.exports = function (utils, Benchpress, relative_path) { } function buildLinkTag(tag) { - const attributes = ['link', 'rel', 'as', 'type', 'href', 'sizes', 'title', 'crossorigin']; - const [link, rel, as, type, href, sizes, title, crossorigin] = attributes.map(attr => (tag[attr] ? `${attr}="${tag[attr]}" ` : '')); + const attributes = [ + 'link', 'rel', 'as', 'type', 'href', 'hreflang', 'sizes', 'title', 'crossorigin', + ]; + const [link, rel, as, type, href, hreflang, sizes, title, crossorigin] = attributes.map(attr => (tag[attr] ? `${attr}="${tag[attr]}" ` : '')); - return '\n\t'; + return '\n\t'; } function stringify(obj) { @@ -111,7 +113,7 @@ module.exports = function (utils, Benchpress, relative_path) { } const href = tag === 'a' ? `href="${relative_path}/category/${category.slug}"` : ''; - return `<${tag} ${href} class="badge px-1 text-truncate text-decoration-none ${className}" style="color: ${category.color};background-color: ${category.bgColor};border-color: ${category.bgColor}!important; max-width: 70vw;"> + return `<${tag} component="topic/category" ${href} class="badge px-1 text-truncate text-decoration-none ${className}" style="color: ${category.color};background-color: ${category.bgColor};border-color: ${category.bgColor}!important; max-width: 70vw;"> ${category.icon && category.icon !== 'fa-nbb-none' ? `` : ''} ${category.name} `; diff --git a/src/api/categories.js b/src/api/categories.js index 476a0d4d9d..4806868f93 100644 --- a/src/api/categories.js +++ b/src/api/categories.js @@ -126,7 +126,7 @@ categoriesAPI.getTopics = async (caller, data) => { throw new Error('[[error:no-privileges]]'); } - const infScrollTopicsPerPage = 20; + const infScrollTopicsPerPage = settings.topicsPerPage; const sort = data.sort || data.categoryTopicSort || meta.config.categoryTopicSort || 'recently_replied'; let start = Math.max(0, parseInt(data.after || 0, 10)); diff --git a/src/api/topics.js b/src/api/topics.js index 91e0882db4..80f2b28cb7 100644 --- a/src/api/topics.js +++ b/src/api/topics.js @@ -309,6 +309,7 @@ topicsAPI.move = async (caller, { tid, cid }) => { throw new Error('[[error:no-privileges]]'); } const topicData = await topics.getTopicFields(tid, ['tid', 'cid', 'mainPid', 'slug', 'deleted']); + topicData.toCid = cid; if (!cids.includes(topicData.cid)) { cids.push(topicData.cid); } diff --git a/src/database/postgres/connection.js b/src/database/postgres/connection.js index 19d796d7ed..c49e7caac4 100644 --- a/src/database/postgres/connection.js +++ b/src/database/postgres/connection.js @@ -1,5 +1,6 @@ 'use strict'; +const fs = require('fs'); const nconf = require('nconf'); const winston = require('winston'); const _ = require('lodash'); @@ -32,6 +33,18 @@ connection.getConnectionOptions = function (postgres) { connectionTimeoutMillis: 90000, }; + if (typeof postgres.ssl === 'object' && !Array.isArray(postgres.ssl) && postgres.ssl !== null) { + const { ssl } = postgres; + connOptions.ssl = { + rejectUnauthorized: ssl.rejectUnauthorized, + }; + ['ca', 'key', 'cert'].forEach((prop) => { + if (ssl.hasOwnProperty(prop)) { + connOptions.ssl[prop] = fs.readFileSync(ssl[prop]).toString(); + } + }); + } + return _.merge(connOptions, postgres.options || {}); }; diff --git a/src/database/redis/connection.js b/src/database/redis/connection.js index 7f0b1912f8..41a9243355 100644 --- a/src/database/redis/connection.js +++ b/src/database/redis/connection.js @@ -22,7 +22,6 @@ connection.connect = async function (options) { const sentinelRootNodes = options.sentinels.map(sentinel => ({ host: sentinel.host, port: sentinel.port })); cxn = createSentinel({ ...options.options, - name: 'sentinel-db', sentinelRootNodes, }); } else if (redis_socket_or_host && String(redis_socket_or_host).indexOf('/') >= 0) { diff --git a/src/middleware/index.js b/src/middleware/index.js index 14cb3138e1..02024b8a19 100644 --- a/src/middleware/index.js +++ b/src/middleware/index.js @@ -273,10 +273,19 @@ middleware.buildSkinAsset = helpers.try(async (req, res, next) => { middleware.addUploadHeaders = function addUploadHeaders(req, res, next) { // Trim uploaded files' timestamps when downloading + force download if html let basename = path.basename(req.path); - const extname = path.extname(req.path); - if (req.path.startsWith('/uploads/files/') && middleware.regexes.timestampedUpload.test(basename)) { - basename = basename.slice(14); - res.header('Content-Disposition', `${extname.startsWith('.htm') ? 'attachment' : 'inline'}; filename="${basename}"`); + const extname = path.extname(req.path).toLowerCase(); + const unsafeExtensions = [ + '.html', '.htm', '.xhtml', '.mht', '.mhtml', '.stm', '.shtm', '.shtml', + '.svg', '.svgz', + '.xml', '.xsl', '.xslt', + ]; + const isInlineSafe = !unsafeExtensions.includes(extname); + const dispositionType = isInlineSafe ? 'inline' : 'attachment'; + if (req.path.startsWith('/uploads/files/')) { + if (middleware.regexes.timestampedUpload.test(basename)) { + basename = basename.slice(14); + } + res.header('Content-Disposition', `${dispositionType}; filename="${basename}"`); } next(); diff --git a/src/posts/votes.js b/src/posts/votes.js index 599c4f92d0..d9487b1dc2 100644 --- a/src/posts/votes.js +++ b/src/posts/votes.js @@ -177,16 +177,18 @@ module.exports = function (Posts) { } const now = Date.now(); - if (type === 'upvote' && !unvote) { - await db.sortedSetAdd(`uid:${uid}:upvote`, now, pid); - } else { - await db.sortedSetRemove(`uid:${uid}:upvote`, pid); - } + if (utils.isNumber(uid)) { + if (type === 'upvote' && !unvote) { + await db.sortedSetAdd(`uid:${uid}:upvote`, now, pid); + } else { + await db.sortedSetRemove(`uid:${uid}:upvote`, pid); + } - if (type === 'upvote' || unvote) { - await db.sortedSetRemove(`uid:${uid}:downvote`, pid); - } else { - await db.sortedSetAdd(`uid:${uid}:downvote`, now, pid); + if (type === 'upvote' || unvote) { + await db.sortedSetRemove(`uid:${uid}:downvote`, pid); + } else { + await db.sortedSetAdd(`uid:${uid}:downvote`, now, pid); + } } const postData = await Posts.getPostFields(pid, ['pid', 'uid', 'tid']); diff --git a/src/topics/unread.js b/src/topics/unread.js index 4dd5a94435..ed93f19abf 100644 --- a/src/topics/unread.js +++ b/src/topics/unread.js @@ -290,7 +290,7 @@ module.exports = function (Topics) { }; Topics.markAsRead = async function (tids, uid) { - if (!Array.isArray(tids) || !tids.length) { + if (!Array.isArray(tids) || !tids.length || !utils.isNumber(uid)) { return false; } diff --git a/src/upgrades/4.7.1/remove_extraneous_ap_data.js b/src/upgrades/4.7.1/remove_extraneous_ap_data.js new file mode 100644 index 0000000000..e32590cb01 --- /dev/null +++ b/src/upgrades/4.7.1/remove_extraneous_ap_data.js @@ -0,0 +1,24 @@ +'use strict'; + +const db = require('../../database'); +const batch = require('../../batch'); + +module.exports = { + name: 'Remove extraneous upvote and tids_read data for remote users', + timestamp: Date.UTC(2025, 11, 11), + method: async function () { + const { progress } = this; + await batch.processSortedSet('usersRemote:lastCrawled', async (uids) => { + const readKeys = uids.map(uid => `uid:${uid}:tids_read`); + const voteKeys = uids.map(uid => `uid:${uid}:upvote`); + + const combined = readKeys.concat(voteKeys); + + await db.deleteAll(combined); + progress.incr(uids.length); + }, { + batch: 500, + progress, + }); + }, +}; diff --git a/test/activitypub/notes.js b/test/activitypub/notes.js index b43f2da49f..1eba189b92 100644 --- a/test/activitypub/notes.js +++ b/test/activitypub/notes.js @@ -432,6 +432,7 @@ describe('Notes', () => { describe('Create', () => { let uid; + let cid; before(async () => { uid = await user.create({ username: utils.generateUUID() }); @@ -451,6 +452,17 @@ describe('Notes', () => { assert.strictEqual(cid, -1); }); + it('should not append to the tids_read sorted set', async () => { + const { note, id } = helpers.mocks.note(); + const { activity } = helpers.mocks.create(note); + + await db.sortedSetAdd(`followersRemote:${note.attributedTo}`, Date.now(), uid); + await activitypub.inbox.create({ body: activity }); + + const exists = await db.exists(`uid:${note.attributedTo}:tids_read`); + assert(!exists); + }); + it('should create a new topic in a remote category if addressed (category same-origin)', async () => { const { id: remoteCid } = helpers.mocks.group(); const { note, id } = helpers.mocks.note({ @@ -467,40 +479,63 @@ describe('Notes', () => { }); it('should create a new topic in cid -1 if a non-same origin remote category is addressed', async function () { - this.timeout(30000); - const start = Date.now(); const { id: remoteCid } = helpers.mocks.group({ id: `https://example.com/${utils.generateUUID()}`, }); - console.log('1', Date.now() - start); const { note, id } = helpers.mocks.note({ audience: [remoteCid], }); - console.log('2', Date.now() - start); const { activity } = helpers.mocks.create(note); - console.log('3', Date.now() - start); try { await activitypub.inbox.create({ body: activity }); } catch (err) { - console.log('error in test', err.stack); assert(false); } - console.log('4', Date.now() - start); assert(await posts.exists(id)); - console.log('5', Date.now() - start); const cid = await posts.getCidByPid(id); - console.log('6', Date.now() - start); assert.strictEqual(cid, -1); }); }); + + describe('(Like)', () => { + let pid; + let voterUid; + + before(async () => { + ({ cid } = await categories.create({ name: utils.generateUUID() })); + const { postData } = await topics.post({ + uid, + cid, + title: utils.generateUUID(), + content: utils.generateUUID(), + }); + pid = postData.pid; + const object = await activitypub.mocks.notes.public(postData); + const { activity } = helpers.mocks.like({ object }); + voterUid = activity.actor; + await activitypub.inbox.like({ body: activity }); + }); + + it('should increment a like for the post', async () => { + const voted = await posts.hasVoted(pid, voterUid); + const count = await posts.getPostField(pid, 'upvotes'); + assert(voted); + assert.strictEqual(count, 1); + }); + + it('should not append to the uid upvotes zset', async () => { + const exists = await db.exists(`uid:${voterUid}:upvote`); + assert(!exists); + }); + }); }); describe('Announce', () => { let cid; before(async () => { - ({ cid } = await categories.create({ name: utils.generateUUID().slice(0, 8) })); + ({ cid } = await categories.create({ name: utils.generateUUID() })); }); describe('(Create)', () => { diff --git a/test/template-helpers.js b/test/template-helpers.js index b0bbb334c5..ea4d142354 100644 --- a/test/template-helpers.js +++ b/test/template-helpers.js @@ -107,7 +107,7 @@ describe('helpers', () => { imageClass: 'auto', name: 'Category 1', }, 'a', ''), - `\n\t\t\t\n\t\t\tCategory 1\n\t\t` + `\n\t\t\t\n\t\t\tCategory 1\n\t\t` ); assert.strictEqual( helpers.buildCategoryLabel({ @@ -118,7 +118,7 @@ describe('helpers', () => { name: 'Category 1', icon: 'fa-book', }, 'span', 'rounded-1'), - `\n\t\t\t\n\t\t\tCategory 1\n\t\t`, + `\n\t\t\t\n\t\t\tCategory 1\n\t\t`, ); done(); });