diff --git a/public/language/en-GB/error.json b/public/language/en-GB/error.json index af0b72e990..a01bdb473a 100644 --- a/public/language/en-GB/error.json +++ b/public/language/en-GB/error.json @@ -211,6 +211,7 @@ "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", "cant-flag-privileged": "You are not allowed to flag the profiles or content of privileged users (moderators/global moderators/admins)", + "cant-locate-flag-report": "Cannot locate flag report", "self-vote": "You cannot vote on your own post", "too-many-upvotes-today": "You can only upvote %1 times a day", "too-many-upvotes-today-user": "You can only upvote a user %1 times a day", diff --git a/public/language/en-GB/flags.json b/public/language/en-GB/flags.json index f12aaae53d..996e027833 100644 --- a/public/language/en-GB/flags.json +++ b/public/language/en-GB/flags.json @@ -1,5 +1,6 @@ { "state": "State", + "report": "Report", "reports": "Reports", "first-reported": "First Reported", "no-flags": "Hooray! No flags found.", @@ -8,6 +9,8 @@ "update": "Update", "updated": "Updated", "resolved": "Resolved", + "report-added": "Added", + "report-rescinded": "Rescinded", "target-purged": "The content this flag referred to has been purged and is no longer available.", "target-aboutme-empty": "This user has no "About Me" set.", diff --git a/src/flags.js b/src/flags.js index 55d97713a8..8cd845f107 100644 --- a/src/flags.js +++ b/src/flags.js @@ -417,7 +417,10 @@ Flags.create = async function (type, id, uid, reason, timestamp, forceFlag = fal const flagId = await Flags.getFlagIdByTarget(type, id); await Promise.all([ Flags.addReport(flagId, type, id, uid, reason, timestamp), - Flags.update(flagId, uid, { state: 'open' }), + Flags.update(flagId, uid, { + state: 'open', + report: 'added', + }), ]); return await Flags.get(flagId); @@ -553,6 +556,45 @@ Flags.addReport = async function (flagId, type, id, uid, reason, timestamp) { plugins.hooks.fire('action:flags.addReport', { flagId, type, id, uid, reason, timestamp }); }; +Flags.rescindReport = async (type, id, uid) => { + const exists = await Flags.exists(type, id, uid); + if (!exists) { + return true; + } + + const flagId = await db.sortedSetScore('flags:hash', [type, id, uid].join(':')); + const reports = await db.getSortedSetMembers(`flag:${flagId}:reports`); + let reason; + reports.forEach((payload) => { + if (!reason) { + const [payloadUid, payloadReason] = payload.split(';'); + if (parseInt(payloadUid, 10) === parseInt(uid, 10)) { + reason = payloadReason; + } + } + }); + + if (!reason) { + throw new Error('[[error:cant-locate-flag-report]]'); + } + + await db.sortedSetRemoveBulk([ + [`flags:byReporter:${uid}`, flagId], + [`flag:${flagId}:reports`, [uid, reason].join(';')], + + ['flags:hash', [type, id, uid].join(':')], + ]); + + // If there are no more reports, consider the flag resolved + const reportCount = await db.sortedSetCard(`flag:${flagId}:reports`); + if (reportCount < 1) { + await Flags.update(flagId, uid, { + state: 'resolved', + report: 'rescinded', + }); + } +}; + Flags.exists = async function (type, id, uid) { return await db.isSortedSetMember('flags:hash', [type, id, uid].join(':')); }; @@ -766,6 +808,9 @@ Flags.getHistory = async function (flagId) { if (changeset.hasOwnProperty('state')) { changeset.state = changeset.state === undefined ? '' : `[[flags:state-${changeset.state}]]`; } + if (changeset.hasOwnProperty('report')) { + changeset.report = `[[flags:report-${changeset.report}]]`; + } return { uid: entry.value[0], diff --git a/src/routes/write/flags.js b/src/routes/write/flags.js index 6e9c8e6623..c498421e78 100644 --- a/src/routes/write/flags.js +++ b/src/routes/write/flags.js @@ -12,6 +12,7 @@ module.exports = function () { setupApiRoute(router, 'post', '/', [...middlewares], controllers.write.flags.create); + // Note: access control provided by middleware.assert.flag setupApiRoute(router, 'get', '/:flagId', [...middlewares, middleware.assert.flag], controllers.write.flags.get); setupApiRoute(router, 'put', '/:flagId', [...middlewares, middleware.assert.flag], controllers.write.flags.update); setupApiRoute(router, 'delete', '/:flagId', [...middlewares, middleware.assert.flag], controllers.write.flags.delete);