From e3e55f25d0cbf19d38db85a538b71f3daa46a08c Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 15 Jul 2020 17:06:49 -0400 Subject: [PATCH] refactor: flag sanity checks, +feat: flag limits - Added new config flag:limitPerTarget, to disallow flags after an item has already been flagged x times (default 0, or infinite) - New zset flags:byTarget, score is the number of times a flag has been made against that item - "already-flagged" translation key removed, now "post-already-flagged" or "user-already-flagged" -- this fixed bug where flagging a user you've already flagged would tell you you've already flagged this post already. - Refactored Flags.canFlag to throw errors only, instead of returning boolean - Updated ACP form inputs for reputation settings page to be more bootstrappy - +1 upgrade script --- install/data/defaults.json | 1 + public/language/en-GB/admin/menu.json | 2 +- .../en-GB/admin/settings/reputation.json | 6 +- public/language/en-GB/error.json | 5 +- src/flags.js | 33 ++++++++--- .../1.14.1/readd_deleted_recent_topics.js | 2 +- src/upgrades/1.14.3/track_flags_by_target.js | 15 +++++ src/upgrades/TEMPLATE | 1 - src/views/admin/settings/reputation.tpl | 59 +++++++++++++++---- 9 files changed, 99 insertions(+), 25 deletions(-) create mode 100644 src/upgrades/1.14.3/track_flags_by_target.js diff --git a/install/data/defaults.json b/install/data/defaults.json index b9f5f039f9..77954100aa 100644 --- a/install/data/defaults.json +++ b/install/data/defaults.json @@ -79,6 +79,7 @@ "min:rep:website": 0, "min:rep:aboutme": 0, "min:rep:signature": 0, + "flags:limitPerTarget": 0, "notificationType_upvote": "notification", "notificationType_new-topic": "notification", "notificationType_new-reply": "notification", diff --git a/public/language/en-GB/admin/menu.json b/public/language/en-GB/admin/menu.json index 5cfee48e7c..338fb1d79e 100644 --- a/public/language/en-GB/admin/menu.json +++ b/public/language/en-GB/admin/menu.json @@ -19,7 +19,7 @@ "settings/general": "General", "settings/homepage": "Home Page", "settings/navigation": "Navigation", - "settings/reputation": "Reputation", + "settings/reputation": "Reputation & Flags", "settings/email": "Email", "settings/user": "Users", "settings/group": "Groups", diff --git a/public/language/en-GB/admin/settings/reputation.json b/public/language/en-GB/admin/settings/reputation.json index 77fe61ac6a..f5659569a9 100644 --- a/public/language/en-GB/admin/settings/reputation.json +++ b/public/language/en-GB/admin/settings/reputation.json @@ -12,5 +12,9 @@ "min-rep-aboutme": "Minimum reputation to add \"About me\" to user profile", "min-rep-signature": "Minimum reputation to add \"Signature\" to user profile", "min-rep-profile-picture": "Minimum reputation to add \"Profile Picture\" to user profile", - "min-rep-cover-picture": "Minimum reputation to add \"Cover Picture\" to user profile" + "min-rep-cover-picture": "Minimum reputation to add \"Cover Picture\" to user profile", + + "flags": "Flag Settings", + "flags.limit-per-target": "Maximum number of times something can be flagged", + "flags.limit-per-target-placeholder": "Default: 0" } \ No newline at end of file diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index 1158b27847..e1f99deed4 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -163,7 +163,10 @@ "not-enough-reputation-min-rep-signature": "You do not have enough reputation to add a signature", "not-enough-reputation-min-rep-profile-picture": "You do not have enough reputation to add a profile picture", "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", + "post-already-flagged": "You have already flagged this post", + "user-already-flagged": "You have already flagged this user", + "post-flagged-too-many-times": "This post has been flagged by others already", + "user-flagged-too-many-times": "This user has been flagged by others already", "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", diff --git a/src/flags.js b/src/flags.js index 2a8927902a..0dc0a05dcd 100644 --- a/src/flags.js +++ b/src/flags.js @@ -277,7 +277,7 @@ Flags.create = async function (type, id, uid, reason, timestamp) { timestamp = Date.now(); doHistoryAppend = true; } - const [flagExists, targetExists, canFlag, targetUid, targetCid] = await Promise.all([ + const [flagExists, targetExists,, targetUid, targetCid] = await Promise.all([ // Sanity checks Flags.exists(type, id, uid), Flags.targetExists(type, id), @@ -287,12 +287,11 @@ Flags.create = async function (type, id, uid, reason, timestamp) { Flags.getTargetCid(type, id), ]); if (flagExists) { - throw new Error('[[error:already-flagged]]'); + throw new Error(`[[error:${type}-already-flagged]]`); } else if (!targetExists) { throw new Error('[[error:invalid-data]]'); - } else if (!canFlag) { - throw new Error('[[error:no-privileges]]'); } + const flagId = await db.incrObjectField('global', 'nextFlagId'); await db.setObject('flag:' + flagId, { @@ -307,6 +306,7 @@ Flags.create = async function (type, id, uid, reason, timestamp) { await db.sortedSetAdd('flags:byReporter:' + uid, timestamp, flagId); // by reporter await db.sortedSetAdd('flags:byType:' + type, timestamp, flagId); // by flag type await db.sortedSetAdd('flags:hash', flagId, [type, id, uid].join(':')); // save zset for duplicate checking + await db.sortedSetIncrBy('flags:byTarget', 1, [type, id].join(':')); // by flag target (score is count) await analytics.increment('flags'); // some fancy analytics if (targetUid) { @@ -336,13 +336,28 @@ Flags.exists = async function (type, id, uid) { }; Flags.canFlag = async function (type, id, uid) { - if (type === 'user') { - return true; + const limit = meta.config['flags:limitPerTarget']; + if (limit > 0) { + const score = await db.sortedSetScore('flags:byTarget', `${type}:${id}`); + if (score >= limit) { + throw new Error(`[[error:${type}-flagged-too-many-times]]`); + } } - if (type === 'post') { - return await privileges.posts.can('topics:read', id, uid); + + const canRead = await privileges.posts.can('topics:read', id, uid); + switch (type) { + case 'user': + return true; + + case 'post': + if (!canRead) { + throw new Error('[[error:no-privileges]]'); + } + break; + + default: + throw new Error('[[error:invalid-data]]'); } - throw new Error('[[error:invalid-data]]'); }; Flags.getTarget = async function (type, id, uid) { diff --git a/src/upgrades/1.14.1/readd_deleted_recent_topics.js b/src/upgrades/1.14.1/readd_deleted_recent_topics.js index 14580953fb..e48afc6291 100644 --- a/src/upgrades/1.14.1/readd_deleted_recent_topics.js +++ b/src/upgrades/1.14.1/readd_deleted_recent_topics.js @@ -5,7 +5,7 @@ const db = require('../../database'); const batch = require('../../batch'); module.exports = { - name: 'Re add deleted topics to topics:recent', + name: 'Re-add deleted topics to topics:recent', timestamp: Date.UTC(2018, 9, 11), method: async function () { const progress = this.progress; diff --git a/src/upgrades/1.14.3/track_flags_by_target.js b/src/upgrades/1.14.3/track_flags_by_target.js new file mode 100644 index 0000000000..c96e993365 --- /dev/null +++ b/src/upgrades/1.14.3/track_flags_by_target.js @@ -0,0 +1,15 @@ +'use strict'; + +const db = require('../../database'); + +module.exports = { + name: 'New sorted set for tracking flags by target', + timestamp: Date.UTC(2020, 6, 15), + method: async () => { + const flags = await db.getSortedSetRange('flags:hash', 0, -1); + await Promise.all(flags.map(async (flag) => { + flag = flag.split(':').slice(0, 2); + await db.sortedSetIncrBy('flags:byTarget', 1, flag.join(':')); + })); + }, +}; diff --git a/src/upgrades/TEMPLATE b/src/upgrades/TEMPLATE index 98eb0288a6..e6396ecb35 100644 --- a/src/upgrades/TEMPLATE +++ b/src/upgrades/TEMPLATE @@ -1,7 +1,6 @@ 'use strict'; const db = require('../../database'); -const winston = require('winston'); module.exports = { // you should use spaces diff --git a/src/views/admin/settings/reputation.tpl b/src/views/admin/settings/reputation.tpl index 4d802383b9..1ce7d808dd 100644 --- a/src/views/admin/settings/reputation.tpl +++ b/src/views/admin/settings/reputation.tpl @@ -27,21 +27,58 @@ -
[[admin/settings/reputation:thresholds]]
- [[admin/settings/reputation:min-rep-downvote]]

- [[admin/settings/reputation:downvotes-per-day]]

- [[admin/settings/reputation:downvotes-per-user-per-day]]

- [[admin/settings/reputation:min-rep-flag]]

- [[admin/settings/reputation:min-rep-website]]

- [[admin/settings/reputation:min-rep-aboutme]]

- [[admin/settings/reputation:min-rep-signature]]

- [[admin/settings/reputation:min-rep-profile-picture]]

- [[admin/settings/reputation:min-rep-cover-picture]]

+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+
+
+
+ +
+
[[admin/settings/reputation:flags]]
+
+
+
+ + +