Revert "Revert "Status history""

This commit is contained in:
Wyatt Johnson
2016-12-08 16:12:44 -05:00
committed by GitHub
parent a81125ae09
commit aaebbabef9
23 changed files with 846 additions and 435 deletions
+29
View File
@@ -1,5 +1,6 @@
const mongoose = require('../mongoose');
const uuid = require('uuid');
const _ = require('lodash');
const Schema = mongoose.Schema;
const ActionSchema = new Schema({
@@ -66,6 +67,34 @@ ActionSchema.statics.findByItemIdArray = function(item_ids) {
});
};
/**
* Fetches the action summaries for the given asset, and comments around the
* given user id.
* @param {[type]} asset_id [description]
* @param {[type]} comments [description]
* @param {String} [current_user_id=''] [description]
* @return {[type]} [description]
*/
ActionSchema.statics.getActionSummariesFromComments = (asset_id = '', comments, current_user_id = '') => {
// Get the user id's from the author id's as a unique array that gets
// sorted.
let userIDs = _.uniq(comments.map((comment) => comment.author_id)).sort();
// Fetch the actions for pretty much everything at this point.
return Action.getActionSummaries(_.uniq([
// Actions can be on assets...
asset_id,
// Comments...
...comments.map((comment) => comment.id),
// Or Authors...
...userIDs
].filter((e) => e)), current_user_id);
};
/**
* Returns summaries of actions for an array of ids
* @param {String} ids array of user identifiers (uuid)
+21 -5
View File
@@ -1,6 +1,8 @@
const mongoose = require('../mongoose');
const Schema = mongoose.Schema;
const Setting = require('./setting');
const uuid = require('uuid');
const AssetSchema = new Schema({
@@ -58,11 +60,6 @@ AssetSchema.index({
background: true
});
/**
* Search for assets. Currently only returns all.
*/
AssetSchema.statics.search = (query) => Asset.find(query);
/**
* Finds an asset by its id.
* @param {String} id identifier of the asset (uuid).
@@ -75,6 +72,25 @@ AssetSchema.statics.findById = (id) => Asset.findOne({id});
*/
AssetSchema.statics.findByUrl = (url) => Asset.findOne({url});
/**
* Retrieves the settings given an asset query and rectifies it against the
* global settings.
* @param {Promise} assetQuery an asset query that returns a single asset.
* @return {Promise}
*/
AssetSchema.statics.rectifySettings = (assetQuery) => Promise.all([
Setting.retrieve(),
assetQuery
]).then(([settings, asset]) => {
// If the asset exists and has settings then return the merged object.
if (asset && asset.settings) {
return Object.assign({}, settings, asset.settings);
}
return settings;
});
/**
* Finds a asset by its url.
*
+204 -92
View File
@@ -1,9 +1,37 @@
const mongoose = require('../mongoose');
const Schema = mongoose.Schema;
const uuid = require('uuid');
const Action = require('./action');
const Schema = mongoose.Schema;
/**
* The Mongo schema for a Comment Status.
* @type {Schema}
*/
const StatusSchema = new Schema({
type: {
type: String,
enum: [
'accepted',
'rejected',
'premod',
],
},
// The User ID of the user that assigned the status.
assigned_by: {
type: String,
default: null
},
created_at: Date
}, {
_id: false
});
/**
* The Mongo schema for a Comment.
* @type {Schema}
*/
const CommentSchema = new Schema({
id: {
type: String,
@@ -17,11 +45,7 @@ const CommentSchema = new Schema({
},
asset_id: String,
author_id: String,
status: {
type: String,
enum: ['accepted', 'rejected', ''],
default: ''
},
status: [StatusSchema],
parent_id: String
}, {
timestamps: {
@@ -30,90 +54,168 @@ const CommentSchema = new Schema({
}
});
//==============================================================================
// Find Statics
//==============================================================================
/**
* toJSON overrides to remove fields from the json
* output.
*/
CommentSchema.options.toJSON = {};
CommentSchema.options.toJSON.hide = '_id status';
CommentSchema.options.toJSON.transform = (doc, ret, options) => {
if (options.hide) {
options.hide.split(' ').forEach((prop) => {
delete ret[prop];
});
}
return ret;
};
/**
* toJSON overrides to remove fields from the json
* output.
*/
CommentSchema.options.toJSON = {};
CommentSchema.options.toJSON.hide = '_id';
CommentSchema.options.toJSON.transform = (doc, ret, options) => {
if (options.hide) {
options.hide.split(' ').forEach((prop) => {
delete ret[prop];
});
}
return ret;
};
/**
* Sets up a virtual getter function on a comment such that when you try and
* access the `comment.last_status` it returns the last status in the array
* of status's on the comment, or `null` if there was no status.
*/
CommentSchema.virtual('last_status').get(function() {
if (this.status && this.status.length > 0) {
return this.status[this.status.length - 1].type;
}
return null;
});
/**
* Creates a new Comment that came from a public source.
* @param {Mixed} comment either a single comment or an array of comments.
* @return {Promise}
*/
CommentSchema.statics.publicCreate = (comment) => {
// Check to see if this is an array of comments, if so map it out.
if (Array.isArray(comment)) {
return Promise.all(comment.map(Comment.publicCreate));
}
const {
body,
asset_id,
parent_id,
status = false,
author_id
} = comment;
comment = new Comment({
body,
asset_id,
parent_id,
status: status ? [{
type: status,
created_at: new Date()
}] : [],
author_id
});
return comment.save();
};
/**
* Finds a comment by the id.
* @param {String} id identifier of comment (uuid)
* @return {Promise}
*/
CommentSchema.statics.findById = function(id) {
return Comment.findOne({'id': id});
};
CommentSchema.statics.findById = (id) => Comment.findOne({id});
/**
* Finds ALL the comments by the asset_id.
* @param {String} asset_id identifier of the asset which owns this comment (uuid)
* @return {Promise}
*/
CommentSchema.statics.findByAssetId = function(asset_id) {
return Comment.find({asset_id});
};
CommentSchema.statics.findByAssetId = (asset_id) => Comment.find({
asset_id
});
/**
* Finds the accepted comments by the asset_id.
* get the comments that are accepted.
* Finds the accepted comments by the asset_id get the comments that are
* accepted.
* @param {String} asset_id identifier of the asset which owns the comments (uuid)
* @return {Promise}
*/
CommentSchema.statics.findAcceptedByAssetId = function(asset_id) {
return Comment.find({asset_id: asset_id, status:'accepted'});
};
CommentSchema.statics.findAcceptedByAssetId = (asset_id) => Comment.find({
asset_id,
'status.type': 'accepted'
});
/**
* Finds the new and accepted comments by the asset_id.
* @param {String} asset_id identifier of the asset which owns the comments (uuid)
* @return {Promise}
*/
CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) {
return Comment.find({asset_id: asset_id, status: {'$in': ['accepted', '']}});
};
CommentSchema.statics.findAcceptedAndNewByAssetId = (asset_id) => Comment.find({
asset_id,
$or: [
{
'status.type': 'accepted'
},
{
status: {
$size: 0
}
}
]
});
/**
* Find comments by an action that was performed on them.
* @param {String} action_type the type of action that was performed on the comment
* @return {Promise}
*/
CommentSchema.statics.findByActionType = function(action_type) {
return Action
.findCommentsIdByActionType(action_type, 'comment')
.then((actions) => {
return Comment.find({'id': {'$in': actions.map(function(a){
return a.item_id;})}
});
});
};
CommentSchema.statics.findByActionType = (action_type) => Action
.findCommentsIdByActionType(action_type, 'comment')
.then((actions) => Comment.find({
id: {
$in: actions.map((a) => a.item_id)
}
}));
/**
* Find not moderated comments by an action that was performed on them.
* Find comment id's where the action type matches the argument.
* @param {String} action_type the type of action that was performed on the comment
* @param {String} status the status of the comment to search for
* @return {Promise}
*/
CommentSchema.statics.findByStatusByActionType = function(status, action_type) {
return Action
.findCommentsIdByActionType(action_type, 'comment')
.then((actions) => {
return Comment.find({
status: status,
id: {
$in: actions.map(a => a.item_id)
}
});
});
};
CommentSchema.statics.findIdsByActionType = (action_type) => Action
.findCommentsIdByActionType(action_type, 'comment')
.then((actions) => actions.map(a => a.item_id));
/**
* Find comments by their status.
* @param {String} status the status of the comment to search for
* @return {Promise}
*/
CommentSchema.statics.findByStatus = function(status) {
return Comment.find({
status: status === 'new' ? '' : status
});
CommentSchema.statics.findByStatus = (status = false) => {
let q = {};
if (status) {
q['status.type'] = status;
} else {
q.status = {$size: 0};
}
return Comment.find(q);
};
/**
@@ -121,39 +223,59 @@ CommentSchema.statics.findByStatus = function(status) {
* @param {String} moderationValue pre or post moderation setting. If it is undefined then look at the settings.
* @return {Promise}
*/
CommentSchema.statics.moderationQueue = function(moderation) {
switch(moderation){
CommentSchema.statics.moderationQueue = (moderation, asset_id = false) => {
// Pre-moderation: New comments are shown in the moderator queues immediately.
case 'pre':
return Comment.findByStatus('').then((comments) => {
return comments;
});
/**
* This adds the asset_id requirement to the query if the asset_id is defined.
*/
const assetIDWrap = (query) => {
if (asset_id) {
query = query.where('asset_id', asset_id);
}
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
case 'post':
return Comment.findByStatusByActionType('', 'flag').then((comments) => {
return comments;
});
return query;
};
default:
return Promise.reject(Error('Moderation setting not found.'));
// Decide on whether or not we need to load extended options for the
// moderation based on the moderation options.
let comments;
if (moderation === 'pre') {
// Pre-moderation: New comments are shown in the moderator queues immediately.
comments = assetIDWrap(CommentSchema.statics.findByStatus('premod'));
} else {
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
comments = CommentSchema.statics.findIdsByActionType('flag')
.then((ids) => assetIDWrap(Comment.find({
id: {
$in: ids
}
})));
}
};
//==============================================================================
// Update Statics
//==============================================================================
return comments;
};
/**
* Change the status of a comment.
* @param {String} id identifier of the comment (uuid)
* @param {String} status the new status of the comment
* Pushes a new status in for the user.
* @param {String} id identifier of the comment (uuid)
* @param {String} status the new status of the comment
* @param {String} assigned_by the user id for the user who performed the
* moderation action
* @return {Promise}
*/
CommentSchema.statics.changeStatus = function(id, status) {
return Comment.findOneAndUpdate({'id': id}, {$set: {'status': status}});
};
CommentSchema.statics.pushStatus = (id, status, assigned_by = null) => Comment.update({id}, {
$push: {
status: {
type: status,
created_at: new Date(),
assigned_by
}
}
});
/**
* Add an action to the comment.
@@ -169,19 +291,13 @@ CommentSchema.statics.addAction = (item_id, user_id, action_type) => Action.inse
action_type
});
//==============================================================================
// Remove Statics
//==============================================================================
/**
* Change the status of a comment.
* @param {String} id identifier of the comment (uuid)
* @param {String} status the new status of the comment
* @return {Promise}
*/
CommentSchema.statics.removeById = function(id) {
return Comment.remove({'id': id});
};
CommentSchema.statics.removeById = (id) => Comment.remove({id});
/**
* Remove an action from the comment.
@@ -190,22 +306,18 @@ CommentSchema.statics.removeById = function(id) {
* @param {String} user_id the id of the user performing the action
* @return {Promise}
*/
CommentSchema.statics.removeAction = function(item_id, user_id, action_type) {
return Action.remove({
action_type,
item_type: 'comment',
item_id,
user_id
});
};
CommentSchema.statics.removeAction = (item_id, user_id, action_type) => Action.remove({
action_type,
item_type: 'comment',
item_id,
user_id
});
/**
* Returns all the comments in the collection.
* @return {Promise}
*/
CommentSchema.statics.all = () => {
return Comment.find();
};
CommentSchema.statics.all = () => Comment.find();
// Comment model.
const Comment = mongoose.model('Comment', CommentSchema);
+77 -40
View File
@@ -1,18 +1,33 @@
const mongoose = require('../mongoose');
const Schema = mongoose.Schema;
const _ = require('lodash');
const cache = require('../cache');
/**
* this Schema manages application settings that get used on front and backend
* NOTE: when you set a setting here, it will not automatically be exposed to
* the front end. You must add it to the whitelist in the settings route
* in /routes/api/settings/index.js
* SettingSchema manages application settings that get used on front and backend.
* @type {Schema}
*/
const SettingSchema = new Schema({
id: {type: String, default: '1'},
moderation: {type: String, enum: ['pre', 'post'], default: 'pre'},
infoBoxEnable: {type: Boolean, default: false},
infoBoxContent: {type: String, default: ''},
id: {
type: String,
default: '1'
},
moderation: {
type: String,
enum: [
'pre',
'post'
],
default: 'pre'
},
infoBoxEnable: {
type: Boolean,
default: false
},
infoBoxContent: {
type: String,
default: ''
},
wordlist: [String]
}, {
timestamps: {
@@ -22,48 +37,70 @@ const SettingSchema = new Schema({
});
/**
* this is run once when the app starts to ensure settings are populated
* @return {Promise} null initialize the global settings object
* The Mongo Mongoose object.
*/
SettingSchema.statics.init = function (defaults) {
return this.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
};
const Setting = mongoose.model('Setting', SettingSchema);
/**
* The Setting Service object exposing the Setting model.
* @type {Object}
*/
const SettingService = module.exports = {};
/**
* The selector used to uniquely identify the settings document.
*/
const selector = {id: '1'};
/**
* Cache expiry time in seconds for when the cached entry of the settings object
* expires. 2 minutes.
*/
const EXPIRY_TIME = 60 * 2;
/**
* Gets the entire settings record and sends it back
* @return {Promise} settings the whole settings record
*/
SettingSchema.statics.getSettings = function () {
return this.findOne({id: '1'});
};
/**
* Gets the settings visible to the public
* @return {Promise} moderation the settings for how to moderate comments
*/
SettingSchema.statics.getPublicSettings = function () {
return this.findOne({id: '1'}).select('moderation infoBoxEnable infoBoxContent');
};
/**
* Gets the info box settings and sends it back
* @return {Promise} content the content of the info Box
*/
SettingSchema.statics.getInfoBoxSetting = function () {
return this.findOne({id: '1'}).select('infoBoxEnable infoBoxContent');
};
SettingService.retrieve = () => cache.wrap('settings', EXPIRY_TIME, () => Setting.findOne(selector));
/**
* This will update the settings object with whatever you pass in
* @param {object} setting a hash of whatever settings you want to update
* @return {Promise} settings Promise that resolves to the entire (updated) settings object.
*/
SettingSchema.statics.updateSettings = function (setting) {
// There should only ever be one record unless something has gone wrong.
// In the future we may have multiple records for custom settings for objects/users.
return this.findOneAndUpdate({id: '1'}, {$set: setting}, {new: true});
SettingService.update = (settings) => Setting.findOneAndUpdate(selector, {
$set: settings
}, {
upsert: true,
new: true,
setDefaultsOnInsert: true
}).then((settings) => {
// Invalidate the settings cache.
return cache
.set('settings', settings, EXPIRY_TIME)
.then(() => settings);
});
/**
* Filters the document to ensure that the resulting document is indeed ready
* for non authenticated users.
* @param {Object} settings the source settings object
* @return {Object} the filtered settings object
*/
SettingService.public = (settings) => _.pick(settings, ['moderation', 'infoBoxEnable', 'infoBoxContent']);
/**
* This is run once when the app starts to ensure settings are populated.
* @return {Promise} null initialize the global settings object
*/
SettingService.init = (defaults) => {
// Inject the defaults on top of the passed in defaults to ensure that the new
// settings conform to the required selector.
defaults = Object.assign({}, defaults, selector);
// Actually update the settings collection.
return SettingService.update(defaults);
};
const Setting = mongoose.model('Setting', SettingSchema);
module.exports = Setting;
+1 -2
View File
@@ -9,7 +9,6 @@ const SALT_ROUNDS = 10;
// USER_ROLES is the array of roles that is permissible as a user role.
const USER_ROLES = [
'',
'admin',
'moderator'
];
@@ -106,7 +105,7 @@ UserSchema.index({
* output.
*/
UserSchema.options.toJSON = {};
UserSchema.options.toJSON.hide = 'password profiles roles disabled';
UserSchema.options.toJSON.hide = '_id password profiles roles disabled';
UserSchema.options.toJSON.transform = (doc, ret, options) => {
if (options.hide) {
options.hide.split(' ').forEach((prop) => {