diff --git a/controllers/_template.js b/controllers/_template.js index f4dbd90b5..51ac5beee 100644 --- a/controllers/_template.js +++ b/controllers/_template.js @@ -6,13 +6,19 @@ var isDev = require('../libs/debug').isDev; var isDbg = require('../libs/debug').isDbg; // + +//--- Dependency inclusions var async = require('async'); var _ = require('underscore'); -var pageMetadata = require('../libs/templateHelpers').pageMetadata; -//--- Models +//--- Model inclusions var Group = require('../models/group').Group; +//--- Library inclusions +var pageMetadata = require('../libs/templateHelpers').pageMetadata; + +//--- Configuration inclusions + //--- Local // Parse a mongoose model and add generated fields (eg: urls, formatted dates) diff --git a/controllers/flag.js b/controllers/flag.js new file mode 100644 index 000000000..1b62fcab6 --- /dev/null +++ b/controllers/flag.js @@ -0,0 +1,69 @@ +'use strict'; + +// Define some pseudo module globals +var isPro = require('../libs/debug').isPro; +var isDev = require('../libs/debug').isDev; +var isDbg = require('../libs/debug').isDbg; + +// + +//--- Dependency inclusions +var async = require('async'); + +//--- Model inclusions +var Flag = require('../models/flag').Flag; +var User = require('../models/user').User; + +//--- Library inclusions +var flagLib = require('../libs/flag'); + +//--- Configuration inclusions + +//--- +exports.getFlaggedListForContent = function (aModelName, aOptions, aCallback) { + + var content = aModelName.toLowerCase(); + var contentList = aOptions[content + 'List'] || [aOptions[content]]; + + async.forEachOf(contentList, function (aContent, aContentKey, aEachOuterCallback) { + + // NOTE: Directly use indexed parent identifier allowing set of the dynamic, virtual, field + // So basically do not use `aContent` anywhere in this function + + // Always ensure a snapshot copy! + if (contentList[aContentKey].toObject) { + contentList[aContentKey] = contentList[aContentKey].toObject({ + virtuals: true + }); + } + + // Ensure reset + contentList[aContentKey].flaggedList = []; + + // Find any flags + Flag.find({ + model: aModelName, + _contentId: contentList[aContentKey]._id + + }, function (aErr, aFlagList) { + if (aErr || !aFlagList || aFlagList.length === 0) { + aEachOuterCallback(); + return; + } + + aOptions.hasFlagged = true; + + async.forEachOfSeries(aFlagList, function (aFlag, aFlagKey, aEachInnerCallback) { + User.findOne({ _id: aFlag._userId }, function (aErr, aUser) { + contentList[aContentKey].flaggedList.push({ + name: aUser.name, + reason: aFlagList[aFlagKey].reason, + since: aFlagList[aFlagKey]._since + }); + aEachInnerCallback(); + }); + }, aEachOuterCallback); + }); + + }, aCallback); +} diff --git a/controllers/index.js b/controllers/index.js index 0c3f5325b..75253f081 100644 --- a/controllers/index.js +++ b/controllers/index.js @@ -18,6 +18,7 @@ var Strategy = require('../models/strategy').Strategy; var strategies = require('./strategies.json'); var discussionLib = require('./discussion'); +var getFlaggedListForContent = require('./flag').getFlaggedListForContent; var modelParser = require('../libs/modelParser'); var modelQuery = require('../libs/modelQuery'); var execQueryTask = require('../libs/tasks').execQueryTask; @@ -147,8 +148,25 @@ exports.home = function (aReq, aRes) { } } } - function render() { aRes.render('pages/scriptListPage', options); } - function asyncComplete() { preRender(); render(); } + function render() { + aRes.render('pages/scriptListPage', options); + } + function asyncComplete() { + + async.parallel([ + function (aCallback) { + if (!options.isFlagged || !options.isAdmin) { // NOTE: Watchpoint + aCallback(); + return; + } + getFlaggedListForContent('Script', options, aCallback); + } + ], function (aErr) { + preRender(); + render(); + }); + + } async.parallel(tasks, asyncComplete); }; diff --git a/controllers/script.js b/controllers/script.js index f6ae00f78..8df1f7756 100644 --- a/controllers/script.js +++ b/controllers/script.js @@ -16,8 +16,10 @@ var Group = require('../models/group').Group; var Script = require('../models/script').Script; var Vote = require('../models/vote').Vote; + var scriptStorage = require('./scriptStorage'); var addScriptToGroups = require('./group').addScriptToGroups; +var getFlaggedListForContent = require('./flag').getFlaggedListForContent; var flagLib = require('../libs/flag'); var removeLib = require('../libs/remove'); var modelQuery = require('../libs/modelQuery'); @@ -319,8 +321,25 @@ exports.view = function (aReq, aRes, aNext) { script.description, _.pluck(script.groups, 'name')); } } - function render() { aRes.render('pages/scriptPage', options); } - function asyncComplete() { preRender(); render(); } + function render() { + aRes.render('pages/scriptPage', options); + } + function asyncComplete() { + + async.parallel([ + function (aCallback) { + if (!options.isAdmin) { // NOTE: Watchpoint + aCallback(); + return; + } + getFlaggedListForContent('Script', options, aCallback); + } + ], function (aErr) { + preRender(); + render(); + }); + + } //--- if (aErr || !aScriptData) { diff --git a/controllers/user.js b/controllers/user.js index 9bd964790..673fa3205 100644 --- a/controllers/user.js +++ b/controllers/user.js @@ -18,8 +18,11 @@ var Strategy = require('../models/strategy').Strategy; var User = require('../models/user').User; var Discussion = require('../models/discussion').Discussion; + // TODO: Possible unneccessary directory traversal var categories = require('../controllers/discussion').categories; +var getFlaggedListForContent = require('./flag').getFlaggedListForContent; + var userRoles = require('../models/userRoles.json'); var scriptStorage = require('./scriptStorage'); var modelParser = require('../libs/modelParser'); @@ -234,8 +237,29 @@ exports.userListPage = function (aReq, aRes, aNext) { pageMetadata(options, ['Flagged Users', 'Moderation']); } } - function render() { aRes.render('pages/userListPage', options); } - function asyncComplete(err) { if (err) { return aNext(); } else { preRender(); render(); } } + function render() { + aRes.render('pages/userListPage', options); + } + function asyncComplete(aErr) { + if (aErr) { + aNext(); + return; + } + + async.parallel([ + function (aCallback) { + if (!options.isFlagged || !options.isAdmin) { // NOTE: Watchpoint + aCallback(); + return; + } + getFlaggedListForContent('User', options, aCallback); + } + ], function (aErr) { + preRender(); + render(); + }); + + } async.parallel(tasks, asyncComplete); }; @@ -292,9 +316,27 @@ exports.view = function (aReq, aRes, aNext) { tasks = tasks.concat(stats.getSummaryTasks(options)); //--- - function preRender() { } - function render() { aRes.render('pages/userPage', options); } - function asyncComplete() { preRender(); render(); } + function preRender() { + } + function render() { + aRes.render('pages/userPage', options); + } + function asyncComplete() { + + async.parallel([ + function (aCallback) { + if (!options.isAdmin) { // NOTE: Watchpoint + aCallback(); + return; + } + getFlaggedListForContent('User', options, aCallback); + } + ], function (aErr) { + preRender(); + render(); + }); + + } async.parallel(tasks, asyncComplete); }); }; @@ -410,13 +452,14 @@ exports.userCommentListPage = function (aReq, aRes, aNext) { exports.userScriptListPage = function (aReq, aRes, aNext) { var authedUser = aReq.session.user; - var username = aReq.params.username; User.findOne({ name: caseInsensitive(username) }, function (aErr, aUserData) { - if (aErr || !aUserData) { return aNext(); } + if (aErr || !aUserData) { + return aNext(); + } // var options = {}; @@ -497,8 +540,25 @@ exports.userScriptListPage = function (aReq, aRes, aNext) { options.scriptListIsEmptyMessage = 'This user hasn\'t added any scripts yet.'; } } - function render() { aRes.render('pages/userScriptListPage', options); } - function asyncComplete() { preRender(); render(); } + function render() { + aRes.render('pages/userScriptListPage', options); + } + function asyncComplete() { + + async.parallel([ + function (aCallback) { + if (!options.isFlagged || !options.isAdmin) { // NOTE: Watchpoint + aCallback(); + return; + } + getFlaggedListForContent('Script', options, aCallback); + } + ], function (aErr) { + preRender(); + render(); + }); + + } async.parallel(tasks, asyncComplete); }); }; diff --git a/controllers/vote.js b/controllers/vote.js new file mode 100644 index 000000000..3088e1cf3 --- /dev/null +++ b/controllers/vote.js @@ -0,0 +1,19 @@ +'use strict'; + +// Define some pseudo module globals +var isPro = require('../libs/debug').isPro; +var isDev = require('../libs/debug').isDev; +var isDbg = require('../libs/debug').isDbg; + +// + +//--- Dependency inclusions + +//--- Model inclusions + +//--- Library inclusions +var voteLib = require('../libs/vote'); + +//--- Configuration inclusions + +//--- diff --git a/libs/modelParser.js b/libs/modelParser.js index 94c4d9a9b..104577b8f 100644 --- a/libs/modelParser.js +++ b/libs/modelParser.js @@ -63,10 +63,12 @@ moment.locale('en-tiny', { }); var parseDateProperty = function (aObj, aKey) { - var date = aObj[aKey]; - if (date) { - aObj[aKey + 'ISOFormat'] = date.toISOString(); - aObj[aKey + 'Humanized'] = moment(date).locale('en-tiny').calendar(); + if (aObj[aKey]) { + var date = new Date(aObj[aKey]); + if (date) { + aObj[aKey + 'ISOFormat'] = date.toISOString(); + aObj[aKey + 'Humanized'] = moment(date).locale('en-tiny').calendar(); + } } }; @@ -243,10 +245,9 @@ var parseUser = function (aUserData) { if (!aUserData) { return; } - // var user = aUserData.toObject ? aUserData.toObject() : aUserData; // Intermediates - var user = aUserData; + var user = aUserData.toObject ? aUserData.toObject({ virtuals: true }) : aUserData; // Role user.isMod = user.role < 4; diff --git a/libs/vote.js b/libs/vote.js new file mode 100644 index 000000000..6108badbb --- /dev/null +++ b/libs/vote.js @@ -0,0 +1,8 @@ +'use strict'; + +// Define some pseudo module globals +var isPro = require('../libs/debug').isPro; +var isDev = require('../libs/debug').isDev; +var isDbg = require('../libs/debug').isDbg; + +// diff --git a/models/flag.js b/models/flag.js index 2fa9747f3..0fa95a217 100644 --- a/models/flag.js +++ b/models/flag.js @@ -11,10 +11,15 @@ var Schema = mongoose.Schema; var flagSchema = new Schema({ model: String, + reason: String, _contentId: Schema.Types.ObjectId, _userId: Schema.Types.ObjectId }); +flagSchema.virtual('_since').get(function () { + return this._id.getTimestamp(); +}); + var Flag = mongoose.model('Flag', flagSchema); exports.Flag = Flag; diff --git a/public/css/common.css b/public/css/common.css index 78fb77203..dcd6273b8 100644 --- a/public/css/common.css +++ b/public/css/common.css @@ -302,3 +302,8 @@ a.panel-heading { .reason-automated { opacity: 0.25; } + +ul.flaggedList { + list-style-type: none; + padding-left: 0; +} diff --git a/views/includes/scriptAdminToolsPanel.html b/views/includes/scriptAdminToolsPanel.html index 9e79980eb..de5891350 100644 --- a/views/includes/scriptAdminToolsPanel.html +++ b/views/includes/scriptAdminToolsPanel.html @@ -7,6 +7,23 @@
+ {{#hasFlagged}} +
+
+ +
+
    + {{#script.flaggedList}} +
  • + {{#name}}{{name}}{{/name}} + {{#reason}}{{reason}}{{/reason}}{{^reason}}…{{/reason}} +
  • + {{/script.flaggedList}} +
+
+
+
+ {{/hasFlagged}} Raw JSON Data
diff --git a/views/includes/scriptList.html b/views/includes/scriptList.html index f5612212c..eb2498f27 100644 --- a/views/includes/scriptList.html +++ b/views/includes/scriptList.html @@ -5,6 +5,9 @@ {{^librariesOnly}}Installs{{/librariesOnly}} Rating Last Updated + {{#hasFlagged}} + Flagged + {{/hasFlagged}} @@ -36,6 +39,18 @@ + {{#hasFlagged}} + + + + {{/hasFlagged}} {{/scriptList}} {{^scriptList}} diff --git a/views/includes/userAdminToolsPanel.html b/views/includes/userAdminToolsPanel.html index 9bdce7845..0a4084f9a 100644 --- a/views/includes/userAdminToolsPanel.html +++ b/views/includes/userAdminToolsPanel.html @@ -7,6 +7,23 @@
+ {{#hasFlagged}} +
+
+ +
+
    + {{#user.flaggedList}} +
  • + {{#name}}{{name}}{{/name}} + {{#reason}}{{reason}}{{/reason}}{{^reason}}…{{/reason}} +
  • + {{/user.flaggedList}} +
+
+
+
+ {{/hasFlagged}}
diff --git a/views/includes/userList.html b/views/includes/userList.html index e2c35e231..d081a92e4 100644 --- a/views/includes/userList.html +++ b/views/includes/userList.html @@ -2,7 +2,10 @@ Name - Rank + Rank + {{#hasFlagged}} + Flagged + {{/hasFlagged}} @@ -13,16 +16,28 @@ {{name}} - +

{{roleName}}

+ {{#hasFlagged}} + +
    + {{#flaggedList}} +
  • + {{#name}}{{name}}{{/name}} + {{#reason}}{{reason}}{{/reason}}{{^reason}}…{{/reason}} +
  • + {{/flaggedList}} +
+ + {{/hasFlagged}} {{/userList}} {{^userList}} - + {{userListIsEmptyMessage}}