Merge branch 'master' into develop

This commit is contained in:
Barış Soner Uşaklı
2024-04-15 12:13:20 -04:00
15 changed files with 120 additions and 49 deletions

View File

@@ -103,7 +103,7 @@
"nodebb-plugin-ntfy": "1.7.4",
"nodebb-plugin-spam-be-gone": "2.2.1",
"nodebb-rewards-essentials": "1.0.0",
"nodebb-theme-harmony": "1.2.48",
"nodebb-theme-harmony": "1.2.49",
"nodebb-theme-lavender": "7.1.8",
"nodebb-theme-peace": "2.2.4",
"nodebb-theme-persona": "13.3.14",

View File

@@ -136,6 +136,8 @@ get:
properties:
pid:
type: number
tid:
type: number
timestamp:
type: number
content:
@@ -182,6 +184,8 @@ get:
topic:
type: object
properties:
tid:
type: number
slug:
type: string
title:
@@ -196,12 +200,16 @@ get:
description: An ISO 8601 formatted date string (complementing `timestamp`)
pid:
type: number
tid:
type: number
index:
type: number
description: The index of the post
topic:
type: object
properties:
tid:
type: number
slug:
type: string
title:

View File

@@ -138,6 +138,8 @@ get:
properties:
pid:
type: number
tid:
type: number
timestamp:
type: number
content:
@@ -184,6 +186,8 @@ get:
topic:
type: object
properties:
tid:
type: number
slug:
type: string
title:
@@ -198,12 +202,16 @@ get:
description: An ISO 8601 formatted date string (complementing `timestamp`)
pid:
type: number
tid:
type: number
index:
type: number
description: The index of the post
topic:
type: object
properties:
tid:
type: number
slug:
type: string
title:

View File

@@ -37,6 +37,9 @@ define('forum/topic/votes', [
socket.emit('posts.getUpvoters', [pid], function (err, data) {
if (err) {
if (err.message === '[[error:no-privileges]]') {
return;
}
return alerts.error(err);
}
if (_showTooltip[pid] && data.length) {
@@ -98,7 +101,7 @@ define('forum/topic/votes', [
};
Votes.showVotes = function (pid) {
socket.emit('posts.getVoters', { pid: pid, cid: ajaxify.data.cid }, function (err, data) {
socket.emit('posts.getVoters', { pid: pid }, function (err, data) {
if (err) {
if (err.message === '[[error:no-privileges]]') {
return;

View File

@@ -40,6 +40,10 @@ usersAPI.create = async function (caller, data) {
};
usersAPI.get = async (caller, { uid }) => {
const canView = await privileges.global.can('view:users', caller.uid);
if (!canView) {
throw new Error('[[error:no-privileges]]');
}
const userData = await user.getUserData(uid);
return await user.hidePrivateData(userData, caller.uid);
};

View File

@@ -116,9 +116,10 @@ module.exports = function (Categories) {
if (teaser) {
teaser.cid = topicData[index].cid;
teaser.parentCids = cidToRoot[teaser.cid];
teaser.tid = undefined;
teaser.uid = undefined;
teaser.tid = topicData[index].tid;
teaser.uid = topicData[index].uid;
teaser.topic = {
tid: topicData[index].tid,
slug: topicData[index].slug,
title: topicData[index].title,
};

View File

@@ -396,6 +396,7 @@ helpers.setCategoryTeaser = function (category) {
url: `${nconf.get('relative_path')}/post/${post.pid}`,
timestampISO: post.timestampISO,
pid: post.pid,
tid: post.tid,
index: post.index,
topic: post.topic,
user: post.user,

View File

@@ -203,8 +203,12 @@ module.exports = function (middleware) {
if (uid <= 0) {
return next();
}
const userslug = await user.getUserField(uid, 'userslug');
if (!userslug) {
const [canView, userslug] = await Promise.all([
privileges.global.can('view:users', req.uid),
user.getUserField(uid, 'userslug'),
]);
if (!userslug || (!canView && req.uid !== uid)) {
return next();
}
const path = req.url.replace(/^\/api/, '')

View File

@@ -197,9 +197,13 @@ async function pushToUids(uids, notification) {
await db.sortedSetsRemoveRangeByScore(unreadKeys.concat(readKeys), '-inf', cutoff);
const websockets = require('./socket.io');
if (websockets.server) {
uids.forEach((uid) => {
await Promise.all(uids.map(async (uid) => {
await plugins.hooks.fire('filter:sockets.sendNewNoticationToUid', {
uid,
notification,
});
websockets.in(`uid_${uid}`).emit('event:new_notification', notification);
});
}));
}
}
@@ -223,7 +227,10 @@ async function pushToUids(uids, notification) {
// Remove uid from recipients list if they have blocked the user triggering the notification
uids = await User.blocks.filterUids(notification.from, uids);
const data = await plugins.hooks.fire('filter:notification.push', { notification: notification, uids: uids });
const data = await plugins.hooks.fire('filter:notification.push', {
notification,
uids,
});
if (!data || !data.notification || !data.uids || !data.uids.length) {
return;
}

View File

@@ -5,7 +5,11 @@ const helpers = require('./helpers');
const { setupPageRoute } = helpers;
module.exports = function (app, name, middleware, controllers) {
const middlewares = [middleware.exposeUid, middleware.canViewUsers, middleware.buildAccountData];
const middlewares = [
middleware.exposeUid,
middleware.canViewUsers,
middleware.buildAccountData,
];
const accountMiddlewares = [
...middlewares,
middleware.ensureLoggedIn,

View File

@@ -28,8 +28,7 @@ SocketHelpers.notifyNew = async function (uid, type, result) {
async function notifyUids(uid, uids, type, result) {
const post = result.posts[0];
const { tid } = post.topic;
const { cid } = post.topic;
const { tid, cid } = post.topic;
uids = await privileges.topics.filterUids('topics:read', tid, uids);
const watchStateUids = uids;
@@ -49,14 +48,28 @@ async function notifyUids(uid, uids, type, result) {
post.ip = undefined;
data.uidsTo.forEach((toUid) => {
post.categoryWatchState = categoryWatchStates[toUid];
post.topic.isFollowing = topicFollowState[toUid];
websockets.in(`uid_${toUid}`).emit('event:new_post', result);
if (result.topic && type === 'newTopic') {
websockets.in(`uid_${toUid}`).emit('event:new_topic', result.topic);
await Promise.all(data.uidsTo.map(async (toUid) => {
const copyResult = _.cloneDeep(result);
const postToUid = copyResult.posts[0];
postToUid.categoryWatchState = categoryWatchStates[toUid];
postToUid.topic.isFollowing = topicFollowState[toUid];
await plugins.hooks.fire('filter:sockets.sendNewPostToUid', {
uid: toUid,
uidFrom: uid,
post: postToUid,
});
websockets.in(`uid_${toUid}`).emit('event:new_post', copyResult);
if (copyResult.topic && type === 'newTopic') {
await plugins.hooks.fire('filter:sockets.sendNewTopicToUid', {
uid: toUid,
uidFrom: uid,
topic: copyResult.topic,
});
websockets.in(`uid_${toUid}`).emit('event:new_topic', copyResult.topic);
}
});
}));
}
async function getWatchStates(uids, tid, cid) {

View File

@@ -10,14 +10,14 @@ const meta = require('../../meta');
module.exports = function (SocketPosts) {
SocketPosts.getVoters = async function (socket, data) {
if (!data || !data.pid || !data.cid) {
if (!data || !data.pid) {
throw new Error('[[error:invalid-data]]');
}
const showDownvotes = !meta.config['downvote:disabled'];
const canSeeVotes = meta.config.votesArePublic || await privileges.categories.isAdminOrMod(data.cid, socket.uid);
if (!canSeeVotes) {
const cid = await posts.getCidByPid(data.pid);
if (!await canSeeVotes(socket.uid, cid)) {
throw new Error('[[error:no-privileges]]');
}
const showDownvotes = !meta.config['downvote:disabled'];
const [upvoteUids, downvoteUids] = await Promise.all([
db.getSetMembers(`pid:${data.pid}:upvote`),
showDownvotes ? db.getSetMembers(`pid:${data.pid}:downvote`) : [],
@@ -42,21 +42,12 @@ module.exports = function (SocketPosts) {
throw new Error('[[error:invalid-data]]');
}
const [cids, data, isAdmin] = await Promise.all([
posts.getCidsByPids(pids),
posts.getUpvotedUidsByPids(pids),
privileges.users.isAdministrator(socket.uid),
]);
if (!isAdmin) {
const isAllowed = await privileges.categories.isUserAllowedTo(
'topics:read', _.uniq(cids), socket.uid
);
if (isAllowed.includes(false)) {
throw new Error('[[error:no-privileges]]');
}
const cids = await posts.getCidsByPids(pids);
if ((await canSeeVotes(socket.uid, cids)).includes(false)) {
throw new Error('[[error:no-privileges]]');
}
const data = await posts.getUpvotedUidsByPids(pids);
if (!data.length) {
return [];
}
@@ -84,4 +75,24 @@ module.exports = function (SocketPosts) {
);
return result;
};
async function canSeeVotes(uid, cids) {
const isArray = Array.isArray(cids);
if (!isArray) {
cids = [cids];
}
const uniqCids = _.uniq(cids);
const [canRead, isAdmin, isMod] = await Promise.all([
privileges.categories.isUserAllowedTo(
'topics:read', uniqCids, uid
),
privileges.users.isAdministrator(uid),
privileges.users.isModerator(uid, cids),
]);
const cidToAllowed = _.zip(uniqCids, canRead);
const checks = cids.map(
(cid, index) => isAdmin || isMod[index] || (cidToAllowed[index] && !!meta.config.votesArePublic)
);
return isArray ? checks : checks[0];
}
};

View File

@@ -416,6 +416,7 @@ module.exports = function (Topics) {
tags = await Topics.filterTags(tags, cid);
await Topics.addTags(tags, [tid]);
plugins.hooks.fire('action:topic.updateTags', { tags, tid });
};
Topics.deleteTopicTags = async function (tid) {

View File

@@ -21,31 +21,37 @@ module.exports = function (User) {
if (parseInt(uid, 10) === parseInt(theiruid, 10)) {
throw new Error('[[error:you-cant-follow-yourself]]');
}
const exists = await User.exists(theiruid);
const [exists, isFollowing] = await Promise.all([
User.exists(theiruid),
User.isFollowing(uid, theiruid),
]);
if (!exists) {
throw new Error('[[error:no-user]]');
}
const isFollowing = await User.isFollowing(uid, theiruid);
await plugins.hooks.fire('filter:user.toggleFollow', {
type,
uid,
theiruid,
isFollowing,
});
if (type === 'follow') {
if (isFollowing) {
throw new Error('[[error:already-following]]');
}
const now = Date.now();
await Promise.all([
db.sortedSetAddBulk([
[`following:${uid}`, now, theiruid],
[`followers:${theiruid}`, now, uid],
]),
await db.sortedSetAddBulk([
[`following:${uid}`, now, theiruid],
[`followers:${theiruid}`, now, uid],
]);
} else {
if (!isFollowing) {
throw new Error('[[error:not-following]]');
}
await Promise.all([
db.sortedSetRemoveBulk([
[`following:${uid}`, theiruid],
[`followers:${theiruid}`, uid],
]),
await db.sortedSetRemoveBulk([
[`following:${uid}`, theiruid],
[`followers:${theiruid}`, uid],
]);
}

View File

@@ -214,7 +214,7 @@ UserNotifications.sendTopicNotificationToFollowers = async function (uid, topicD
const notifObj = await notifications.create({
type: 'new-topic',
bodyShort: `[[notifications:user-posted-topic, ${postData.user.displayname}, ${title}]]`,
bodyShort: translator.compile('notifications:user-posted-topic', postData.user.displayname, title),
bodyLong: postData.content,
pid: postData.pid,
path: `/post/${postData.pid}`,