mirror of
https://github.com/NodeBB/NodeBB.git
synced 2025-10-27 17:16:14 +01:00
@@ -180,6 +180,10 @@ paths:
|
|||||||
$ref: 'write/posts/pid/move.yaml'
|
$ref: 'write/posts/pid/move.yaml'
|
||||||
/posts/{pid}/vote:
|
/posts/{pid}/vote:
|
||||||
$ref: 'write/posts/pid/vote.yaml'
|
$ref: 'write/posts/pid/vote.yaml'
|
||||||
|
/posts/{pid}/voters:
|
||||||
|
$ref: 'write/posts/pid/voters.yaml'
|
||||||
|
/posts/{pid}/upvoters:
|
||||||
|
$ref: 'write/posts/pid/upvoters.yaml'
|
||||||
/posts/{pid}/bookmark:
|
/posts/{pid}/bookmark:
|
||||||
$ref: 'write/posts/pid/bookmark.yaml'
|
$ref: 'write/posts/pid/bookmark.yaml'
|
||||||
/posts/{pid}/diffs:
|
/posts/{pid}/diffs:
|
||||||
|
|||||||
33
public/openapi/write/posts/pid/upvoters.yaml
Normal file
33
public/openapi/write/posts/pid/upvoters.yaml
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
summary: get upvoter usernames of a post
|
||||||
|
description: This is used for getting a list of upvoter usernames for the vote tooltip
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: pid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: a valid post id
|
||||||
|
example: 2
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Usernames of upvoters of post
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
otherCount:
|
||||||
|
type: number
|
||||||
|
usernames:
|
||||||
|
type: array
|
||||||
|
cutoff:
|
||||||
|
type: number
|
||||||
|
|
||||||
37
public/openapi/write/posts/pid/voters.yaml
Normal file
37
public/openapi/write/posts/pid/voters.yaml
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
get:
|
||||||
|
tags:
|
||||||
|
- posts
|
||||||
|
summary: get voters of a post
|
||||||
|
description: This returns the upvoters and downvoters of a post if the user has permission to view them
|
||||||
|
parameters:
|
||||||
|
- in: path
|
||||||
|
name: pid
|
||||||
|
schema:
|
||||||
|
type: string
|
||||||
|
required: true
|
||||||
|
description: a valid post id
|
||||||
|
example: 2
|
||||||
|
responses:
|
||||||
|
'200':
|
||||||
|
description: Data about upvoters and downvoters of the post
|
||||||
|
content:
|
||||||
|
application/json:
|
||||||
|
schema:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
status:
|
||||||
|
$ref: ../../../components/schemas/Status.yaml#/Status
|
||||||
|
response:
|
||||||
|
type: object
|
||||||
|
properties:
|
||||||
|
upvoteCount:
|
||||||
|
type: number
|
||||||
|
downvoteCount:
|
||||||
|
type: number
|
||||||
|
showDownvotes:
|
||||||
|
type: boolean
|
||||||
|
upvoters:
|
||||||
|
type: array
|
||||||
|
downvoters:
|
||||||
|
type: array
|
||||||
|
|
||||||
@@ -35,15 +35,15 @@ define('forum/topic/votes', [
|
|||||||
$this.attr('title', '');
|
$this.attr('title', '');
|
||||||
}
|
}
|
||||||
|
|
||||||
socket.emit('posts.getUpvoters', [pid], function (err, data) {
|
api.get(`/posts/${pid}/upvoters`, {}, function (err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.message === '[[error:no-privileges]]') {
|
if (err.message === '[[error:no-privileges]]') {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return alerts.error(err);
|
return alerts.error(err);
|
||||||
}
|
}
|
||||||
if (_showTooltip[pid] && data.length) {
|
if (_showTooltip[pid] && data) {
|
||||||
createTooltip($this, data[0]);
|
createTooltip($this, data);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -101,7 +101,7 @@ define('forum/topic/votes', [
|
|||||||
};
|
};
|
||||||
|
|
||||||
Votes.showVotes = function (pid) {
|
Votes.showVotes = function (pid) {
|
||||||
socket.emit('posts.getVoters', { pid: pid }, function (err, data) {
|
api.get(`/posts/${pid}/voters`, {}, function (err, data) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (err.message === '[[error:no-privileges]]') {
|
if (err.message === '[[error:no-privileges]]') {
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
const validator = require('validator');
|
const validator = require('validator');
|
||||||
const _ = require('lodash');
|
const _ = require('lodash');
|
||||||
|
|
||||||
|
const db = require('../database');
|
||||||
const utils = require('../utils');
|
const utils = require('../utils');
|
||||||
const user = require('../user');
|
const user = require('../user');
|
||||||
const posts = require('../posts');
|
const posts = require('../posts');
|
||||||
@@ -306,6 +307,95 @@ postsAPI.unvote = async function (caller, data) {
|
|||||||
return await apiHelpers.postCommand(caller, 'unvote', 'voted', '', data);
|
return await apiHelpers.postCommand(caller, 'unvote', 'voted', '', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
postsAPI.getVoters = async function (caller, data) {
|
||||||
|
if (!data || !data.pid) {
|
||||||
|
throw new Error('[[error:invalid-data]]');
|
||||||
|
}
|
||||||
|
const { pid } = data;
|
||||||
|
const cid = await posts.getCidByPid(pid);
|
||||||
|
if (!await canSeeVotes(caller.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`) : [],
|
||||||
|
]);
|
||||||
|
|
||||||
|
const [upvoters, downvoters] = await Promise.all([
|
||||||
|
user.getUsersFields(upvoteUids, ['username', 'userslug', 'picture']),
|
||||||
|
user.getUsersFields(downvoteUids, ['username', 'userslug', 'picture']),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return {
|
||||||
|
upvoteCount: upvoters.length,
|
||||||
|
downvoteCount: downvoters.length,
|
||||||
|
showDownvotes: showDownvotes,
|
||||||
|
upvoters: upvoters,
|
||||||
|
downvoters: downvoters,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
postsAPI.getUpvoters = async function (caller, data) {
|
||||||
|
if (!data.pid) {
|
||||||
|
throw new Error('[[error:invalid-data]]');
|
||||||
|
}
|
||||||
|
const { pid } = data;
|
||||||
|
const cid = await posts.getCidByPid(pid);
|
||||||
|
if (!await canSeeVotes(caller.uid, cid)) {
|
||||||
|
throw new Error('[[error:no-privileges]]');
|
||||||
|
}
|
||||||
|
|
||||||
|
let upvotedUids = (await posts.getUpvotedUidsByPids([pid]))[0];
|
||||||
|
const cutoff = 6;
|
||||||
|
if (!upvotedUids.length) {
|
||||||
|
return {
|
||||||
|
otherCount: 0,
|
||||||
|
usernames: [],
|
||||||
|
cutoff,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let otherCount = 0;
|
||||||
|
if (upvotedUids.length > cutoff) {
|
||||||
|
otherCount = upvotedUids.length - (cutoff - 1);
|
||||||
|
upvotedUids = upvotedUids.slice(0, cutoff - 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const usernames = await user.getUsernamesByUids(upvotedUids);
|
||||||
|
return {
|
||||||
|
otherCount,
|
||||||
|
usernames,
|
||||||
|
cutoff,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
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 = _.zipObject(uniqCids, canRead);
|
||||||
|
const checks = cids.map(
|
||||||
|
(cid, index) => isAdmin || isMod[index] ||
|
||||||
|
(
|
||||||
|
cidToAllowed[cid] &&
|
||||||
|
(
|
||||||
|
meta.config.voteVisibility === 'all' ||
|
||||||
|
(meta.config.voteVisibility === 'loggedin' && parseInt(uid, 10) > 0)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return isArray ? checks : checks[0];
|
||||||
|
}
|
||||||
|
|
||||||
postsAPI.bookmark = async function (caller, data) {
|
postsAPI.bookmark = async function (caller, data) {
|
||||||
return await apiHelpers.postCommand(caller, 'bookmark', 'bookmarked', '', data);
|
return await apiHelpers.postCommand(caller, 'bookmark', 'bookmarked', '', data);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -131,6 +131,16 @@ Posts.unvote = async (req, res) => {
|
|||||||
helpers.formatApiResponse(200, res);
|
helpers.formatApiResponse(200, res);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Posts.getVoters = async (req, res) => {
|
||||||
|
const data = await api.posts.getVoters(req, { pid: req.params.pid });
|
||||||
|
helpers.formatApiResponse(200, res, data);
|
||||||
|
};
|
||||||
|
|
||||||
|
Posts.getUpvoters = async (req, res) => {
|
||||||
|
const data = await api.posts.getUpvoters(req, { pid: req.params.pid });
|
||||||
|
helpers.formatApiResponse(200, res, data);
|
||||||
|
};
|
||||||
|
|
||||||
Posts.bookmark = async (req, res) => {
|
Posts.bookmark = async (req, res) => {
|
||||||
const data = await mock(req);
|
const data = await mock(req);
|
||||||
await api.posts.bookmark(req, data);
|
await api.posts.bookmark(req, data);
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ module.exports = function () {
|
|||||||
|
|
||||||
setupApiRoute(router, 'put', '/:pid/vote', [...middlewares, middleware.checkRequired.bind(null, ['delta'])], controllers.write.posts.vote);
|
setupApiRoute(router, 'put', '/:pid/vote', [...middlewares, middleware.checkRequired.bind(null, ['delta'])], controllers.write.posts.vote);
|
||||||
setupApiRoute(router, 'delete', '/:pid/vote', middlewares, controllers.write.posts.unvote);
|
setupApiRoute(router, 'delete', '/:pid/vote', middlewares, controllers.write.posts.unvote);
|
||||||
|
setupApiRoute(router, 'get', '/:pid/voters', [middleware.assert.post], controllers.write.posts.getVoters);
|
||||||
|
setupApiRoute(router, 'get', '/:pid/upvoters', [middleware.assert.post], controllers.write.posts.getUpvoters);
|
||||||
|
|
||||||
setupApiRoute(router, 'put', '/:pid/bookmark', middlewares, controllers.write.posts.bookmark);
|
setupApiRoute(router, 'put', '/:pid/bookmark', middlewares, controllers.write.posts.bookmark);
|
||||||
setupApiRoute(router, 'delete', '/:pid/bookmark', middlewares, controllers.write.posts.unbookmark);
|
setupApiRoute(router, 'delete', '/:pid/bookmark', middlewares, controllers.write.posts.unbookmark);
|
||||||
|
|||||||
@@ -1,105 +1,16 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
const _ = require('lodash');
|
const api = require('../../api');
|
||||||
|
const sockets = require('../index');
|
||||||
const db = require('../../database');
|
|
||||||
const user = require('../../user');
|
|
||||||
const posts = require('../../posts');
|
|
||||||
const privileges = require('../../privileges');
|
|
||||||
const meta = require('../../meta');
|
|
||||||
|
|
||||||
module.exports = function (SocketPosts) {
|
module.exports = function (SocketPosts) {
|
||||||
SocketPosts.getVoters = async function (socket, data) {
|
SocketPosts.getVoters = async function (socket, data) {
|
||||||
if (!data || !data.pid) {
|
sockets.warnDeprecated(socket, 'GET /api/v3/posts/:pid/voters');
|
||||||
throw new Error('[[error:invalid-data]]');
|
return await api.posts.getVoters(socket, { pid: data.pid });
|
||||||
}
|
|
||||||
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`) : [],
|
|
||||||
]);
|
|
||||||
|
|
||||||
const [upvoters, downvoters] = await Promise.all([
|
|
||||||
user.getUsersFields(upvoteUids, ['username', 'userslug', 'picture']),
|
|
||||||
user.getUsersFields(downvoteUids, ['username', 'userslug', 'picture']),
|
|
||||||
]);
|
|
||||||
|
|
||||||
return {
|
|
||||||
upvoteCount: upvoters.length,
|
|
||||||
downvoteCount: downvoters.length,
|
|
||||||
showDownvotes: showDownvotes,
|
|
||||||
upvoters: upvoters,
|
|
||||||
downvoters: downvoters,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
SocketPosts.getUpvoters = async function (socket, pids) {
|
SocketPosts.getUpvoters = async function (socket, pids) {
|
||||||
if (!Array.isArray(pids)) {
|
sockets.warnDeprecated(socket, 'GET /api/v3/posts/:pid/upvoters');
|
||||||
throw new Error('[[error:invalid-data]]');
|
return await api.posts.getUpvoters(socket, { pid: pids[0] });
|
||||||
}
|
|
||||||
|
|
||||||
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 [];
|
|
||||||
}
|
|
||||||
const cutoff = 6;
|
|
||||||
const sliced = data.map((uids) => {
|
|
||||||
let otherCount = 0;
|
|
||||||
if (uids.length > cutoff) {
|
|
||||||
otherCount = uids.length - (cutoff - 1);
|
|
||||||
uids = uids.slice(0, cutoff - 1);
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
otherCount,
|
|
||||||
uids,
|
|
||||||
};
|
|
||||||
});
|
|
||||||
|
|
||||||
const uniqUids = _.uniq(_.flatten(sliced.map(d => d.uids)));
|
|
||||||
const usernameMap = _.zipObject(uniqUids, await user.getUsernamesByUids(uniqUids));
|
|
||||||
const result = sliced.map(
|
|
||||||
data => ({
|
|
||||||
otherCount: data.otherCount,
|
|
||||||
cutoff: cutoff,
|
|
||||||
usernames: data.uids.map(uid => usernameMap[uid]),
|
|
||||||
})
|
|
||||||
);
|
|
||||||
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 = _.zipObject(uniqCids, canRead);
|
|
||||||
const checks = cids.map(
|
|
||||||
(cid, index) => isAdmin || isMod[index] ||
|
|
||||||
(
|
|
||||||
cidToAllowed[cid] &&
|
|
||||||
(
|
|
||||||
meta.config.voteVisibility === 'all' ||
|
|
||||||
(meta.config.voteVisibility === 'loggedin' && parseInt(uid, 10) > 0)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
return isArray ? checks : checks[0];
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user