mirror of
https://github.com/NodeBB/NodeBB.git
synced 2026-05-07 05:05:58 +02:00
feat: handling multiple reporters per flag, #8510
This commit is contained in:
@@ -1,8 +1,7 @@
|
||||
{
|
||||
"state": "State",
|
||||
"reporter": "Reporter",
|
||||
"reported-at": "Reported At",
|
||||
"description": "Description",
|
||||
"reports": "Reports",
|
||||
"first-reported": "First Reported",
|
||||
"no-flags": "Hooray! No flags found.",
|
||||
"assignee": "Assignee",
|
||||
"update": "Update",
|
||||
|
||||
@@ -6,6 +6,11 @@ define('forum/flags/list', ['components', 'Chart'], function (components, Chart)
|
||||
Flags.init = function () {
|
||||
Flags.enableFilterForm();
|
||||
|
||||
components.get('flags/list').on('click', '[data-flag-id]', function () {
|
||||
var flagId = this.getAttribute('data-flag-id');
|
||||
ajaxify.go('flags/' + flagId);
|
||||
});
|
||||
|
||||
var graphWrapper = $('#flags-daily-wrapper');
|
||||
var graphFooter = graphWrapper.siblings('.panel-footer');
|
||||
$('#flags-daily-wrapper').one('shown.bs.collapse', function () {
|
||||
|
||||
97
src/flags.js
97
src/flags.js
@@ -84,31 +84,25 @@ Flags.init = async function () {
|
||||
};
|
||||
|
||||
Flags.get = async function (flagId) {
|
||||
const [base, history, notes] = await Promise.all([
|
||||
const [base, history, notes, reports] = await Promise.all([
|
||||
db.getObject('flag:' + flagId),
|
||||
Flags.getHistory(flagId),
|
||||
Flags.getNotes(flagId),
|
||||
Flags.getReports(flagId),
|
||||
]);
|
||||
if (!base) {
|
||||
return;
|
||||
}
|
||||
|
||||
const [userObj, targetObj] = await Promise.all([
|
||||
user.getUserFields(base.uid, ['username', 'userslug', 'picture', 'reputation']),
|
||||
Flags.getTarget(base.type, base.targetId, 0),
|
||||
]);
|
||||
|
||||
const flagObj = {
|
||||
state: 'open',
|
||||
assignee: null,
|
||||
...base,
|
||||
description: validator.escape(base.description),
|
||||
datetimeISO: utils.toISOString(base.datetime),
|
||||
target_readable: base.type.charAt(0).toUpperCase() + base.type.slice(1) + ' ' + base.targetId,
|
||||
target: targetObj,
|
||||
target: await Flags.getTarget(base.type, base.targetId, 0),
|
||||
history: history,
|
||||
notes: notes,
|
||||
reporter: userObj,
|
||||
reports: reports,
|
||||
};
|
||||
const data = await plugins.fireHook('filter:flags.get', {
|
||||
flag: flagObj,
|
||||
@@ -160,19 +154,15 @@ Flags.list = async function (filters, uid) {
|
||||
const pageCount = Math.ceil(flagIds.length / flagsPerPage);
|
||||
flagIds = flagIds.slice((filters.page - 1) * flagsPerPage, filters.page * flagsPerPage);
|
||||
|
||||
const flags = await Promise.all(flagIds.map(async (flagId) => {
|
||||
const reportCounts = await db.sortedSetsCard(flagIds.map(flagId => `flag:${flagId}:reports`));
|
||||
|
||||
const flags = await Promise.all(flagIds.map(async (flagId, idx) => {
|
||||
let flagObj = await db.getObject('flag:' + flagId);
|
||||
const userObj = await user.getUserFields(flagObj.uid, ['username', 'picture']);
|
||||
flagObj = {
|
||||
state: 'open',
|
||||
assignee: null,
|
||||
heat: reportCounts[idx],
|
||||
...flagObj,
|
||||
reporter: {
|
||||
username: userObj.username,
|
||||
picture: userObj.picture,
|
||||
'icon:bgColor': userObj['icon:bgColor'],
|
||||
'icon:text': userObj['icon:text'],
|
||||
},
|
||||
};
|
||||
flagObj.labelClass = Flags._constants.state_class[flagObj.state];
|
||||
|
||||
@@ -242,6 +232,24 @@ Flags.getNote = async function (flagId, datetime) {
|
||||
return notes[0];
|
||||
};
|
||||
|
||||
Flags.getFlagIdByTarget = async function (type, id) {
|
||||
let method;
|
||||
switch (type) {
|
||||
case 'post':
|
||||
method = posts.getPostField;
|
||||
break;
|
||||
|
||||
case 'user':
|
||||
method = user.getUserField;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
|
||||
return await method(id, 'flagId');
|
||||
};
|
||||
|
||||
async function modifyNotes(notes) {
|
||||
const uids = [];
|
||||
notes = notes.map(function (note) {
|
||||
@@ -277,11 +285,13 @@ Flags.create = async function (type, id, uid, reason, timestamp) {
|
||||
timestamp = Date.now();
|
||||
doHistoryAppend = true;
|
||||
}
|
||||
const [flagExists, targetExists,, targetUid, targetCid] = await Promise.all([
|
||||
const [flagExists, targetExists,, targetFlagged, targetUid, targetCid] = await Promise.all([
|
||||
// Sanity checks
|
||||
Flags.exists(type, id, uid),
|
||||
Flags.targetExists(type, id),
|
||||
Flags.canFlag(type, id, uid),
|
||||
Flags.targetFlagged(type, id),
|
||||
|
||||
// Extra data for zset insertion
|
||||
Flags.getTargetUid(type, id),
|
||||
Flags.getTargetCid(type, id),
|
||||
@@ -292,18 +302,24 @@ Flags.create = async function (type, id, uid, reason, timestamp) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
}
|
||||
|
||||
// If the flag already exists, just add the report
|
||||
if (targetFlagged) {
|
||||
const flagId = await Flags.getFlagIdByTarget(type, id);
|
||||
await Flags.addReport(flagId, uid, reason, timestamp);
|
||||
|
||||
return await Flags.get(flagId);
|
||||
}
|
||||
|
||||
const flagId = await db.incrObjectField('global', 'nextFlagId');
|
||||
|
||||
await db.setObject('flag:' + flagId, {
|
||||
flagId: flagId,
|
||||
type: type,
|
||||
targetId: id,
|
||||
description: reason,
|
||||
uid: uid,
|
||||
datetime: timestamp,
|
||||
});
|
||||
await Flags.addReport(flagId, uid, reason, timestamp);
|
||||
await db.sortedSetAdd('flags:datetime', timestamp, flagId); // by time, the default
|
||||
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)
|
||||
@@ -322,6 +338,10 @@ Flags.create = async function (type, id, uid, reason, timestamp) {
|
||||
if (targetUid) {
|
||||
await user.incrementUserFlagsBy(targetUid, 1);
|
||||
}
|
||||
|
||||
await posts.setPostField(id, 'flagId', flagId);
|
||||
} else if (type === 'user') {
|
||||
await user.setUserField(id, 'flagId', flagId);
|
||||
}
|
||||
|
||||
if (doHistoryAppend) {
|
||||
@@ -331,6 +351,31 @@ Flags.create = async function (type, id, uid, reason, timestamp) {
|
||||
return await Flags.get(flagId);
|
||||
};
|
||||
|
||||
Flags.getReports = async function (flagId) {
|
||||
const [reports, reporterUids] = await Promise.all([
|
||||
db.getSortedSetRangeWithScores(`flag:${flagId}:reports`, 0, -1),
|
||||
db.getSortedSetRange(`flag:${flagId}:reporters`, 0, -1),
|
||||
]);
|
||||
|
||||
await Promise.all(reports.map(async (report, idx) => {
|
||||
report.timestamp = report.score;
|
||||
report.timestampISO = new Date(report.score).toISOString();
|
||||
delete report.score;
|
||||
report.reporter = await user.getUserFields(reporterUids[idx], ['username', 'userslug', 'picture', 'reputation']);
|
||||
}));
|
||||
|
||||
return reports;
|
||||
};
|
||||
|
||||
Flags.addReport = async function (flagId, uid, reason, timestamp) {
|
||||
// adds to reporters/report zsets
|
||||
await Promise.all([
|
||||
db.sortedSetAdd(`flags:byReporter:${uid}`, timestamp, flagId),
|
||||
db.sortedSetAdd(`flag:${flagId}:reports`, timestamp, reason),
|
||||
db.sortedSetAdd(`flag:${flagId}:reporters`, timestamp, uid),
|
||||
]);
|
||||
};
|
||||
|
||||
Flags.exists = async function (type, id, uid) {
|
||||
return await db.isSortedSetMember('flags:hash', [type, id, uid].join(':'));
|
||||
};
|
||||
@@ -386,6 +431,10 @@ Flags.targetExists = async function (type, id) {
|
||||
throw new Error('[[error:invalid-data]]');
|
||||
};
|
||||
|
||||
Flags.targetFlagged = async function (type, id) {
|
||||
return await db.sortedSetScore('flags:byTarget', [type, id].join(':')) >= 1;
|
||||
};
|
||||
|
||||
Flags.getTargetUid = async function (type, id) {
|
||||
if (type === 'post') {
|
||||
return await posts.getPostField(id, 'uid');
|
||||
@@ -545,7 +594,7 @@ Flags.notify = async function (flagObj, uid) {
|
||||
|
||||
notifObj = await notifications.create({
|
||||
type: 'new-post-flag',
|
||||
bodyShort: '[[notifications:user_flagged_post_in, ' + flagObj.reporter.username + ', ' + titleEscaped + ']]',
|
||||
bodyShort: '[[notifications:user_flagged_post_in, ' + flagObj.reports[flagObj.reports.length - 1].reporter.username + ', ' + titleEscaped + ']]',
|
||||
bodyLong: flagObj.description,
|
||||
pid: flagObj.targetId,
|
||||
path: '/flags/' + flagObj.flagId,
|
||||
@@ -558,7 +607,7 @@ Flags.notify = async function (flagObj, uid) {
|
||||
} else if (flagObj.type === 'user') {
|
||||
notifObj = await notifications.create({
|
||||
type: 'new-user-flag',
|
||||
bodyShort: '[[notifications:user_flagged_user, ' + flagObj.reporter.username + ', ' + flagObj.target.username + ']]',
|
||||
bodyShort: '[[notifications:user_flagged_user, ' + flagObj.reports[flagObj.reports.length - 1].reporter.username + ', ' + flagObj.target.username + ']]',
|
||||
bodyLong: flagObj.description,
|
||||
path: '/flags/' + flagObj.flagId,
|
||||
nid: 'flag:user:' + flagObj.targetId + ':uid:' + uid,
|
||||
|
||||
@@ -13,22 +13,30 @@ module.exports = {
|
||||
|
||||
let flags = await db.getSortedSetRange('flags:datetime', 0, -1);
|
||||
flags = flags.map(flagId => `flag:${flagId}`);
|
||||
flags = await db.getObjectsFields(flags, ['flagId', 'type', 'targetId']);
|
||||
flags = await db.getObjectsFields(flags, ['flagId', 'type', 'targetId', 'uid', 'description', 'datetime']);
|
||||
progress.total = flags.length;
|
||||
|
||||
await batch.processArray(flags, async function (subset) {
|
||||
progress.incr(subset.length);
|
||||
|
||||
await Promise.all(subset.map(async (flagObj) => {
|
||||
const methods = [];
|
||||
switch (flagObj.type) {
|
||||
case 'post':
|
||||
await posts.setPostField(flagObj.targetId, 'flagId', flagObj.flagId);
|
||||
methods.push(posts.setPostField.bind(posts, flagObj.targetId, 'flagId', flagObj.flagId));
|
||||
break;
|
||||
|
||||
case 'user':
|
||||
await user.setUserField(flagObj.targetId, 'flagId', flagObj.flagId);
|
||||
methods.push(user.setUserField.bind(user, flagObj.targetId, 'flagId', flagObj.flagId));
|
||||
break;
|
||||
}
|
||||
|
||||
methods.push(
|
||||
db.sortedSetAdd.bind(db, `flag:${flagObj.flagId}:reports`, flagObj.datetime, flagObj.description),
|
||||
db.sortedSetAdd.bind(db, `flag:${flagObj.flagId}:reporters`, flagObj.datetime, flagObj.uid)
|
||||
);
|
||||
|
||||
await Promise.all(methods.map(async method => method()));
|
||||
}));
|
||||
}, {
|
||||
progress: progress,
|
||||
|
||||
Reference in New Issue
Block a user