mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-12-25 01:40:27 +01:00
Compare commits
17 Commits
crossposti
...
v4.7.2
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bab4304e04 | ||
|
|
1f9f2dff2f | ||
|
|
59dd1ca607 | ||
|
|
d03137128c | ||
|
|
da79582148 | ||
|
|
550411fb58 | ||
|
|
1305faa838 | ||
|
|
d505301fa0 | ||
|
|
9f8d50706e | ||
|
|
8668cfb38c | ||
|
|
e6deb625f2 | ||
|
|
b1fc5bfdaa | ||
|
|
9f94a72117 | ||
|
|
9f72996416 | ||
|
|
5ae8d553ed | ||
|
|
90a151348e | ||
|
|
f49f540bfa |
72
CHANGELOG.md
72
CHANGELOG.md
@@ -1,3 +1,75 @@
|
||||
#### v4.7.1 (2025-12-17)
|
||||
|
||||
##### Chores
|
||||
|
||||
* up widget-essentials (9d666550)
|
||||
* remove log (2142b680)
|
||||
* up harmony (59f649b8)
|
||||
* incrementing version number - v4.7.0 (e82d40f8)
|
||||
* update changelog for v4.7.0 (1c0a43dc)
|
||||
* incrementing version number - v4.6.3 (9fc5b0f3)
|
||||
* incrementing version number - v4.6.2 (f98747db)
|
||||
* 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)
|
||||
|
||||
##### Continuous Integration
|
||||
|
||||
* drop ARM v7 from docker builds (#13808) (254370c5)
|
||||
|
||||
##### New Features
|
||||
|
||||
* stop extraneous vote and tids_read data from being saved for remote users (9f729964)
|
||||
* add hreflang to buildLinkTag (ba85474d)
|
||||
* #13790, allow ssl setup in psql (5bd1f7b7)
|
||||
|
||||
##### Bug Fixes
|
||||
|
||||
* wrong increment value (b1fc5bfd)
|
||||
* increment progress on upgrade script (9f94a721)
|
||||
* disallow inline viewing of unsafe files (#13833) (5ae8d553)
|
||||
* moving topic to cid=-1 will remove it from list (90a15134)
|
||||
* show errors when saving settings (f49f540b)
|
||||
* closes #13666, update category label (193aaf55)
|
||||
* respect user pagination settings in infinite scroll (#13765) (#13788) (ebf2a2c5)
|
||||
* remove hardcoded name for sentinel, #13794 (53e22acf)
|
||||
|
||||
##### Other Changes
|
||||
|
||||
* fix missing comma (9fb41c69)
|
||||
|
||||
##### Reverts
|
||||
|
||||
* spec change (b19281b0)
|
||||
|
||||
##### Tests
|
||||
|
||||
* fix tests (11b01dfc)
|
||||
|
||||
#### v4.7.0 (2025-11-26)
|
||||
|
||||
##### Chores
|
||||
|
||||
@@ -12,6 +12,7 @@ set_defaults() {
|
||||
export SETUP="${SETUP:-}"
|
||||
export PACKAGE_MANAGER="${PACKAGE_MANAGER:-npm}"
|
||||
export OVERRIDE_UPDATE_LOCK="${OVERRIDE_UPDATE_LOCK:-false}"
|
||||
export NODEBB_ADDITIONAL_PLUGINS="${NODEBB_ADDITIONAL_PLUGINS:-}"
|
||||
}
|
||||
|
||||
# Function to check if a directory exists and is writable
|
||||
@@ -172,6 +173,33 @@ debug_log() {
|
||||
echo "DEBUG: $message"
|
||||
}
|
||||
|
||||
install_additional_plugins() {
|
||||
if [[ ! -z ${NODEBB_ADDITIONAL_PLUGINS} ]]; then
|
||||
export START_BUILD="true"
|
||||
for plugin in "${NODEBB_ADDITIONAL_PLUGINS[@]}"; do
|
||||
echo "Installing additional plugin ${plugin}..."
|
||||
case "$PACKAGE_MANAGER" in
|
||||
yarn) yarn install || {
|
||||
echo "Failed to install plugin ${plugin} with yarn"
|
||||
exit 1
|
||||
} ;;
|
||||
npm) npm install || {
|
||||
echo "Failed to install plugin ${plugin} with npm"
|
||||
exit 1
|
||||
} ;;
|
||||
pnpm) pnpm install || {
|
||||
echo "Failed to install plugin ${plugin} with pnpm"
|
||||
exit 1
|
||||
} ;;
|
||||
*)
|
||||
echo "Unknown package manager: $PACKAGE_MANAGER"
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
fi
|
||||
}
|
||||
|
||||
# Main function
|
||||
main() {
|
||||
set_defaults
|
||||
@@ -182,12 +210,14 @@ main() {
|
||||
debug_log "PACKAGE_MANAGER: $PACKAGE_MANAGER"
|
||||
debug_log "CONFIG location: $CONFIG"
|
||||
debug_log "START_BUILD: $START_BUILD"
|
||||
debug_log "NODEBB_ADDITIONAL_PLUGINS: ${NODEBB_ADDITIONAL_PLUGINS}"
|
||||
|
||||
if [ -n "$SETUP" ]; then
|
||||
start_setup_session "$CONFIG"
|
||||
fi
|
||||
|
||||
if [ -f "$CONFIG" ]; then
|
||||
install_additional_plugins
|
||||
start_forum "$CONFIG" "$START_BUILD"
|
||||
else
|
||||
start_installation_session "$NODEBB_INIT_VERB" "$CONFIG"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "nodebb",
|
||||
"license": "GPL-3.0",
|
||||
"description": "NodeBB Forum",
|
||||
"version": "4.7.0",
|
||||
"version": "4.7.2",
|
||||
"homepage": "https://www.nodebb.org",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -45,7 +45,7 @@
|
||||
"autoprefixer": "10.4.22",
|
||||
"bcryptjs": "3.0.3",
|
||||
"benchpressjs": "2.5.5",
|
||||
"body-parser": "2.2.0",
|
||||
"body-parser": "2.2.1",
|
||||
"bootbox": "6.0.4",
|
||||
"bootstrap": "5.3.8",
|
||||
"bootswatch": "5.3.8",
|
||||
@@ -96,14 +96,14 @@
|
||||
"mousetrap": "1.6.5",
|
||||
"multer": "2.0.2",
|
||||
"nconf": "0.13.0",
|
||||
"nodebb-plugin-2factor": "7.6.0",
|
||||
"nodebb-plugin-2factor": "7.6.1",
|
||||
"nodebb-plugin-composer-default": "10.3.1",
|
||||
"nodebb-plugin-dbsearch": "6.3.4",
|
||||
"nodebb-plugin-emoji": "6.0.5",
|
||||
"nodebb-plugin-emoji-android": "4.1.1",
|
||||
"nodebb-plugin-link-preview": "2.1.5",
|
||||
"nodebb-plugin-markdown": "13.2.2",
|
||||
"nodebb-plugin-mentions": "4.8.3",
|
||||
"nodebb-plugin-mentions": "4.8.4",
|
||||
"nodebb-plugin-spam-be-gone": "2.3.2",
|
||||
"nodebb-plugin-web-push": "0.7.6",
|
||||
"nodebb-rewards-essentials": "1.0.2",
|
||||
|
||||
@@ -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() {
|
||||
|
||||
@@ -285,7 +285,7 @@ define('forum/category/tools', [
|
||||
}
|
||||
|
||||
async function onTopicMoved(data) {
|
||||
if (ajaxify.data.template.category) {
|
||||
if (ajaxify.data.template.category || String(data.toCid) === '-1') {
|
||||
getTopicEl(data.tid).remove();
|
||||
} else {
|
||||
const category = await api.get(`/categories/${data.toCid}`);
|
||||
|
||||
@@ -81,6 +81,7 @@ define('forum/groups/details', [
|
||||
case 'toggleOwnership':
|
||||
api[isOwner ? 'del' : 'put'](`/groups/${ajaxify.data.group.slug}/ownership/${uid}`, {}).then(() => {
|
||||
ownerFlagEl.toggleClass('invisible');
|
||||
userRow.attr('data-isowner', isOwner ? '0' : '1');
|
||||
}).catch(alerts.error);
|
||||
break;
|
||||
|
||||
|
||||
@@ -267,7 +267,7 @@ Notes.assert = async (uid, input, options = { skipChecks: false }) => {
|
||||
|
||||
if (!hasTid && options.cid) {
|
||||
// New topic, have category announce it
|
||||
activitypub.out.announce.topic(tid);
|
||||
await activitypub.out.announce.topic(tid);
|
||||
}
|
||||
|
||||
return { tid, count };
|
||||
|
||||
@@ -58,13 +58,12 @@ connection.connect = async function (options) {
|
||||
winston.error(err.stack);
|
||||
reject(err);
|
||||
});
|
||||
cxn.on('ready', () => {
|
||||
|
||||
cxn.connect().then(() => {
|
||||
// back-compat with node_redis
|
||||
cxn.batch = cxn.multi;
|
||||
resolve(cxn);
|
||||
});
|
||||
cxn.connect().then(() => {
|
||||
winston.info('Connected to Redis successfully');
|
||||
resolve(cxn);
|
||||
}).catch((err) => {
|
||||
winston.error('Error connecting to Redis:', err);
|
||||
});
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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,
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -164,8 +164,9 @@ describe('FEPs', () => {
|
||||
});
|
||||
pid = id;
|
||||
({ activity } = await helpers.mocks.create(note));
|
||||
console.log('before inbox create', activitypub._sent);
|
||||
await activitypub.inbox.create({ body: activity });
|
||||
|
||||
console.log('after inbox create', activitypub._sent);
|
||||
const activities = Array.from(activitypub._sent);
|
||||
|
||||
const test1 = activities.some((activity) => {
|
||||
@@ -174,14 +175,13 @@ describe('FEPs', () => {
|
||||
activity.object && activity.object.type === 'Create' &&
|
||||
activity.object.object && activity.object.object.type === 'Note';
|
||||
});
|
||||
|
||||
assert(test1);
|
||||
const test2 = activities.some((activity) => {
|
||||
[, activity] = activity;
|
||||
return activity.type === 'Announce' &&
|
||||
activity.object && activity.object.type === 'Note';
|
||||
});
|
||||
|
||||
assert(test1 && test2);
|
||||
assert(test2);
|
||||
});
|
||||
|
||||
it('should federate out an Announce(Create(Note)) on reply', async () => {
|
||||
|
||||
@@ -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