mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-16 05:20:24 +01:00
Compare commits
16 Commits
renovate/c
...
e771a02d7d
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e771a02d7d | ||
|
|
ad895efb61 | ||
|
|
22fe83f005 | ||
|
|
b169621860 | ||
|
|
da7c9b32b8 | ||
|
|
0fcc8543c6 | ||
|
|
ed977c48b4 | ||
|
|
f49f540bfa | ||
|
|
20918b5281 | ||
|
|
8abe0dfa9f | ||
|
|
097d0802b7 | ||
|
|
3adcbe0f7d | ||
|
|
b992511bb9 | ||
|
|
d4f53a6242 | ||
|
|
528cd258c4 | ||
|
|
a2f2c8c761 |
6
.github/workflows/docker.yml
vendored
6
.github/workflows/docker.yml
vendored
@@ -53,7 +53,7 @@ jobs:
|
||||
|
||||
- name: Cache node_modules
|
||||
id: cache-node-modules
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@v5
|
||||
with:
|
||||
path: var-cache-node-modules
|
||||
key: var-cache-node-modules-${{ hashFiles('Dockerfile', 'install/package.json') }}
|
||||
@@ -77,7 +77,7 @@ jobs:
|
||||
touch "${{ runner.temp }}/digests/${digest#sha256:}"
|
||||
|
||||
- name: Upload digest
|
||||
uses: actions/upload-artifact@v5
|
||||
uses: actions/upload-artifact@v6
|
||||
with:
|
||||
name: digests-${{ env.PLATFORM_PAIR }}
|
||||
path: ${{ runner.temp }}/digests/*
|
||||
@@ -93,7 +93,7 @@ jobs:
|
||||
echo "IMAGE=ghcr.io/${GITHUB_REPOSITORY@L}" >> $GITHUB_ENV
|
||||
echo "CURRENT_DATE_NST=$(date +'%Y%m%d-%H%M%S' -d '-3 hours -30 minutes')" >> $GITHUB_ENV
|
||||
- name: Download digests
|
||||
uses: actions/download-artifact@v6
|
||||
uses: actions/download-artifact@v7
|
||||
with:
|
||||
path: ${{ runner.temp }}/digests
|
||||
pattern: digests-*
|
||||
|
||||
@@ -129,7 +129,7 @@
|
||||
"rss": "1.2.2",
|
||||
"rtlcss": "4.3.0",
|
||||
"sanitize-html": "2.17.0",
|
||||
"sass": "1.95.1",
|
||||
"sass": "1.96.0",
|
||||
"satori": "0.18.3",
|
||||
"sbd": "^1.0.19",
|
||||
"semver": "7.7.3",
|
||||
@@ -141,7 +141,7 @@
|
||||
"@socket.io/redis-adapter": "8.3.0",
|
||||
"sortablejs": "1.15.6",
|
||||
"spdx-license-list": "6.10.0",
|
||||
"terser-webpack-plugin": "5.3.15",
|
||||
"terser-webpack-plugin": "5.3.16",
|
||||
"textcomplete": "0.18.2",
|
||||
"textcomplete.contenteditable": "0.1.1",
|
||||
"timeago": "1.6.7",
|
||||
@@ -164,23 +164,23 @@
|
||||
"@commitlint/cli": "20.2.0",
|
||||
"@commitlint/config-angular": "20.2.0",
|
||||
"coveralls": "3.1.1",
|
||||
"@eslint/js": "9.39.1",
|
||||
"@eslint/js": "9.39.2",
|
||||
"@stylistic/eslint-plugin": "5.6.1",
|
||||
"eslint-config-nodebb": "1.1.11",
|
||||
"eslint-plugin-import": "2.32.0",
|
||||
"grunt": "1.6.1",
|
||||
"grunt-contrib-watch": "1.1.0",
|
||||
"husky": "8.0.3",
|
||||
"husky": "9.1.7",
|
||||
"jsdom": "27.3.0",
|
||||
"lint-staged": "16.2.7",
|
||||
"mocha": "11.7.5",
|
||||
"mocha-lcov-reporter": "1.3.0",
|
||||
"mockdate": "3.0.5",
|
||||
"nyc": "17.1.0",
|
||||
"smtp-server": "3.16.1"
|
||||
"smtp-server": "3.17.1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"sass-embedded": "1.93.3"
|
||||
"sass-embedded": "1.96.0"
|
||||
},
|
||||
"resolutions": {
|
||||
"*/jquery": "3.7.1"
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -352,6 +352,26 @@ inbox.like = async (req) => {
|
||||
socketHelpers.upvote(result, 'notifications:upvoted-your-post-in');
|
||||
};
|
||||
|
||||
inbox.dislike = async (req) => {
|
||||
const { actor, object } = req.body;
|
||||
const { type, id } = await activitypub.helpers.resolveLocalId(object.id);
|
||||
|
||||
if (type !== 'post' || !(await posts.exists(id))) {
|
||||
return reject('Dislike', object, actor);
|
||||
}
|
||||
|
||||
const allowed = await privileges.posts.can('posts:downvote', id, activitypub._constants.uid);
|
||||
if (!allowed) {
|
||||
activitypub.helpers.log(`[activitypub/inbox.like] ${id} not allowed to be downvoted.`);
|
||||
return reject('Dislike', object, actor);
|
||||
}
|
||||
|
||||
activitypub.helpers.log(`[activitypub/inbox/dislike] id ${id} via ${actor}`);
|
||||
|
||||
await posts.downvote(id, actor);
|
||||
await activitypub.feps.announce(object.id, req.body);
|
||||
};
|
||||
|
||||
inbox.announce = async (req) => {
|
||||
let { actor, object, published, to, cc } = req.body;
|
||||
activitypub.helpers.log(`[activitypub/inbox/announce] Parsing Announce(${object.type}) from ${actor}`);
|
||||
|
||||
@@ -277,6 +277,32 @@ Out.like.note = enabledCheck(async (uid, pid) => {
|
||||
]);
|
||||
});
|
||||
|
||||
Out.dislike = {};
|
||||
|
||||
Out.dislike.note = enabledCheck(async (uid, pid) => {
|
||||
const payload = {
|
||||
id: `${nconf.get('url')}/uid/${uid}#activity/dislike/${encodeURIComponent(pid)}`,
|
||||
type: 'Dislike',
|
||||
actor: `${nconf.get('url')}/uid/${uid}`,
|
||||
object: utils.isNumber(pid) ? `${nconf.get('url')}/post/${pid}` : pid,
|
||||
};
|
||||
|
||||
if (!activitypub.helpers.isUri(pid)) { // only 1b12 announce for local likes
|
||||
await activitypub.feps.announce(pid, payload);
|
||||
return;
|
||||
}
|
||||
|
||||
const recipient = await posts.getPostField(pid, 'uid');
|
||||
if (!activitypub.helpers.isUri(recipient)) {
|
||||
return;
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
activitypub.send('uid', uid, [recipient], payload),
|
||||
activitypub.feps.announce(pid, payload),
|
||||
]);
|
||||
});
|
||||
|
||||
Out.announce = {};
|
||||
|
||||
Out.announce.topic = enabledCheck(async (tid) => {
|
||||
|
||||
@@ -147,14 +147,34 @@ async function executeCommand(caller, command, eventName, notification, data) {
|
||||
websockets.in(`uid_${caller.uid}`).emit(`posts.${command}`, result);
|
||||
websockets.in(data.room_id).emit(`event:${eventName}`, result);
|
||||
}
|
||||
if (result && command === 'upvote') {
|
||||
socketHelpers.upvote(result, notification);
|
||||
await activitypub.out.like.note(caller.uid, data.pid);
|
||||
} else if (result && notification) {
|
||||
socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, command, notification);
|
||||
} else if (result && command === 'unvote') {
|
||||
socketHelpers.rescindUpvoteNotification(data.pid, caller.uid);
|
||||
await activitypub.out.undo.like(caller.uid, data.pid);
|
||||
|
||||
if (result) {
|
||||
switch (command) {
|
||||
case 'upvote': {
|
||||
socketHelpers.upvote(result, notification);
|
||||
await activitypub.out.like.note(caller.uid, data.pid);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'downvote': {
|
||||
await activitypub.out.dislike.note(caller.uid, data.pid);
|
||||
break;
|
||||
}
|
||||
|
||||
case 'unvote': {
|
||||
socketHelpers.rescindUpvoteNotification(data.pid, caller.uid);
|
||||
await activitypub.out.undo.like(caller.uid, data.pid);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
if (notification) {
|
||||
socketHelpers.sendNotificationToPostOwner(data.pid, caller.uid, command, notification);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -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']);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
24
src/upgrades/4.7.1/remove_extraneous_ap_data.js
Normal file
24
src/upgrades/4.7.1/remove_extraneous_ap_data.js
Normal file
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -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)', () => {
|
||||
|
||||
Reference in New Issue
Block a user