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}
${tag}>`;
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();
});