From 1aa70c57eb60daa51bf1181335f37e5a46ffbb72 Mon Sep 17 00:00:00 2001 From: Julian Lam Date: Wed, 23 Nov 2016 19:41:35 -0500 Subject: [PATCH] WIP --- src/controllers/admin/flags.js | 3 +- src/flags.js | 484 +++++++++++++++++++++++++++++++++ src/posts.js | 1 - src/posts/delete.js | 3 +- src/posts/flags.js | 417 ---------------------------- src/socket.io/posts/flag.js | 9 +- src/upgrade.js | 7 +- src/user/admin.js | 3 +- 8 files changed, 499 insertions(+), 428 deletions(-) create mode 100644 src/flags.js delete mode 100644 src/posts/flags.js diff --git a/src/controllers/admin/flags.js b/src/controllers/admin/flags.js index 1b31a95ff4..80c31ba60a 100644 --- a/src/controllers/admin/flags.js +++ b/src/controllers/admin/flags.js @@ -5,6 +5,7 @@ var validator = require('validator'); var posts = require('../../posts'); var user = require('../../user'); +var flags = require('../../flags'); var categories = require('../../categories'); var analytics = require('../../analytics'); var pagination = require('../../pagination'); @@ -94,7 +95,7 @@ function getFlagData(req, res, callback) { sets.push('uid:' + uid + ':flag:pids'); } - posts.getFlags(sets, cid, req.uid, start, stop, next); + flags.get(sets, cid, req.uid, start, stop, next); } ], callback); } diff --git a/src/flags.js b/src/flags.js new file mode 100644 index 0000000000..1e6294aecb --- /dev/null +++ b/src/flags.js @@ -0,0 +1,484 @@ + + +'use strict'; + +var async = require('async'); +var winston = require('winston'); +var db = require('./database'); +var user = require('./user'); +var analytics = require('./analytics'); +var topics = require('./topics'); +var posts = require('./posts'); +var utils = require('../public/src/utils'); + +var Flags = { + _defaults: { + state: 'open' + } +}; + +Flags.create = function (type, id, uid, reason, callback) { + async.waterfall([ + function (next) { + // Sanity checks + async.parallel([ + async.apply(Flags.exists, type, id, uid), + async.apply(Flags.targetExists, type, id) + ], function (err, checks) { + if (checks[0]) { + return next(new Error('[[error:already-flagged]]')); + } else if (!checks[1]) { + return next(new Error('[[error:invalid-data]]')); + } else { + next(); + } + }); + }, + function (next) { + var flagId = utils.generateUUID(); + + async.parallel([ + async.apply(db.setObject.bind(db), 'flag:' + flagId, _.defaults({ + description: reason + }), Flags._defaults), + async.apply(db.sortedSetAdd.bind(db), 'flags:datetime', now, flagId) + ], function (err) { + if (err) { + return next(err); + } + }); + } + ], function (err) { + if (err) { + return callback(err); + } + + console.log('done', arguments); + process.exit(); + }); + // if (!parseInt(uid, 10) || !reason) { + // return callback(); + // } + + // async.waterfall([ + // function (next) { + // async.parallel({ + // hasFlagged: async.apply(Flags.isFlaggedByUser, post.pid, uid), + // exists: async.apply(Posts.exists, post.pid) + // }, next); + // }, + // function (results, next) { + // if (!results.exists) { + // return next(new Error('[[error:no-post]]')); + // } + + // if (results.hasFlagged) { + // return next(new Error('[[error:already-flagged]]')); + // } + + // var now = Date.now(); + // async.parallel([ + // function (next) { + // db.sortedSetAdd('posts:flagged', now, post.pid, next); + // }, + // function (next) { + // db.sortedSetIncrBy('posts:flags:count', 1, post.pid, next); + // }, + // function (next) { + // db.incrObjectField('post:' + post.pid, 'flags', next); + // }, + // function (next) { + // db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next); + // }, + // function (next) { + // db.sortedSetAdd('pid:' + post.pid + ':flag:uid:reason', 0, uid + ':' + reason, next); + // }, + // function (next) { + // if (parseInt(post.uid, 10)) { + // async.parallel([ + // async.apply(db.sortedSetIncrBy, 'users:flags', 1, post.uid), + // async.apply(db.incrObjectField, 'user:' + post.uid, 'flags'), + // async.apply(db.sortedSetAdd, 'uid:' + post.uid + ':flag:pids', now, post.pid) + // ], next); + // } else { + // next(); + // } + // } + // ], next); + // }, + // function (data, next) { + // openNewFlag(post.pid, uid, next); + // } + // ], function (err) { + // if (err) { + // return callback(err); + // } + // analytics.increment('flags'); + // callback(); + // }); +}; + +function openNewFlag(pid, uid, callback) { + db.sortedSetScore('posts:flags:count', pid, function (err, count) { + if (err) { + return callback(err); + } + if (count === 1) { // Only update state on new flag + Flags.update(uid, pid, { + state: 'open' + }, callback); + } else { + callback(); + } + }); +} + +Flags.exists = function (type, id, uid, callback) { + db.isObjectField('flagHash:flagId', [type, id, uid].join(':'), callback); +}; + +Flags.targetExists = function (type, id, callback) { + switch (type) { + case 'topic': + topics.exists(id, callback); + break; + + case 'post': + posts.exists(id, callback); + break; + } +}; + +/* new signature (type, id, uid, callback) */ +Flags.isFlaggedByUser = function (pid, uid, callback) { + db.isSortedSetMember('pid:' + pid + ':flag:uids', uid, callback); +}; + +Flags.dismiss = function (pid, callback) { + async.waterfall([ + function (next) { + db.getObjectFields('post:' + pid, ['pid', 'uid', 'flags'], next); + }, + function (postData, next) { + if (!postData.pid) { + return callback(); + } + async.parallel([ + function (next) { + if (parseInt(postData.uid, 10)) { + if (parseInt(postData.flags, 10) > 0) { + async.parallel([ + async.apply(db.sortedSetIncrBy, 'users:flags', -postData.flags, postData.uid), + async.apply(db.incrObjectFieldBy, 'user:' + postData.uid, 'flags', -postData.flags) + ], next); + } else { + next(); + } + } else { + next(); + } + }, + function (next) { + db.sortedSetsRemove([ + 'posts:flagged', + 'posts:flags:count', + 'uid:' + postData.uid + ':flag:pids' + ], pid, next); + }, + function (next) { + async.series([ + function (next) { + db.getSortedSetRange('pid:' + pid + ':flag:uids', 0, -1, function (err, uids) { + if (err) { + return next(err); + } + + async.each(uids, function (uid, next) { + var nid = 'post_flag:' + pid + ':uid:' + uid; + async.parallel([ + async.apply(db.delete, 'notifications:' + nid), + async.apply(db.sortedSetRemove, 'notifications', 'post_flag:' + pid + ':uid:' + uid) + ], next); + }, next); + }); + }, + async.apply(db.delete, 'pid:' + pid + ':flag:uids') + ], next); + }, + async.apply(db.deleteObjectField, 'post:' + pid, 'flags'), + async.apply(db.delete, 'pid:' + pid + ':flag:uid:reason'), + async.apply(db.deleteObjectFields, 'post:' + pid, ['flag:state', 'flag:assignee', 'flag:notes', 'flag:history']) + ], next); + }, + function (results, next) { + db.sortedSetsRemoveRangeByScore(['users:flags'], '-inf', 0, next); + } + ], callback); +}; + +// Pretty sure we don't need this method... +Flags.dismissAll = function (callback) { + db.getSortedSetRange('posts:flagged', 0, -1, function (err, pids) { + if (err) { + return callback(err); + } + async.eachSeries(pids, Flags.dismiss, callback); + }); +}; + +Flags.dismissByUid = function (uid, callback) { + db.getSortedSetRange('uid:' + uid + ':flag:pids', 0, -1, function (err, pids) { + if (err) { + return callback(err); + } + async.eachSeries(pids, Flags.dismiss, callback); + }); +}; + +Flags.get = function (set, cid, uid, start, stop, callback) { + async.waterfall([ + function (next) { + if (Array.isArray(set)) { + db.getSortedSetRevIntersect({sets: set, start: start, stop: -1, aggregate: 'MAX'}, next); + } else { + db.getSortedSetRevRange(set, start, -1, next); + } + }, + function (pids, next) { + if (cid) { + Posts.filterPidsByCid(pids, cid, next); + } else { + process.nextTick(next, null, pids); + } + }, + function (pids, next) { + getFlaggedPostsWithReasons(pids, uid, next); + }, + function (posts, next) { + var count = posts.length; + var end = stop - start + 1; + next(null, {posts: posts.slice(0, stop === -1 ? undefined : end), count: count}); + } + ], callback); +}; + +function getFlaggedPostsWithReasons(pids, uid, callback) { + async.waterfall([ + function (next) { + async.parallel({ + uidsReasons: function (next) { + async.map(pids, function (pid, next) { + db.getSortedSetRange('pid:' + pid + ':flag:uid:reason', 0, -1, next); + }, next); + }, + posts: function (next) { + Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags', 'flag:assignee', 'flag:state', 'flag:notes', 'flag:history']}, next); + } + }, next); + }, + function (results, next) { + async.map(results.uidsReasons, function (uidReasons, next) { + async.map(uidReasons, function (uidReason, next) { + var uid = uidReason.split(':')[0]; + var reason = uidReason.substr(uidReason.indexOf(':') + 1); + user.getUserFields(uid, ['username', 'userslug', 'picture'], function (err, userData) { + next(err, {user: userData, reason: reason}); + }); + }, next); + }, function (err, reasons) { + if (err) { + return callback(err); + } + + results.posts.forEach(function (post, index) { + if (post) { + post.flagReasons = reasons[index]; + } + }); + + next(null, results.posts); + }); + }, + async.apply(Posts.expandFlagHistory), + function (posts, next) { + // Parse out flag data into its own object inside each post hash + async.map(posts, function (postObj, next) { + for(var prop in postObj) { + postObj.flagData = postObj.flagData || {}; + + if (postObj.hasOwnProperty(prop) && prop.startsWith('flag:')) { + postObj.flagData[prop.slice(5)] = postObj[prop]; + + if (prop === 'flag:state') { + switch(postObj[prop]) { + case 'open': + postObj.flagData.labelClass = 'info'; + break; + case 'wip': + postObj.flagData.labelClass = 'warning'; + break; + case 'resolved': + postObj.flagData.labelClass = 'success'; + break; + case 'rejected': + postObj.flagData.labelClass = 'danger'; + break; + } + } + + delete postObj[prop]; + } + } + + if (postObj.flagData.assignee) { + user.getUserFields(parseInt(postObj.flagData.assignee, 10), ['username', 'picture'], function (err, userData) { + if (err) { + return next(err); + } + + postObj.flagData.assigneeUser = userData; + next(null, postObj); + }); + } else { + setImmediate(next.bind(null, null, postObj)); + } + }, next); + } + ], callback); +} + +// New method signature (type, id, flagObj, callback) and name (.update()) +// uid used in history string, which should be rewritten too. +Flags.update = function (uid, pid, flagObj, callback) { + // Retrieve existing flag data to compare for history-saving purposes + var changes = []; + var changeset = {}; + var prop; + + Posts.getPostData(pid, function (err, postData) { + if (err) { + return callback(err); + } + + // Track new additions + for(prop in flagObj) { + if (flagObj.hasOwnProperty(prop) && !postData.hasOwnProperty('flag:' + prop) && flagObj[prop].length) { + changes.push(prop); + } + } + + // Track changed items + for(prop in postData) { + if ( + postData.hasOwnProperty(prop) && prop.startsWith('flag:') && + flagObj.hasOwnProperty(prop.slice(5)) && + postData[prop] !== flagObj[prop.slice(5)] + ) { + changes.push(prop.slice(5)); + } + } + + changeset = changes.reduce(function (memo, prop) { + memo['flag:' + prop] = flagObj[prop]; + return memo; + }, {}); + + // Append changes to history string + if (changes.length) { + try { + var history = JSON.parse(postData['flag:history'] || '[]'); + + changes.forEach(function (property) { + switch(property) { + case 'assignee': // intentional fall-through + case 'state': + history.unshift({ + uid: uid, + type: property, + value: flagObj[property], + timestamp: Date.now() + }); + break; + + case 'notes': + history.unshift({ + uid: uid, + type: property, + timestamp: Date.now() + }); + } + }); + + changeset['flag:history'] = JSON.stringify(history); + } catch (e) { + winston.warn('[flags/update] Unable to deserialise post flag history, likely malformed data'); + } + } + + // Save flag data into post hash + if (changes.length) { + Posts.setPostFields(pid, changeset, callback); + } else { + setImmediate(callback); + } + }); +}; + +// To be rewritten and deprecated +Flags.expandFlagHistory = function (posts, callback) { + // Expand flag history + async.map(posts, function (post, next) { + var history; + try { + history = JSON.parse(post['flag:history'] || '[]'); + } catch (e) { + winston.warn('[flags/get] Unable to deserialise post flag history, likely malformed data'); + return callback(e); + } + + async.map(history, function (event, next) { + event.timestampISO = new Date(event.timestamp).toISOString(); + + async.parallel([ + function (next) { + user.getUserFields(event.uid, ['username', 'picture'], function (err, userData) { + if (err) { + return next(err); + } + + event.user = userData; + next(); + }); + }, + function (next) { + if (event.type === 'assignee') { + user.getUserField(parseInt(event.value, 10), 'username', function (err, username) { + if (err) { + return next(err); + } + + event.label = username || 'Unknown user'; + next(null); + }); + } else if (event.type === 'state') { + event.label = '[[topic:flag_manage_state_' + event.value + ']]'; + setImmediate(next); + } else { + setImmediate(next); + } + } + ], function (err) { + next(err, event); + }); + }, function (err, history) { + if (err) { + return next(err); + } + + post['flag:history'] = history; + next(null, post); + }); + }, callback); +}; + +module.exports = Flags; \ No newline at end of file diff --git a/src/posts.js b/src/posts.js index 047917cb5f..b476b84414 100644 --- a/src/posts.js +++ b/src/posts.js @@ -21,7 +21,6 @@ var plugins = require('./plugins'); require('./posts/category')(Posts); require('./posts/summary')(Posts); require('./posts/recent')(Posts); - require('./posts/flags')(Posts); require('./posts/tools')(Posts); require('./posts/votes')(Posts); require('./posts/bookmarks')(Posts); diff --git a/src/posts/delete.js b/src/posts/delete.js index 7a1d3d0cc8..ebf902aef2 100644 --- a/src/posts/delete.js +++ b/src/posts/delete.js @@ -8,6 +8,7 @@ var topics = require('../topics'); var user = require('../user'); var notifications = require('../notifications'); var plugins = require('../plugins'); +var flags = require('../flags'); module.exports = function (Posts) { @@ -145,7 +146,7 @@ module.exports = function (Posts) { db.sortedSetsRemove(['posts:pid', 'posts:flagged'], pid, next); }, function (next) { - Posts.dismissFlag(pid, next); + flags.dismiss(pid, next); } ], function (err) { if (err) { diff --git a/src/posts/flags.js b/src/posts/flags.js deleted file mode 100644 index e81da20f95..0000000000 --- a/src/posts/flags.js +++ /dev/null @@ -1,417 +0,0 @@ - - -'use strict'; - -var async = require('async'); -var winston = require('winston'); -var db = require('../database'); -var user = require('../user'); -var analytics = require('../analytics'); - -module.exports = function (Posts) { - - Posts.flag = function (post, uid, reason, callback) { - if (!parseInt(uid, 10) || !reason) { - return callback(); - } - - async.waterfall([ - function (next) { - async.parallel({ - hasFlagged: async.apply(Posts.isFlaggedByUser, post.pid, uid), - exists: async.apply(Posts.exists, post.pid) - }, next); - }, - function (results, next) { - if (!results.exists) { - return next(new Error('[[error:no-post]]')); - } - - if (results.hasFlagged) { - return next(new Error('[[error:already-flagged]]')); - } - - var now = Date.now(); - async.parallel([ - function (next) { - db.sortedSetAdd('posts:flagged', now, post.pid, next); - }, - function (next) { - db.sortedSetIncrBy('posts:flags:count', 1, post.pid, next); - }, - function (next) { - db.incrObjectField('post:' + post.pid, 'flags', next); - }, - function (next) { - db.sortedSetAdd('pid:' + post.pid + ':flag:uids', now, uid, next); - }, - function (next) { - db.sortedSetAdd('pid:' + post.pid + ':flag:uid:reason', 0, uid + ':' + reason, next); - }, - function (next) { - if (parseInt(post.uid, 10)) { - async.parallel([ - async.apply(db.sortedSetIncrBy, 'users:flags', 1, post.uid), - async.apply(db.incrObjectField, 'user:' + post.uid, 'flags'), - async.apply(db.sortedSetAdd, 'uid:' + post.uid + ':flag:pids', now, post.pid) - ], next); - } else { - next(); - } - } - ], next); - }, - function (data, next) { - openNewFlag(post.pid, uid, next); - } - ], function (err) { - if (err) { - return callback(err); - } - analytics.increment('flags'); - callback(); - }); - }; - - function openNewFlag(pid, uid, callback) { - db.sortedSetScore('posts:flags:count', pid, function (err, count) { - if (err) { - return callback(err); - } - if (count === 1) { // Only update state on new flag - Posts.updateFlagData(uid, pid, { - state: 'open' - }, callback); - } else { - callback(); - } - }); - } - - Posts.isFlaggedByUser = function (pid, uid, callback) { - db.isSortedSetMember('pid:' + pid + ':flag:uids', uid, callback); - }; - - Posts.dismissFlag = function (pid, callback) { - async.waterfall([ - function (next) { - db.getObjectFields('post:' + pid, ['pid', 'uid', 'flags'], next); - }, - function (postData, next) { - if (!postData.pid) { - return callback(); - } - async.parallel([ - function (next) { - if (parseInt(postData.uid, 10)) { - if (parseInt(postData.flags, 10) > 0) { - async.parallel([ - async.apply(db.sortedSetIncrBy, 'users:flags', -postData.flags, postData.uid), - async.apply(db.incrObjectFieldBy, 'user:' + postData.uid, 'flags', -postData.flags) - ], next); - } else { - next(); - } - } else { - next(); - } - }, - function (next) { - db.sortedSetsRemove([ - 'posts:flagged', - 'posts:flags:count', - 'uid:' + postData.uid + ':flag:pids' - ], pid, next); - }, - function (next) { - async.series([ - function (next) { - db.getSortedSetRange('pid:' + pid + ':flag:uids', 0, -1, function (err, uids) { - if (err) { - return next(err); - } - - async.each(uids, function (uid, next) { - var nid = 'post_flag:' + pid + ':uid:' + uid; - async.parallel([ - async.apply(db.delete, 'notifications:' + nid), - async.apply(db.sortedSetRemove, 'notifications', 'post_flag:' + pid + ':uid:' + uid) - ], next); - }, next); - }); - }, - async.apply(db.delete, 'pid:' + pid + ':flag:uids') - ], next); - }, - async.apply(db.deleteObjectField, 'post:' + pid, 'flags'), - async.apply(db.delete, 'pid:' + pid + ':flag:uid:reason'), - async.apply(db.deleteObjectFields, 'post:' + pid, ['flag:state', 'flag:assignee', 'flag:notes', 'flag:history']) - ], next); - }, - function (results, next) { - db.sortedSetsRemoveRangeByScore(['users:flags'], '-inf', 0, next); - } - ], callback); - }; - - Posts.dismissAllFlags = function (callback) { - db.getSortedSetRange('posts:flagged', 0, -1, function (err, pids) { - if (err) { - return callback(err); - } - async.eachSeries(pids, Posts.dismissFlag, callback); - }); - }; - - Posts.dismissUserFlags = function (uid, callback) { - db.getSortedSetRange('uid:' + uid + ':flag:pids', 0, -1, function (err, pids) { - if (err) { - return callback(err); - } - async.eachSeries(pids, Posts.dismissFlag, callback); - }); - }; - - Posts.getFlags = function (set, cid, uid, start, stop, callback) { - async.waterfall([ - function (next) { - if (Array.isArray(set)) { - db.getSortedSetRevIntersect({sets: set, start: start, stop: -1, aggregate: 'MAX'}, next); - } else { - db.getSortedSetRevRange(set, start, -1, next); - } - }, - function (pids, next) { - if (cid) { - Posts.filterPidsByCid(pids, cid, next); - } else { - process.nextTick(next, null, pids); - } - }, - function (pids, next) { - getFlaggedPostsWithReasons(pids, uid, next); - }, - function (posts, next) { - var count = posts.length; - var end = stop - start + 1; - next(null, {posts: posts.slice(0, stop === -1 ? undefined : end), count: count}); - } - ], callback); - }; - - function getFlaggedPostsWithReasons(pids, uid, callback) { - async.waterfall([ - function (next) { - async.parallel({ - uidsReasons: function (next) { - async.map(pids, function (pid, next) { - db.getSortedSetRange('pid:' + pid + ':flag:uid:reason', 0, -1, next); - }, next); - }, - posts: function (next) { - Posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags', 'flag:assignee', 'flag:state', 'flag:notes', 'flag:history']}, next); - } - }, next); - }, - function (results, next) { - async.map(results.uidsReasons, function (uidReasons, next) { - async.map(uidReasons, function (uidReason, next) { - var uid = uidReason.split(':')[0]; - var reason = uidReason.substr(uidReason.indexOf(':') + 1); - user.getUserFields(uid, ['username', 'userslug', 'picture'], function (err, userData) { - next(err, {user: userData, reason: reason}); - }); - }, next); - }, function (err, reasons) { - if (err) { - return callback(err); - } - - results.posts.forEach(function (post, index) { - if (post) { - post.flagReasons = reasons[index]; - } - }); - - next(null, results.posts); - }); - }, - async.apply(Posts.expandFlagHistory), - function (posts, next) { - // Parse out flag data into its own object inside each post hash - async.map(posts, function (postObj, next) { - for(var prop in postObj) { - postObj.flagData = postObj.flagData || {}; - - if (postObj.hasOwnProperty(prop) && prop.startsWith('flag:')) { - postObj.flagData[prop.slice(5)] = postObj[prop]; - - if (prop === 'flag:state') { - switch(postObj[prop]) { - case 'open': - postObj.flagData.labelClass = 'info'; - break; - case 'wip': - postObj.flagData.labelClass = 'warning'; - break; - case 'resolved': - postObj.flagData.labelClass = 'success'; - break; - case 'rejected': - postObj.flagData.labelClass = 'danger'; - break; - } - } - - delete postObj[prop]; - } - } - - if (postObj.flagData.assignee) { - user.getUserFields(parseInt(postObj.flagData.assignee, 10), ['username', 'picture'], function (err, userData) { - if (err) { - return next(err); - } - - postObj.flagData.assigneeUser = userData; - next(null, postObj); - }); - } else { - setImmediate(next.bind(null, null, postObj)); - } - }, next); - } - ], callback); - } - - Posts.updateFlagData = function (uid, pid, flagObj, callback) { - // Retrieve existing flag data to compare for history-saving purposes - var changes = []; - var changeset = {}; - var prop; - - Posts.getPostData(pid, function (err, postData) { - if (err) { - return callback(err); - } - - // Track new additions - for(prop in flagObj) { - if (flagObj.hasOwnProperty(prop) && !postData.hasOwnProperty('flag:' + prop) && flagObj[prop].length) { - changes.push(prop); - } - } - - // Track changed items - for(prop in postData) { - if ( - postData.hasOwnProperty(prop) && prop.startsWith('flag:') && - flagObj.hasOwnProperty(prop.slice(5)) && - postData[prop] !== flagObj[prop.slice(5)] - ) { - changes.push(prop.slice(5)); - } - } - - changeset = changes.reduce(function (memo, prop) { - memo['flag:' + prop] = flagObj[prop]; - return memo; - }, {}); - - // Append changes to history string - if (changes.length) { - try { - var history = JSON.parse(postData['flag:history'] || '[]'); - - changes.forEach(function (property) { - switch(property) { - case 'assignee': // intentional fall-through - case 'state': - history.unshift({ - uid: uid, - type: property, - value: flagObj[property], - timestamp: Date.now() - }); - break; - - case 'notes': - history.unshift({ - uid: uid, - type: property, - timestamp: Date.now() - }); - } - }); - - changeset['flag:history'] = JSON.stringify(history); - } catch (e) { - winston.warn('[posts/updateFlagData] Unable to deserialise post flag history, likely malformed data'); - } - } - - // Save flag data into post hash - if (changes.length) { - Posts.setPostFields(pid, changeset, callback); - } else { - setImmediate(callback); - } - }); - }; - - Posts.expandFlagHistory = function (posts, callback) { - // Expand flag history - async.map(posts, function (post, next) { - var history; - try { - history = JSON.parse(post['flag:history'] || '[]'); - } catch (e) { - winston.warn('[posts/getFlags] Unable to deserialise post flag history, likely malformed data'); - return callback(e); - } - - async.map(history, function (event, next) { - event.timestampISO = new Date(event.timestamp).toISOString(); - - async.parallel([ - function (next) { - user.getUserFields(event.uid, ['username', 'picture'], function (err, userData) { - if (err) { - return next(err); - } - - event.user = userData; - next(); - }); - }, - function (next) { - if (event.type === 'assignee') { - user.getUserField(parseInt(event.value, 10), 'username', function (err, username) { - if (err) { - return next(err); - } - - event.label = username || 'Unknown user'; - next(null); - }); - } else if (event.type === 'state') { - event.label = '[[topic:flag_manage_state_' + event.value + ']]'; - setImmediate(next); - } else { - setImmediate(next); - } - } - ], function (err) { - next(err, event); - }); - }, function (err, history) { - if (err) { - return next(err); - } - - post['flag:history'] = history; - next(null, post); - }); - }, callback); - }; -}; diff --git a/src/socket.io/posts/flag.js b/src/socket.io/posts/flag.js index 077b88bfc9..88b47058d1 100644 --- a/src/socket.io/posts/flag.js +++ b/src/socket.io/posts/flag.js @@ -12,6 +12,7 @@ var notifications = require('../../notifications'); var plugins = require('../../plugins'); var meta = require('../../meta'); var utils = require('../../../public/src/utils'); +var flags = require('../../flags'); module.exports = function (SocketPosts) { @@ -64,7 +65,7 @@ module.exports = function (SocketPosts) { flaggingUser = user.userData; flaggingUser.uid = socket.uid; - posts.flag(post, socket.uid, data.reason, next); + flags.create('post', post.pid, socket.uid, data.reason, next); }, function (next) { async.parallel({ @@ -119,7 +120,7 @@ module.exports = function (SocketPosts) { if (!isAdminOrGlobalModerator) { return next(new Error('[[no-privileges]]')); } - posts.dismissFlag(pid, next); + flags.dismiss(pid, next); } ], callback); }; @@ -133,7 +134,7 @@ module.exports = function (SocketPosts) { if (!isAdminOrGlobalModerator) { return next(new Error('[[no-privileges]]')); } - posts.dismissAllFlags(next); + flags.dismissAll(next); } ], callback); }; @@ -165,7 +166,7 @@ module.exports = function (SocketPosts) { return memo; }, payload); - posts.updateFlagData(socket.uid, data.pid, payload, next); + flags.update(socket.uid, data.pid, payload, next); } ], callback); }; diff --git a/src/upgrade.js b/src/upgrade.js index 79ffa6b5ee..2a605e84f9 100644 --- a/src/upgrade.js +++ b/src/upgrade.js @@ -462,8 +462,9 @@ Upgrade.upgrade = function (callback) { updatesMade = true; winston.info('[2016/04/29] Dismiss flags from deleted topics'); - var posts = require('./posts'), - topics = require('./topics'); + var posts = require('./posts'); + var topics = require('./topics'); + var flags = require('./flags'); var pids, tids; @@ -486,7 +487,7 @@ Upgrade.upgrade = function (callback) { }).filter(Boolean); winston.info('[2016/04/29] ' + toDismiss.length + ' dismissable flags found'); - async.each(toDismiss, posts.dismissFlag, next); + async.each(toDismiss, flags.dismiss, next); } ], function (err) { if (err) { diff --git a/src/user/admin.js b/src/user/admin.js index 8b5a6ebef4..5d2215980c 100644 --- a/src/user/admin.js +++ b/src/user/admin.js @@ -6,6 +6,7 @@ var db = require('../database'); var posts = require('../posts'); var plugins = require('../plugins'); var winston = require('winston'); +var flags = require('../flags'); module.exports = function (User) { @@ -62,7 +63,7 @@ module.exports = function (User) { } async.eachSeries(uids, function (uid, next) { - posts.dismissUserFlags(uid, next); + flags.dismissByUid(uid, next); }, callback); }; };