| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | '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: { | 
					
						
							| 
									
										
										
										
											2016-11-25 12:43:10 -05:00
										 |  |  | 		state: 'open', | 
					
						
							|  |  |  | 		assignee: null | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 	} | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-25 12:43:10 -05:00
										 |  |  | Flags.list = function (filters, callback) { | 
					
						
							|  |  |  | 	if (typeof filters === 'function' && !callback) { | 
					
						
							|  |  |  | 		callback = filters; | 
					
						
							|  |  |  | 		filters = {}; | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	async.waterfall([ | 
					
						
							|  |  |  | 		async.apply(db.getSortedSetRevRange.bind(db), 'flags:datetime', 0, 19), | 
					
						
							|  |  |  | 		function (flagIds, next) { | 
					
						
							|  |  |  | 			async.map(flagIds, function (flagId, next) { | 
					
						
							|  |  |  | 				async.waterfall([ | 
					
						
							|  |  |  | 					async.apply(db.getObject, 'flag:' + flagId), | 
					
						
							|  |  |  | 					function (flagObj, next) { | 
					
						
							|  |  |  | 						user.getUserFields(flagObj.uid, ['username', 'picture'], function (err, userObj) { | 
					
						
							|  |  |  | 							next(err, Object.assign(flagObj, { | 
					
						
							|  |  |  | 								user: { | 
					
						
							|  |  |  | 									username: userObj.username, | 
					
						
							|  |  |  | 									picture: userObj.picture, | 
					
						
							|  |  |  | 									'icon:bgColor': userObj['icon:bgColor'], | 
					
						
							|  |  |  | 									'icon:text': userObj['icon:text'] | 
					
						
							|  |  |  | 								} | 
					
						
							|  |  |  | 							})); | 
					
						
							|  |  |  | 						}) | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 				], function (err, flagObj) { | 
					
						
							|  |  |  | 					if (err) { | 
					
						
							|  |  |  | 						return next(err); | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					switch(flagObj.state) { | 
					
						
							|  |  |  | 						case 'open': | 
					
						
							|  |  |  | 							flagObj.labelClass = 'info'; | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						case 'wip': | 
					
						
							|  |  |  | 							flagObj.labelClass = 'warning'; | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						case 'resolved': | 
					
						
							|  |  |  | 							flagObj.labelClass = 'success'; | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 						case 'rejected': | 
					
						
							|  |  |  | 							flagObj.labelClass = 'danger'; | 
					
						
							|  |  |  | 							break; | 
					
						
							|  |  |  | 					} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 					next(null, Object.assign(flagObj, { | 
					
						
							|  |  |  | 						target_readable: flagObj.type.charAt(0).toUpperCase() + flagObj.type.slice(1) + ' ' + flagObj.id, | 
					
						
							|  |  |  | 						datetimeISO: new Date(parseInt(flagObj.datetime, 10)).toISOString() | 
					
						
							|  |  |  | 					})); | 
					
						
							|  |  |  | 				}); | 
					
						
							|  |  |  | 			},  next); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	], function (err, flags) { | 
					
						
							|  |  |  | 		if (err) { | 
					
						
							|  |  |  | 			return callback(err); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		return callback(null, flags); | 
					
						
							|  |  |  | 	}); | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 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([ | 
					
						
							| 
									
										
										
										
											2016-11-25 12:43:10 -05:00
										 |  |  | 				async.apply(db.setObject.bind(db), 'flag:' + flagId, Object.assign({}, Flags._defaults, { | 
					
						
							|  |  |  | 					type: type, | 
					
						
							|  |  |  | 					id: id, | 
					
						
							|  |  |  | 					description: reason, | 
					
						
							|  |  |  | 					uid: uid, | 
					
						
							|  |  |  | 					datetime: Date.now() | 
					
						
							|  |  |  | 				})), | 
					
						
							|  |  |  | 				async.apply(db.sortedSetAdd.bind(db), 'flags:datetime', Date.now(), flagId) | 
					
						
							|  |  |  | 			], next); | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	], function (err) { | 
					
						
							|  |  |  | 		if (err) { | 
					
						
							|  |  |  | 			return callback(err); | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-25 12:43:10 -05:00
										 |  |  | 		callback(); | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 	}); | 
					
						
							|  |  |  | 	// 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) {
 | 
					
						
							| 
									
										
										
										
											2016-11-25 12:43:10 -05:00
										 |  |  | 	// 		openNewFlag(post.pid, uid, next);		// removed, used to just update flag to open state if new flag
 | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	// ], function (err) {
 | 
					
						
							|  |  |  | 	// 	if (err) {
 | 
					
						
							|  |  |  | 	// 		return callback(err);
 | 
					
						
							|  |  |  | 	// 	}
 | 
					
						
							|  |  |  | 	// 	analytics.increment('flags');
 | 
					
						
							|  |  |  | 	// 	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) { | 
					
						
							| 
									
										
										
										
											2016-11-24 11:56:57 -05:00
										 |  |  | 				posts.filterPidsByCid(pids, cid, next); | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 			} 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) { | 
					
						
							| 
									
										
										
										
											2016-11-24 11:56:57 -05:00
										 |  |  | 					posts.getPostSummaryByPids(pids, uid, {stripTags: false, extraFields: ['flags', 'flag:assignee', 'flag:state', 'flag:notes', 'flag:history']}, next); | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 				} | 
					
						
							|  |  |  | 			}, 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); | 
					
						
							|  |  |  | 			}); | 
					
						
							|  |  |  | 		}, | 
					
						
							| 
									
										
										
										
											2016-11-24 11:56:57 -05:00
										 |  |  | 		async.apply(Flags.expandFlagHistory), | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 		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; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2016-11-24 11:56:57 -05:00
										 |  |  | 	posts.getPostData(pid, function (err, postData) { | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 		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) { | 
					
						
							| 
									
										
										
										
											2016-11-24 11:56:57 -05:00
										 |  |  | 			posts.setPostFields(pid, changeset, callback); | 
					
						
							| 
									
										
										
										
											2016-11-23 19:41:35 -05:00
										 |  |  | 		} 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; |