refactor: async/await flags

v1.18.x
Barış Soner Uşaklı 6 years ago
parent 310c6fd33f
commit 0ced71be39

@ -105,14 +105,11 @@ Flags.get = async function (flagId) {
return data.flag; return data.flag;
}; };
Flags.list = function (filters, uid, callback) { Flags.list = async function (filters, uid) {
if (typeof filters === 'function' && !uid && !callback) { filters = filters || {};
callback = filters;
filters = {};
}
var sets = []; let sets = [];
var orSets = []; const orSets = [];
// Default filter // Default filter
filters.page = filters.hasOwnProperty('page') ? Math.abs(parseInt(filters.page, 10) || 1) : 1; filters.page = filters.hasOwnProperty('page') ? Math.abs(parseInt(filters.page, 10) || 1) : 1;
@ -129,168 +126,105 @@ Flags.list = function (filters, uid, callback) {
} }
sets = (sets.length || orSets.length) ? sets : ['flags:datetime']; // No filter default sets = (sets.length || orSets.length) ? sets : ['flags:datetime']; // No filter default
async.waterfall([ let flagIds = [];
function (next) {
if (sets.length === 1) { if (sets.length === 1) {
db.getSortedSetRevRange(sets[0], 0, -1, next); flagIds = await db.getSortedSetRevRange(sets[0], 0, -1);
} else if (sets.length > 1) { } else if (sets.length > 1) {
db.getSortedSetRevIntersect({ sets: sets, start: 0, stop: -1, aggregate: 'MAX' }, next); flagIds = await db.getSortedSetRevIntersect({ sets: sets, start: 0, stop: -1, aggregate: 'MAX' });
} else {
next(null, []);
}
},
function (flagIds, next) {
// Find flags according to "or" rules, if any
if (orSets.length) {
db.getSortedSetRevUnion({ sets: orSets, start: 0, stop: -1, aggregate: 'MAX' }, function (err, _flagIds) {
if (err) {
return next(err);
} }
if (orSets.length) {
const _flagIds = await db.getSortedSetRevUnion({ sets: orSets, start: 0, stop: -1, aggregate: 'MAX' });
if (sets.length) { if (sets.length) {
// If flag ids are already present, return a subset of flags that are in both sets // If flag ids are already present, return a subset of flags that are in both sets
next(null, _.intersection(flagIds, _flagIds)); flagIds = _.intersection(flagIds, _flagIds);
} else { } else {
// Otherwise, return all flags returned via orSets // Otherwise, return all flags returned via orSets
next(null, _.union(flagIds, _flagIds)); flagIds = _.union(flagIds, _flagIds);
} }
});
} else {
setImmediate(next, null, flagIds);
} }
},
function (flagIds, next) {
// Create subset for parsing based on page number (n=20) // Create subset for parsing based on page number (n=20)
const flagsPerPage = Math.abs(parseInt(filters.perPage, 10) || 1); const flagsPerPage = Math.abs(parseInt(filters.perPage, 10) || 1);
const pageCount = Math.ceil(flagIds.length / flagsPerPage); const pageCount = Math.ceil(flagIds.length / flagsPerPage);
flagIds = flagIds.slice((filters.page - 1) * flagsPerPage, filters.page * flagsPerPage); flagIds = flagIds.slice((filters.page - 1) * flagsPerPage, filters.page * flagsPerPage);
async.map(flagIds, function (flagId, next) { const flags = await Promise.all(flagIds.map(async (flagId) => {
async.waterfall([ let flagObj = await db.getObject('flag:' + flagId);
async.apply(db.getObject, 'flag:' + flagId), const userObj = await user.getUserFields(flagObj.uid, ['username', 'picture']);
function (flagObj, next) { flagObj = {
user.getUserFields(flagObj.uid, ['username', 'picture'], function (err, userObj) { state: 'open',
next(err, { state: 'open',
...flagObj, ...flagObj,
reporter: { reporter: {
username: userObj.username, username: userObj.username,
picture: userObj.picture, picture: userObj.picture,
'icon:bgColor': userObj['icon:bgColor'], 'icon:bgColor': userObj['icon:bgColor'],
'icon:text': userObj['icon:text'], 'icon:text': userObj['icon:text'],
} });
});
}, },
], function (err, flagObj) { };
if (err) { const stateToLabel = {
return next(err); open: 'info',
} wip: 'warning',
resolved: 'success',
switch (flagObj.state) { rejected: 'danger',
case 'open': };
flagObj.labelClass = 'info'; flagObj.labelClass = stateToLabel[flagObj.state];
break;
case 'wip':
flagObj.labelClass = 'warning';
break;
case 'resolved':
flagObj.labelClass = 'success';
break;
case 'rejected':
flagObj.labelClass = 'danger';
break;
}
next(null, Object.assign(flagObj, { return Object.assign(flagObj, {
description: validator.escape(String(flagObj.description)), description: validator.escape(String(flagObj.description)),
target_readable: flagObj.type.charAt(0).toUpperCase() + flagObj.type.slice(1) + ' ' + flagObj.targetId, target_readable: flagObj.type.charAt(0).toUpperCase() + flagObj.type.slice(1) + ' ' + flagObj.targetId,
datetimeISO: utils.toISOString(flagObj.datetime), datetimeISO: utils.toISOString(flagObj.datetime),
}));
});
}, function (err, flags) {
next(err, flags, pageCount);
}); });
}, }));
function (flags, pageCount, next) {
plugins.fireHook('filter:flags.list', { const data = await plugins.fireHook('filter:flags.list', {
flags: flags, flags: flags,
page: filters.page, page: filters.page,
}, function (err, data) { });
next(err, {
return {
flags: data.flags, flags: data.flags,
page: data.page, page: data.page,
pageCount: pageCount, pageCount: pageCount,
}); };
});
},
], callback);
}; };
Flags.validate = function (payload, callback) { Flags.validate = async function (payload) {
async.parallel({ const [target, reporter] = await Promise.all([
target: async.apply(Flags.getTarget, payload.type, payload.id, payload.uid), Flags.getTarget(payload.type, payload.id, payload.uid),
reporter: async.apply(user.getUserData, payload.uid), user.getUserData(payload.uid),
}, function (err, data) { ]);
if (err) {
return callback(err);
}
if (!data.target) {
return callback(new Error('[[error:invalid-data]]'));
} else if (data.target.deleted) {
return callback(new Error('[[error:post-deleted]]'));
} else if (!data.reporter || !data.reporter.userslug) {
return callback(new Error('[[error:no-user]]'));
} else if (data.reporter.banned) {
return callback(new Error('[[error:user-banned]]'));
}
switch (payload.type) {
case 'post':
privileges.posts.canEdit(payload.id, payload.uid, function (err, editable) {
if (err) {
return callback(err);
}
// Check if reporter meets rep threshold (or can edit the target post, in which case threshold does not apply) if (!target) {
if (!editable.flag && !meta.config['reputation:disabled'] && data.reporter.reputation < meta.config['min:rep:flag']) { throw new Error('[[error:invalid-data]]');
return callback(new Error('[[error:not-enough-reputation-to-flag]]')); } else if (target.deleted) {
throw new Error('[[error:post-deleted]]');
} else if (!reporter || !reporter.userslug) {
throw new Error('[[error:no-user]]');
} else if (reporter.banned) {
throw new Error('[[error:user-banned]]');
} }
callback(); if (payload.type === 'post') {
}); const editable = await privileges.posts.canEdit(payload.id, payload.uid);
break; if (!editable.flag && !meta.config['reputation:disabled'] && reporter.reputation < meta.config['min:rep:flag']) {
throw new Error('[[error:not-enough-reputation-to-flag]]');
case 'user':
privileges.users.canEdit(payload.uid, payload.id, function (err, editable) {
if (err) {
return callback(err);
} }
} else if (payload.type === 'user') {
// Check if reporter meets rep threshold (or can edit the target user, in which case threshold does not apply) const editable = await privileges.users.canEdit(payload.uid, payload.id);
if (!editable && !meta.config['reputation:disabled'] && data.reporter.reputation < meta.config['min:rep:flag']) { if (!editable && !meta.config['reputation:disabled'] && reporter.reputation < meta.config['min:rep:flag']) {
return callback(new Error('[[error:not-enough-reputation-to-flag]]')); throw new Error('[[error:not-enough-reputation-to-flag]]');
} }
} else {
callback(); throw new Error('[[error:invalid-data]]');
});
break;
default:
callback(new Error('[[error:invalid-data]]'));
break;
} }
});
}; };
Flags.getNotes = function (flagId, callback) { Flags.getNotes = async function (flagId) {
async.waterfall([ let notes = await db.getSortedSetRevRangeWithScores('flag:' + flagId + ':notes', 0, -1);
async.apply(db.getSortedSetRevRangeWithScores.bind(db), 'flag:' + flagId + ':notes', 0, -1), const uids = [];
function (notes, next) {
var uids = [];
var noteObj;
notes = notes.map(function (note) { notes = notes.map(function (note) {
try { const noteObj = JSON.parse(note.value);
noteObj = JSON.parse(note.value);
uids.push(noteObj[0]); uids.push(noteObj[0]);
return { return {
uid: noteObj[0], uid: noteObj[0],
@ -298,110 +232,71 @@ Flags.getNotes = function (flagId, callback) {
datetime: note.score, datetime: note.score,
datetimeISO: utils.toISOString(note.score), datetimeISO: utils.toISOString(note.score),
}; };
} catch (e) {
return next(e);
}
}); });
next(null, notes, uids); const userData = await user.getUsersFields(uids, ['username', 'userslug', 'picture']);
}, return notes.map(function (note, idx) {
function (notes, uids, next) { note.user = userData[idx];
user.getUsersFields(uids, ['username', 'userslug', 'picture'], function (err, users) {
if (err) {
return next(err);
}
next(null, notes.map(function (note, idx) {
note.user = users[idx];
note.content = validator.escape(note.content); note.content = validator.escape(note.content);
return note; return note;
}));
}); });
},
], callback);
}; };
Flags.create = function (type, id, uid, reason, timestamp, callback) { Flags.create = async function (type, id, uid, reason, timestamp) {
var targetUid; let doHistoryAppend = false;
var targetCid; if (!timestamp) {
var doHistoryAppend = false;
// timestamp is optional
if (typeof timestamp === 'function' && !callback) {
callback = timestamp;
timestamp = Date.now(); timestamp = Date.now();
doHistoryAppend = true; doHistoryAppend = true;
} }
const [exists, targetExists, targetUid, targetCid] = await Promise.all([
async.waterfall([
function (next) {
async.parallel([
// Sanity checks // Sanity checks
async.apply(Flags.exists, type, id, uid), Flags.exists(type, id, uid),
async.apply(Flags.targetExists, type, id), Flags.targetExists(type, id),
// Extra data for zset insertion // Extra data for zset insertion
async.apply(Flags.getTargetUid, type, id), Flags.getTargetUid(type, id),
async.apply(Flags.getTargetCid, type, id), Flags.getTargetCid(type, id),
], function (err, checks) { ]);
if (err) { if (exists) {
return next(err); throw new Error('[[error:already-flagged]]');
} else if (!targetExists) {
throw new Error('[[error:invalid-data]]');
} }
const flagId = await db.incrObjectField('global', 'nextFlagId');
targetUid = checks[2] || null; await db.setObject('flag:' + flagId, {
targetCid = checks[3] || null;
if (checks[0]) {
return next(new Error('[[error:already-flagged]]'));
} else if (!checks[1]) {
return next(new Error('[[error:invalid-data]]'));
}
next();
});
},
async.apply(db.incrObjectField, 'global', 'nextFlagId'),
function (flagId, next) {
var tasks = [
async.apply(db.setObject.bind(db), 'flag:' + flagId, {
flagId: flagId, flagId: flagId,
type: type, type: type,
targetId: id, targetId: id,
description: reason, description: reason,
uid: uid, uid: uid,
datetime: timestamp, datetime: timestamp,
}), });
async.apply(db.sortedSetAdd.bind(db), 'flags:datetime', timestamp, flagId), // by time, the default await db.sortedSetAdd('flags:datetime', timestamp, flagId); // by time, the default
async.apply(db.sortedSetAdd.bind(db), 'flags:byReporter:' + uid, timestamp, flagId), // by reporter await db.sortedSetAdd('flags:byReporter:' + uid, timestamp, flagId); // by reporter
async.apply(db.sortedSetAdd.bind(db), 'flags:byType:' + type, timestamp, flagId), // by flag type await db.sortedSetAdd('flags:byType:' + type, timestamp, flagId); // by flag type
async.apply(db.sortedSetAdd.bind(db), 'flags:hash', flagId, [type, id, uid].join(':')), // save zset for duplicate checking await db.sortedSetAdd('flags:hash', flagId, [type, id, uid].join(':')); // save zset for duplicate checking
async.apply(analytics.increment, 'flags'), // some fancy analytics await analytics.increment('flags'); // some fancy analytics
];
if (targetUid) { if (targetUid) {
tasks.push(async.apply(db.sortedSetAdd.bind(db), 'flags:byTargetUid:' + targetUid, timestamp, flagId)); // by target uid await db.sortedSetAdd('flags:byTargetUid:' + targetUid, timestamp, flagId); // by target uid
} }
if (targetCid) { if (targetCid) {
tasks.push(async.apply(db.sortedSetAdd.bind(db), 'flags:byCid:' + targetCid, timestamp, flagId)); // by target cid await db.sortedSetAdd('flags:byCid:' + targetCid, timestamp, flagId); // by target cid
} }
if (type === 'post') { if (type === 'post') {
tasks.push(async.apply(db.sortedSetAdd.bind(db), 'flags:byPid:' + id, timestamp, flagId)); // by target pid await db.sortedSetAdd('flags:byPid:' + id, timestamp, flagId); // by target pid
if (targetUid) { if (targetUid) {
tasks.push(async.apply(db.sortedSetIncrBy.bind(db), 'users:flags', 1, targetUid)); await db.sortedSetIncrBy('users:flags', 1, targetUid);
tasks.push(async.apply(user.incrementUserFieldBy, targetUid, 'flags', 1)); await user.incrementUserFieldBy(targetUid, 'flags', 1);
} }
} }
if (doHistoryAppend) { if (doHistoryAppend) {
tasks.push(async.apply(Flags.update, flagId, uid, { state: 'open' })); await Flags.update(flagId, uid, { state: 'open' });
} }
async.series(tasks, function (err) { return await Flags.get(flagId);
next(err, flagId);
});
},
async.apply(Flags.get),
], callback);
}; };
Flags.exists = async function (type, id, uid) { Flags.exists = async function (type, id, uid) {

@ -12,7 +12,7 @@ const plugins = require('../plugins');
module.exports = function (Topics) { module.exports = function (Topics) {
Topics.getSortedTopics = async function (params) { Topics.getSortedTopics = async function (params) {
var data = { const data = {
nextStart: 0, nextStart: 0,
topicCount: 0, topicCount: 0,
topics: [], topics: [],

Loading…
Cancel
Save