feat: #8427, daily downvote limits

v1.18.x
Barış Soner Uşaklı 5 years ago
parent fca4ee312e
commit c513b88dff

@ -70,6 +70,8 @@
"reputation:disabled": 0,
"downvote:disabled": 0,
"disableSignatures": 0,
"downvotesPerDay": 10,
"downvotesPerUserPerDay": 3,
"min:rep:downvote": 0,
"min:rep:flag": 0,
"min:rep:profile-picture": 0,

@ -5,6 +5,8 @@
"votes-are-public": "All Votes Are Public",
"thresholds": "Activity Thresholds",
"min-rep-downvote": "Minimum reputation to downvote posts",
"downvotes-per-day": "Downvotes per day (set to 0 for unlimited downvotes)",
"downvotes-per-user-per-day": "Downvotes per user per day (set to 0 for unlimited downvotes)",
"min-rep-flag": "Minimum reputation to flag posts",
"min-rep-website": "Minimum reputation to add \"Website\" to user profile",
"min-rep-aboutme": "Minimum reputation to add \"About me\" to user profile",

@ -165,6 +165,8 @@
"not-enough-reputation-min-rep-cover-picture": "You do not have enough reputation to add a cover picture",
"already-flagged": "You have already flagged this post",
"self-vote": "You cannot vote on your own post",
"too-many-downvotes-today": "You can only downvote %1 times a day",
"too-many-downvotes-today-user": "You can only downvote a user %1 times a day",
"reload-failed": "NodeBB encountered a problem while reloading: \"%1\". NodeBB will continue to serve the existing client-side assets, although you should undo what you did just prior to reloading.",

@ -119,18 +119,17 @@ module.exports = function (Posts) {
}
async function unvote(pid, uid, command) {
const [owner, voteStatus, reputation] = await Promise.all([
const [owner, voteStatus] = await Promise.all([
Posts.getPostField(pid, 'uid'),
Posts.hasVoted(pid, uid),
user.getUserField(uid, 'reputation'),
]);
if (parseInt(uid, 10) === parseInt(owner, 10)) {
throw new Error('[[error:self-vote]]');
}
if (command === 'downvote' && reputation < meta.config['min:rep:downvote']) {
throw new Error('[[error:not-enough-reputation-to-downvote]]');
if (command === 'downvote') {
await checkDownvoteLimitation(pid, uid);
}
let hook;
@ -159,6 +158,33 @@ module.exports = function (Posts) {
return await vote(voteStatus.upvoted ? 'downvote' : 'upvote', true, pid, uid);
}
async function checkDownvoteLimitation(pid, uid) {
const oneDay = 86400000;
const [reputation, targetUid, downvotedPids] = await Promise.all([
user.getUserField(uid, 'reputation'),
Posts.getPostField(pid, 'uid'),
db.getSortedSetRevRangeByScore(
'uid:' + uid + ':downvote', 0, -1, '+inf', Date.now() - oneDay
),
]);
if (reputation < meta.config['min:rep:downvote']) {
throw new Error('[[error:not-enough-reputation-to-downvote]]');
}
if (meta.config.downvotesPerDay && downvotedPids.length >= meta.config.downvotesPerDay) {
throw new Error('[[error:too-many-downvotes-today, ' + meta.config.downvotesPerDay + ']]');
}
if (meta.config.downvotesPerUserPerDay) {
const postData = await Posts.getPostsFields(downvotedPids, ['uid']);
const targetDownvotes = postData.filter(p => p.uid === targetUid).length;
if (targetDownvotes >= meta.config.downvotesPerUserPerDay) {
throw new Error('[[error:too-many-downvotes-today-user, ' + meta.config.downvotesPerUserPerDay + ']]');
}
}
}
async function vote(type, unvote, pid, uid) {
uid = parseInt(uid, 10);
if (uid <= 0) {

@ -32,7 +32,10 @@
<div class="col-sm-2 col-xs-12 settings-header">[[admin/settings/reputation:thresholds]]</div>
<div class="col-sm-10 col-xs-12">
<form>
<strong>[[admin/settings/reputation:min-rep-downvote]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:downvote"><br />
<strong>[[admin/settings/reputation:min-rep-downvote]]</strong><br /> <input type="text" class="form-control" placeholder="0"
data-field="min:rep:downvote"><br />
<strong>[[admin/settings/reputation:downvotes-per-day]]</strong><br /> <input type="text" class="form-control" placeholder="10" data-field="downvotesPerDay"><br />
<strong>[[admin/settings/reputation:downvotes-per-user-per-day]]</strong><br /> <input type="text" class="form-control" placeholder="3" data-field="downvotesPerUserPerDay"><br />
<strong>[[admin/settings/reputation:min-rep-flag]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:flag"><br />
<strong>[[admin/settings/reputation:min-rep-website]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:website"><br />
<strong>[[admin/settings/reputation:min-rep-aboutme]]</strong><br /> <input type="text" class="form-control" placeholder="0" data-field="min:rep:aboutme"><br />

@ -242,6 +242,42 @@ describe('Post\'s', function () {
});
});
});
it('should prevent downvoting more than total daily limit', async () => {
const oldValue = meta.config.downvotesPerDay;
meta.config.downvotesPerDay = 1;
let err;
const p1 = await topics.reply({
uid: voteeUid,
tid: topicData.tid,
content: 'raw content',
});
try {
await socketPosts.downvote({ uid: voterUid }, { pid: p1.pid, room_id: 'topic_1' });
} catch (_err) {
err = _err;
}
assert.equal(err.message, '[[error:too-many-downvotes-today, 1]]');
meta.config.downvotesPerDay = oldValue;
});
it('should prevent downvoting target user more than total daily limit', async () => {
const oldValue = meta.config.downvotesPerUserPerDay;
meta.config.downvotesPerUserPerDay = 1;
let err;
const p1 = await topics.reply({
uid: voteeUid,
tid: topicData.tid,
content: 'raw content',
});
try {
await socketPosts.downvote({ uid: voterUid }, { pid: p1.pid, room_id: 'topic_1' });
} catch (_err) {
err = _err;
}
assert.equal(err.message, '[[error:too-many-downvotes-today-user, 1]]');
meta.config.downvotesPerUserPerDay = oldValue;
});
});
describe('bookmarking', function () {
@ -910,7 +946,7 @@ describe('Post\'s', function () {
it('should get pid index', function (done) {
socketPosts.getPidIndex({ uid: voterUid }, { pid: pid, tid: topicData.tid, topicPostSort: 'oldest_to_newest' }, function (err, index) {
assert.ifError(err);
assert.equal(index, 2);
assert.equal(index, 4);
done();
});
});

Loading…
Cancel
Save