Revert "Status history"

This commit is contained in:
David Erwin
2016-12-08 10:59:56 -10:00
committed by GitHub
parent e7fd77a45e
commit 9ea6c66744
23 changed files with 435 additions and 846 deletions
-4
View File
@@ -96,10 +96,6 @@ function startApp() {
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
})
.catch((err) => {
console.error(err);
util.shutdown(1);
});
}
-15
View File
@@ -76,21 +76,6 @@ cache.get = (key) => new Promise((resolve, reject) => {
});
});
/**
* This invalidates a cached entry in the cache.
* @param {Mixed} key Either an array of items composing a key or a string
* @return {Promise}
*/
cache.invalidate = (key) => new Promise((resolve, reject) => {
cache.client.del(keyfunc(key), (err) => {
if (err) {
return reject(err);
}
resolve();
});
});
/**
* This sets a value on the key with the expiry and then resolves once it is
* done.
-29
View File
@@ -1,6 +1,5 @@
const mongoose = require('../mongoose');
const uuid = require('uuid');
const _ = require('lodash');
const Schema = mongoose.Schema;
const ActionSchema = new Schema({
@@ -67,34 +66,6 @@ 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)
+5 -21
View File
@@ -1,8 +1,6 @@
const mongoose = require('../mongoose');
const Schema = mongoose.Schema;
const Setting = require('./setting');
const uuid = require('uuid');
const AssetSchema = new Schema({
@@ -60,6 +58,11 @@ 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).
@@ -72,25 +75,6 @@ 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.
*
+92 -204
View File
@@ -1,37 +1,9 @@
const mongoose = require('../mongoose');
const Schema = mongoose.Schema;
const uuid = require('uuid');
const Action = require('./action');
/**
* The Mongo schema for a Comment Status.
* @type {Schema}
*/
const StatusSchema = new Schema({
type: {
type: String,
enum: [
'accepted',
'rejected',
'premod',
],
},
const Schema = mongoose.Schema;
// 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,
@@ -45,7 +17,11 @@ const CommentSchema = new Schema({
},
asset_id: String,
author_id: String,
status: [StatusSchema],
status: {
type: String,
enum: ['accepted', 'rejected', ''],
default: ''
},
parent_id: String
}, {
timestamps: {
@@ -54,168 +30,90 @@ const CommentSchema = new Schema({
}
});
/**
* 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();
};
//==============================================================================
// Find Statics
//==============================================================================
/**
* Finds a comment by the id.
* @param {String} id identifier of comment (uuid)
* @return {Promise}
*/
CommentSchema.statics.findById = (id) => Comment.findOne({id});
CommentSchema.statics.findById = function(id) {
return Comment.findOne({'id': 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 = (asset_id) => Comment.find({
asset_id
});
CommentSchema.statics.findByAssetId = function(asset_id) {
return 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 = (asset_id) => Comment.find({
asset_id,
'status.type': 'accepted'
});
CommentSchema.statics.findAcceptedByAssetId = function(asset_id) {
return Comment.find({asset_id: asset_id, status:'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 = (asset_id) => Comment.find({
asset_id,
$or: [
{
'status.type': 'accepted'
},
{
status: {
$size: 0
}
}
]
});
CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) {
return Comment.find({asset_id: asset_id, status: {'$in': ['accepted', '']}});
};
/**
* 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 = (action_type) => Action
.findCommentsIdByActionType(action_type, 'comment')
.then((actions) => Comment.find({
id: {
$in: actions.map((a) => a.item_id)
}
}));
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;})}
});
});
};
/**
* Find comment id's where the action type matches the argument.
* Find not moderated comments by an action that was performed on them.
* @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.findIdsByActionType = (action_type) => Action
.findCommentsIdByActionType(action_type, 'comment')
.then((actions) => actions.map(a => a.item_id));
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)
}
});
});
};
/**
* Find comments by their status.
* @param {String} status the status of the comment to search for
* @return {Promise}
*/
CommentSchema.statics.findByStatus = (status = false) => {
let q = {};
if (status) {
q['status.type'] = status;
} else {
q.status = {$size: 0};
}
return Comment.find(q);
CommentSchema.statics.findByStatus = function(status) {
return Comment.find({
status: status === 'new' ? '' : status
});
};
/**
@@ -223,59 +121,39 @@ CommentSchema.statics.findByStatus = (status = false) => {
* @param {String} moderationValue pre or post moderation setting. If it is undefined then look at the settings.
* @return {Promise}
*/
CommentSchema.statics.moderationQueue = (moderation, asset_id = false) => {
CommentSchema.statics.moderationQueue = function(moderation) {
switch(moderation){
/**
* 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);
}
// Pre-moderation: New comments are shown in the moderator queues immediately.
case 'pre':
return Comment.findByStatus('').then((comments) => {
return comments;
});
return query;
};
// 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;
});
// 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
}
})));
default:
return Promise.reject(Error('Moderation setting not found.'));
}
return comments;
};
//==============================================================================
// Update Statics
//==============================================================================
/**
* 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
* 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.pushStatus = (id, status, assigned_by = null) => Comment.update({id}, {
$push: {
status: {
type: status,
created_at: new Date(),
assigned_by
}
}
});
CommentSchema.statics.changeStatus = function(id, status) {
return Comment.findOneAndUpdate({'id': id}, {$set: {'status': status}});
};
/**
* Add an action to the comment.
@@ -291,13 +169,19 @@ 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 = (id) => Comment.remove({id});
CommentSchema.statics.removeById = function(id) {
return Comment.remove({'id': id});
};
/**
* Remove an action from the comment.
@@ -306,18 +190,22 @@ CommentSchema.statics.removeById = (id) => Comment.remove({id});
* @param {String} user_id the id of the user performing the action
* @return {Promise}
*/
CommentSchema.statics.removeAction = (item_id, user_id, action_type) => Action.remove({
action_type,
item_type: 'comment',
item_id,
user_id
});
CommentSchema.statics.removeAction = function(item_id, user_id, action_type) {
return Action.remove({
action_type,
item_type: 'comment',
item_id,
user_id
});
};
/**
* Returns all the comments in the collection.
* @return {Promise}
*/
CommentSchema.statics.all = () => Comment.find();
CommentSchema.statics.all = () => {
return Comment.find();
};
// Comment model.
const Comment = mongoose.model('Comment', CommentSchema);
+40 -77
View File
@@ -1,33 +1,18 @@
const mongoose = require('../mongoose');
const Schema = mongoose.Schema;
const _ = require('lodash');
const cache = require('../cache');
/**
* SettingSchema manages application settings that get used on front and backend.
* 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
* @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: {
@@ -37,70 +22,48 @@ const SettingSchema = new Schema({
});
/**
* The Mongo Mongoose object.
* this is run once when the app starts to ensure settings are populated
* @return {Promise} null initialize the global settings object
*/
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;
SettingSchema.statics.init = function (defaults) {
return this.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
};
/**
* Gets the entire settings record and sends it back
* @return {Promise} settings the whole settings record
*/
SettingService.retrieve = () => cache.wrap('settings', EXPIRY_TIME, () => Setting.findOne(selector));
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');
};
/**
* 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.
*/
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);
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});
};
const Setting = mongoose.model('Setting', SettingSchema);
module.exports = Setting;
+2 -1
View File
@@ -9,6 +9,7 @@ const SALT_ROUNDS = 10;
// USER_ROLES is the array of roles that is permissible as a user role.
const USER_ROLES = [
'',
'admin',
'moderator'
];
@@ -105,7 +106,7 @@ UserSchema.index({
* output.
*/
UserSchema.options.toJSON = {};
UserSchema.options.toJSON.hide = '_id password profiles roles disabled';
UserSchema.options.toJSON.hide = 'password profiles roles disabled';
UserSchema.options.toJSON.transform = (doc, ret, options) => {
if (options.hide) {
options.hide.split(' ').forEach((prop) => {
-1
View File
@@ -89,7 +89,6 @@
"eslint-config-standard": "^6.2.1",
"eslint-plugin-flowtype": "^2.25.0",
"eslint-plugin-import": "^2.2.0",
"eslint-plugin-mocha": "^4.7.0",
"eslint-plugin-promise": "^3.3.1",
"eslint-plugin-react": "^6.6.0",
"eslint-plugin-standard": "^2.0.1",
+24 -63
View File
@@ -1,6 +1,5 @@
const express = require('express');
const Comment = require('../../../models/comment');
const Asset = require('../../../models/asset');
const User = require('../../../models/user');
const Action = require('../../../models/action');
const wordlist = require('../../../services/wordlist');
@@ -10,45 +9,24 @@ const _ = require('lodash');
const router = express.Router();
router.get('/', authorization.needed('admin'), (req, res, next) => {
const {
status = null,
action_type = null,
asset_id = null
} = req.query;
/**
* 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);
}
return query;
};
let query;
if (status) {
query = assetIDWrap(Comment.findByStatus(status === 'new' ? null : status));
} else if (action_type) {
query = Comment
.findIdsByActionType(action_type)
.then((ids) => assetIDWrap(Comment.find({
id: {
$in: ids
},
})));
if (req.query.status) {
query = Comment.findByStatus(req.query.status);
} else if (req.query.action_type) {
query = Comment.findByActionType(req.query.action_type);
} else {
query = assetIDWrap(Comment.all());
query = Comment.all();
}
query.then((comments) => {
return Promise.all([
comments,
User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))),
Action.getActionSummariesFromComments(asset_id, comments, req.user ? req.user.id : false)
Action.getActionSummaries(_.uniq([
...comments.map((comment) => comment.id),
...comments.map((comment) => comment.author_id)
]))
]);
})
.then(([comments, users, actions])=>
@@ -70,38 +48,21 @@ router.post('/', wordlist.filter('body'), (req, res, next) => {
parent_id
} = req.body;
// Decide the status based on whether or not the current asset/settings
// has pre-mod enabled or not. If the comment was rejected based on the
// wordlist, then reject it, otherwise if the moderation setting is
// premod, set it to `premod`.
let status;
Comment
.create({
body,
asset_id,
parent_id,
status: req.wordlist.matched ? 'rejected' : '',
author_id: req.user.id
})
.then((comment) => {
if (req.wordlist.matched) {
status = Promise.resolve('rejected');
} else {
status = Asset
.rectifySettings(Asset.findById(asset_id))
// Return `premod` if pre-moderation is enabled and an empty "new" status
// in the event that it is not in pre-moderation mode.
.then(({moderation}) => moderation === 'pre' ? 'premod' : '');
}
status.then((status) => Comment.publicCreate({
body,
asset_id,
parent_id,
status,
author_id: req.user.id
}))
.then((comment) => {
// The comment was created! Send back the created comment.
res.status(201).send(comment);
})
.catch((err) => {
next(err);
});
res.status(201).send(comment);
})
.catch((err) => {
next(err);
});
});
router.get('/:comment_id', authorization.needed('admin'), (req, res, next) => {
@@ -138,7 +99,7 @@ router.put('/:comment_id/status', authorization.needed('admin'), (req, res, next
} = req.body;
Comment
.pushStatus(req.params.comment_id, status, req.user.id)
.changeStatus(req.params.comment_id, status)
.then(() => {
res.status(204).end();
})
+21 -47
View File
@@ -3,7 +3,6 @@ const Comment = require('../../../models/comment');
const User = require('../../../models/user');
const Action = require('../../../models/action');
const Setting = require('../../../models/setting');
const Asset = require('../../../models/asset');
const _ = require('lodash');
const router = express.Router();
@@ -17,52 +16,27 @@ const router = express.Router();
// Pre-moderation: New comments are shown in the moderator queues immediately.
// Post-moderation: New comments do not appear in moderation queues unless they are flagged by other users.
router.get('/comments/pending', (req, res, next) => {
const {
asset_id
} = req.query;
let settings = Setting.retrieve();
if (asset_id) {
// In the event that we have an asset_id, we should fetch the asset settings
// in order to actually determine if there is additional comments to parse.
settings = Promise.all([
settings,
Asset.findById(asset_id).select('settings')
]).then(([{moderation}, asset]) => {
if (asset.settings && asset.settings.moderation) {
return {moderation: asset.settings.moderation};
}
return {moderation};
});
}
settings
.then(({moderation}) => {
return Comment.moderationQueue(moderation);
}).then((comments) => {
return Promise.all([
comments,
User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))),
Action.getActionSummaries(_.uniq([
...comments.map((comment) => comment.id),
...comments.map((comment) => comment.author_id)
]))
]);
})
.then(([comments, users, actions]) => {
res.json({
comments,
users,
actions
});
})
.catch(error => {
next(error);
});
Setting.getPublicSettings().then(({moderation}) =>
Comment.moderationQueue(moderation))
.then((comments) => {
return Promise.all([
comments,
User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))),
Action.getActionSummaries(_.uniq([
...comments.map((comment) => comment.id),
...comments.map((comment) => comment.author_id)
]))
]);
})
.then(([comments, users, actions])=>
res.status(200).json({
comments,
users,
actions
}))
.catch(error => {
next(error);
});
});
module.exports = router;
+10 -12
View File
@@ -4,21 +4,19 @@ const Setting = require('../../../models/setting');
const router = express.Router();
router.get('/', (req, res, next) => {
Setting.retrieve().then((settings) => {
res.json(settings);
})
.catch((err) => {
next(err);
});
Setting
.getSettings()
.then(settings => {
res.json(settings);
})
.catch(next);
});
router.put('/', (req, res, next) => {
Setting.update(req.body).then(() => {
res.status(204).end();
})
.catch((err) => {
next(err);
});
Setting
.updateSettings(req.body)
.then(() => res.status(204).end())
.catch(next);
});
module.exports = router;
+15 -5
View File
@@ -26,14 +26,14 @@ router.get('/', (req, res, next) => {
return asset;
}),
// Get the moderation setting from the settings.
Setting.retrieve()
// Get the public settings.
Setting.getPublicSettings()
])
.then(([asset, settings]) => {
// Merge the asset specific settings with the returned settings object in
// the event that the asset that was returned also had settings.
if (asset && asset.settings) {
if (asset.settings) {
settings = Object.assign({}, settings, asset.settings);
}
@@ -70,7 +70,17 @@ router.get('/', (req, res, next) => {
let users = userIDs.length > 0 ? User.findByIdArray(userIDs) : [];
// Fetch the actions for pretty much everything at this point.
let actions = Action.getActionSummariesFromComments(asset.id, comments, req.user ? req.user.id : false);
let actions = Action.getActionSummaries(_.uniq([
// Actions can be on assets...
asset.id,
// Comments...
...comments.map((comment) => comment.id),
// Or Authors...
...userIDs
]), req.user ? req.user.id : false);
return Promise.all([
@@ -98,7 +108,7 @@ router.get('/', (req, res, next) => {
comments,
users,
actions,
settings: Setting.public(settings)
settings
});
})
.catch(error => {
+1 -1
View File
@@ -20,7 +20,7 @@ const wordlist = {
*/
wordlist.init = () => {
return Setting
.retrieve()
.getSettings()
.then((settings) => {
// Insert the settings wordlist.
+1 -5
View File
@@ -4,12 +4,8 @@
"node": true,
"mocha": true
},
"plugins": [
"mocha"
],
"extends": "../.eslintrc.json",
"rules": {
"no-undef": [0],
"mocha/no-exclusive-tests": "warn"
"no-undef": [0]
}
}
+24 -23
View File
@@ -1,34 +1,35 @@
const Action = require('../../models/action');
const expect = require('chai').expect;
describe('models.Action', () => {
let mockActions = [];
describe('Action: models', () => {
let mockActions;
beforeEach(() => Action.create([{
action_type: 'flag',
item_id: '123',
item_type: 'comment',
user_id: 'flagginguserid'
}, {
action_type: 'flag',
item_id: '456',
item_type: 'comment'
}, {
action_type: 'flag',
item_id: '123',
item_type: 'comment'
}, {
action_type: 'like',
item_id: '123',
item_type: 'comment'
}]).then((actions) => {
mockActions = actions;
}));
beforeEach(() => {
return Action.create([{
action_type: 'flag',
item_id: '123',
item_type: 'comment',
user_id: 'flagginguserid'
}, {
action_type: 'flag',
item_id: '456',
item_type: 'comment'
}, {
action_type: 'flag',
item_id: '123',
item_type: 'comment'
}, {
action_type: 'like',
item_id: '123',
item_type: 'comment'
}]).then((actions) => {
mockActions = actions;
});
});
describe('#findById()', () => {
it('should find an action by id', () => {
return Action.findById(mockActions[0].id).then((result) => {
expect(result).to.not.be.null;
expect(result).to.have.property('action_type', 'flag');
});
});
+1 -1
View File
@@ -6,7 +6,7 @@ const expect = chai.expect;
// Use the chai should.
chai.should();
describe('models.Asset', () => {
describe('Asset: model', () => {
beforeEach(() => {
const defaults = {url:'http://test.com'};
+23 -129
View File
@@ -7,57 +7,35 @@ const settings = {id: '1', moderation: 'pre'};
const expect = require('chai').expect;
describe('models.Comment', () => {
describe('Comment: models', () => {
const comments = [{
body: 'comment 10',
asset_id: '123',
status: [],
status: '',
parent_id: '',
author_id: '123',
id: '1'
}, {
body: 'comment 20',
asset_id: '123',
status: [{
type: 'accepted'
}],
status: 'accepted',
parent_id: '',
author_id: '123',
id: '2'
}, {
body: 'comment 30',
asset_id: '456',
status: [],
status: '',
parent_id: '',
author_id: '456',
id: '3'
}, {
body: 'comment 40',
asset_id: '123',
status: [{
type: 'rejected'
}],
status: 'rejected',
parent_id: '',
author_id: '456',
id: '4'
}, {
body: 'comment 50',
asset_id: '1234',
status: [{
type: 'premod'
}],
parent_id: '',
author_id: '456',
id: '5'
}, {
body: 'comment 60',
asset_id: '1234',
status: [{
type: 'premod'
}],
parent_id: '',
author_id: '456',
id: '6'
}];
const users = [{
@@ -82,69 +60,25 @@ describe('models.Comment', () => {
user_id: '456'
}];
beforeEach(() => Promise.all([
Setting.init(settings),
Comment.create(comments),
User.createLocalUsers(users),
Action.create(actions)
]));
describe('#publicCreate()', () => {
it('creates a new comment', () => {
return Comment.publicCreate({
body: 'This is a comment!',
status: 'accepted'
}).then((c) => {
expect(c).to.not.be.null;
expect(c.id).to.not.be.null;
expect(c.id).to.be.uuid;
expect(c.status).to.have.length(1);
expect(c.status[0]).to.have.property('type', 'accepted');
});
});
it('creates many new comments', () => {
return Comment.publicCreate([{
body: 'This is a comment!',
status: 'accepted'
}, {
body: 'This is another comment!'
}, {
body: 'This is a rejected comment!',
status: 'rejected'
}]).then(([c1, c2, c3]) => {
expect(c1).to.not.be.null;
expect(c1.id).to.be.uuid;
expect(c1.status).to.have.length(1);
expect(c1.status[0]).to.have.property('type', 'accepted');
expect(c2).to.not.be.null;
expect(c2.id).to.be.uuid;
expect(c2.status).to.have.length(0);
expect(c3).to.not.be.null;
expect(c3.id).to.be.uuid;
expect(c3.status).to.have.length(1);
expect(c3.status[0]).to.have.property('type', 'rejected');
});
});
beforeEach(() => {
return Promise.all([
Setting.create(settings),
Comment.create(comments),
User.createLocalUsers(users),
Action.create(actions)
]);
});
describe('#findById()', () => {
it('should find a comment by id', () => {
return Comment.findById('1').then((result) => {
expect(result).to.not.be.null;
expect(result).to.have.property('body', 'comment 10');
});
});
});
describe('#findByAssetId()', () => {
it('should find an array of all comments by asset id', () => {
return Comment.findByAssetId('123').then((result) => {
expect(result).to.have.length(3);
@@ -157,7 +91,6 @@ describe('models.Comment', () => {
expect(result[2]).to.have.property('body', 'comment 40');
});
});
it('should find an array of accepted comments by asset id', () => {
return Comment.findAcceptedByAssetId('123').then((result) => {
expect(result).to.have.length(1);
@@ -168,7 +101,6 @@ describe('models.Comment', () => {
expect(result[0]).to.have.property('body', 'comment 20');
});
});
it('should find an array of new and accepted comments by asset id', () => {
return Comment.findAcceptedAndNewByAssetId('123').then((result) => {
expect(result).to.have.length(2);
@@ -180,16 +112,13 @@ describe('models.Comment', () => {
});
});
});
describe('#moderationQueue()', () => {
it('should find an array of new comments to moderate when pre-moderation', () => {
return Comment.moderationQueue('pre').then((result) => {
expect(result).to.not.be.null;
expect(result).to.have.lengthOf(2);
});
});
it('should find an array of new comments to moderate when post-moderation', () => {
return Comment.moderationQueue('post').then((result) => {
expect(result).to.not.be.null;
@@ -197,56 +126,21 @@ describe('models.Comment', () => {
expect(result[0]).to.have.property('body', 'comment 30');
});
});
// it('should fail when the moderation is not pre or post', () => {
// return Comment.moderationQueue('any').catch(function(error) {
// expect(error).to.not.be.null;
// });
// });
});
describe('#removeAction', () => {
it('should remove an action', () => {
return Comment.removeAction('3', '123', 'flag')
.then(() => {
return Action.findByItemIdArray(['123']);
})
.then((actions) => {
expect(actions.length).to.equal(0);
});
return Comment.removeAction('3', '123', 'flag').then(() => {
return Action.findByItemIdArray(['123']);
})
.then((actions) => {
expect(actions.length).to.equal(0);
});
});
});
describe('#changeStatus', () => {
it('should change the status of a comment from no status', () => {
let comment_id = comments[0].id;
return Comment.findById(comment_id)
.then((c) => {
expect(c).to.have.property('status');
expect(c.status).to.have.length(0);
return Comment.pushStatus(comment_id, 'rejected', '123');
})
.then(() => Comment.findById(comment_id))
.then((c) => {
expect(c).to.have.property('status');
expect(c.status).to.have.length(1);
expect(c.status[0]).to.have.property('type', 'rejected');
expect(c.status[0]).to.have.property('assigned_by', '123');
});
});
it('should change the status of a comment from accepted', () => {
return Comment.pushStatus(comments[1].id, 'rejected', '123')
.then(() => Comment.findById(comments[1].id))
.then((c) => {
expect(c).to.have.property('status');
expect(c.status).to.have.length(2);
expect(c.status[0]).to.have.property('type', 'accepted');
expect(c.status[0]).to.have.property('assigned_by', null);
expect(c.status[1]).to.have.property('type', 'rejected');
expect(c.status[1]).to.have.property('assigned_by', '123');
});
});
});
});
+17 -9
View File
@@ -1,29 +1,34 @@
const Setting = require('../../models/setting');
const expect = require('chai').expect;
describe('models.Setting', () => {
describe('Setting: model', () => {
beforeEach(() => Setting.init({moderation: 'pre'}));
beforeEach(() => {
const defaults = {
id: 1
};
return Setting.update({id: '1'}, {$setOnInsert: defaults}, {upsert: true});
});
describe('#retrieve()', () => {
describe('#getSettings()', () => {
it('should have a moderation field defined', () => {
return Setting.retrieve().then(settings => {
return Setting.getSettings().then(settings => {
expect(settings).to.have.property('moderation').and.to.equal('pre');
});
});
it('should have two infoBox fields defined', () => {
return Setting.retrieve().then(settings => {
return Setting.getSettings().then(settings => {
expect(settings).to.have.property('infoBoxEnable').and.to.equal(false);
expect(settings).to.have.property('infoBoxContent').and.to.equal('');
});
});
});
describe('#update()', () => {
describe('#updateSettings()', () => {
it('should update the settings with a passed object', () => {
const mockSettings = {moderation: 'post', infoBoxEnable: true, infoBoxContent: 'yeah'};
return Setting.update(mockSettings).then(updatedSettings => {
return Setting.updateSettings(mockSettings).then(updatedSettings => {
expect(updatedSettings).to.be.an('object');
expect(updatedSettings).to.have.property('moderation').and.to.equal('post');
expect(updatedSettings).to.have.property('infoBoxEnable', true);
@@ -32,10 +37,13 @@ describe('models.Setting', () => {
});
});
describe('#get', () => {
describe('#getPublicSettings', () => {
it('should return the moderation settings', () => {
return Setting.retrieve().then(({moderation}) => {
return Setting.getPublicSettings().then(({moderation, infoBoxEnable, infoBoxContent, wordlist}) => {
expect(moderation).not.to.be.null;
expect(infoBoxEnable).not.to.be.null;
expect(infoBoxContent).not.to.be.null;
expect(wordlist).to.be.undefined;
});
});
});
+1 -1
View File
@@ -1,7 +1,7 @@
const User = require('../../models/user');
const expect = require('chai').expect;
describe('models.User', () => {
describe('User: models', () => {
let mockUsers;
beforeEach(() => {
return User.createLocalUsers([{
+65 -101
View File
@@ -10,7 +10,6 @@ chai.use(require('chai-http'));
const wordlist = require('../../../../services/wordlist');
const Comment = require('../../../../models/comment');
const Asset = require('../../../../models/asset');
const Action = require('../../../../models/action');
const User = require('../../../../models/user');
@@ -18,71 +17,63 @@ const Setting = require('../../../../models/setting');
const settings = {id: '1', moderation: 'pre'};
describe('/api/v1/comments', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
}, {
id: 'def-rejected',
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: 'rejected'
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456',
author_id: '456',
status: 'accepted'
}];
const users = [{
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123'
}, {
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123'
}];
const actions = [{
action_type: 'flag',
item_id: 'abc',
item_type: 'comment'
}, {
action_type: 'like',
item_id: 'hij',
item_type: 'comment'
}];
beforeEach(() => {
return Promise.all([
Comment.create(comments),
User.createLocalUsers(users),
Action.create(actions),
wordlist.insert([
'bad words'
]),
Setting.create(settings)
]);
});
describe('#get', () => {
const comments = [{
body: 'comment 10',
asset_id: 'asset',
author_id: '123'
}, {
body: 'comment 20',
asset_id: 'asset',
author_id: '456'
}, {
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: [{
type: 'rejected'
}]
}, {
body: 'comment 30',
asset_id: '456',
status: [{
type: 'accepted'
}]
}];
const users = [{
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123'
}, {
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123'
}];
const actions = [{
action_type: 'flag',
item_id: 'abc',
item_type: 'comment'
}, {
action_type: 'like',
item_id: 'hij',
item_type: 'comment'
}];
beforeEach(() => {
return Promise.all([
Comment.create(comments).then((newComments) => {
newComments.forEach((comment, i) => {
comments[i].id = comment.id;
});
actions[0].item_id = comments[0].id;
actions[1].item_id = comments[1].id;
return Action.create(actions);
}),
User.createLocalUsers(users),
wordlist.insert([
'bad words'
]),
Setting.init(settings)
]);
});
it('should return all the comments', () => {
return chai.request(app)
.get('/api/v1/comments')
@@ -100,9 +91,7 @@ describe('/api/v1/comments', () => {
.set(passport.inject({roles: ['admin']}))
.then((res) => {
expect(res).to.have.status(200);
expect(res.body).to.have.property('comments');
expect(res.body.comments).to.have.length(1);
expect(res.body.comments[0]).to.have.property('id', comments[2].id);
expect(res.body.comments[0]).to.have.property('id', 'def-rejected');
});
});
@@ -113,7 +102,7 @@ describe('/api/v1/comments', () => {
.then((res) => {
expect(res).to.have.status(200);
expect(res.body.comments).to.have.length(1);
expect(res.body.comments[0]).to.have.property('id', comments[3].id);
expect(res.body.comments[0]).to.have.property('id', 'hij');
});
});
@@ -135,7 +124,8 @@ describe('/api/v1/comments', () => {
expect(res).to.have.status(200);
expect(res.body.comments).to.have.length(1);
expect(res.body.comments[0]).to.have.property('id', comments[0].id);
expect(res.body.comments[0]).to.have.property('id', 'abc');
});
});
});
@@ -161,31 +151,7 @@ describe('/api/v1/comments', () => {
.then((res) => {
expect(res).to.have.status(201);
expect(res.body).to.have.property('id');
expect(res.body).to.have.property('status').and.to.have.length(1);
expect(res.body.status[0]).to.have.property('type', 'rejected');
});
});
it('should create a comment with a premod status if it\'s asset is has pre-moderation enabled', () => {
return Asset
.findOrCreateByUrl('https://coralproject.net/article1')
.then((asset) => {
return Asset
.overrideSettings(asset.id, {moderation: 'pre'})
.then(() => asset);
})
.then((asset) => {
return chai.request(app)
.post('/api/v1/comments')
.set(passport.inject({roles: []}))
.send({'body': 'Something body.', 'author_id': '123', 'asset_id': asset.id, 'parent_id': ''});
})
.then((res) => {
expect(res).to.have.status(201);
expect(res.body).to.have.property('id');
expect(res.body).to.have.property('asset_id');
expect(res.body).to.have.property('status').and.to.have.length(1);
expect(res.body.status[0]).to.have.property('type', 'premod');
expect(res.body).to.have.property('status', 'rejected');
});
});
});
@@ -301,22 +267,18 @@ describe('/api/v1/comments/:comment_id/actions', () => {
body: 'comment 10',
asset_id: 'asset',
author_id: '123',
status: []
status: ''
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: [{
type: 'rejected'
}]
status: 'rejected'
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456',
status: [{
type: 'accepted'
}]
status: 'accepted'
}];
const users = [{
@@ -354,8 +316,10 @@ describe('/api/v1/comments/:comment_id/actions', () => {
.then((res) => {
expect(res).to.have.status(201);
expect(res).to.have.body;
expect(res.body).to.have.property('item_type', 'comment');
expect(res.body).to.have.property('action_type', 'flag');
expect(res.body).to.have.property('item_id', 'abc');
expect(res.body).to.have.property('user_id', '456');
});
});
});
+62 -62
View File
@@ -15,54 +15,56 @@ const User = require('../../../../models/user');
const Setting = require('../../../../models/setting');
const settings = {id: '1', moderation: 'pre'};
describe('/api/v1/queue', () => {
const comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
author_id: '123',
status: [{
type: 'rejected'
}]
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset',
author_id: '456',
status: [{
type: 'premod'
}]
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456',
status: [{
type: 'accepted'
}]
}];
beforeEach(() => {
return Setting.create(settings);
});
const users = [{
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123'
}, {
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123'
}];
describe('Get moderation queues rejected, pending, flags', () => {
const actions = [{
action_type: 'flag',
item_id: 'abc',
item_type: 'comment'
}, {
action_type: 'like',
item_id: 'hij',
item_type: 'comment'
}];
describe('/api/v1/queue', () => {
let comments;
beforeEach(() => {
return User.createLocalUsers(users)
const users = [{
id: '456',
displayName: 'Ana',
email: 'ana@gmail.com',
password: '123'
}, {
id: '123',
displayName: 'Maria',
email: 'maria@gmail.com',
password: '123'
}];
let actions;
beforeEach(() => {
comments = [{
id: 'abc',
body: 'comment 10',
asset_id: 'asset',
status: 'rejected'
}, {
id: 'def',
body: 'comment 20',
asset_id: 'asset'
}, {
id: 'hij',
body: 'comment 30',
asset_id: '456',
status: 'accepted'
}];
actions = [{
action_type: 'flag',
item_type: 'comment'
}, {
action_type: 'like',
item_type: 'comment'
}];
return User.createLocalUsers(users)
.then((u) => {
comments[0].author_id = u[0].id;
comments[1].author_id = u[1].id;
@@ -74,24 +76,22 @@ describe('/api/v1/queue', () => {
actions[0].item_id = c[0].id;
actions[1].item_id = c[1].id;
return Promise.all([
Action.create(actions),
Setting.init(settings)
]);
return Action.create(actions);
});
});
});
it('should return all the pending comments, users and actions', function(done){
chai.request(app)
.get('/api/v1/queue/comments/pending')
.set(passport.inject({roles: ['admin']}))
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res.body.comments[0]).to.have.property('body');
expect(res.body.users[0]).to.have.property('displayName');
expect(res.body.actions[0]).to.have.property('action_type');
done();
});
it('should return all the pending comments, users and actions', function(done){
chai.request(app)
.get('/api/v1/queue/comments/pending')
.set(passport.inject({roles: ['admin']}))
.end(function(err, res){
expect(err).to.be.null;
expect(res).to.have.status(200);
expect(res.body.comments[0]).to.have.property('body');
expect(res.body.users[0]).to.have.property('displayName');
expect(res.body.actions[0]).to.have.property('action_type');
done();
});
});
});
});
+2 -2
View File
@@ -12,7 +12,7 @@ const defaults = {id: '1', moderation: 'pre'};
describe('/api/v1/settings', () => {
beforeEach(() => Setting.init(defaults));
beforeEach(() => Setting.create(defaults));
describe('#get', () => {
@@ -40,7 +40,7 @@ describe('/api/v1/settings', () => {
.then((res) => {
expect(res).to.have.status(204);
return Setting.retrieve();
return Setting.getSettings();
})
.then((settings) => {
expect(settings).to.have.property('moderation', 'post');
+29 -33
View File
@@ -20,37 +20,7 @@ describe('/api/v1/stream', () => {
moderation: 'post'
};
const comments = [{
id: 'abc',
body: 'comment 10',
author_id: '',
parent_id: '',
status: [{
type: 'accepted'
}]
}, {
id: 'def',
body: 'comment 20',
author_id: '',
parent_id: '',
status: []
}, {
id: 'uio',
body: 'comment 30',
asset_id: 'asset',
author_id: '456',
parent_id: '',
status: [{
type: 'accepted'
}]
}, {
id: 'hij',
body: 'comment 40',
asset_id: '456',
status: [{
type: 'rejected'
}]
}];
let comments;
const users = [{
displayName: 'Ana',
@@ -71,6 +41,33 @@ describe('/api/v1/stream', () => {
}];
beforeEach(() => {
comments = [{
id: 'abc',
body: 'comment 10',
author_id: '',
parent_id: '',
status: 'accepted'
}, {
id: 'def',
body: 'comment 20',
author_id: '',
parent_id: '',
status: ''
}, {
id: 'uio',
body: 'comment 30',
asset_id: 'asset',
author_id: '456',
parent_id: '',
status: 'accepted'
}, {
id: 'hij',
body: 'comment 40',
asset_id: '456',
status: 'rejected'
}];
return Promise.all([
User.createLocalUsers(users),
Asset.findOrCreateByUrl('http://test.com'),
@@ -97,11 +94,10 @@ describe('/api/v1/stream', () => {
return Promise.all([
Comment.create(comments),
Action.create(actions),
Setting.init(settings)
Setting.init().then(() => Setting.updateSettings(settings))
]);
});
});
it('should return a stream with comments, users and actions for an existing asset', () => {
return chai.request(app)
.get('/api/v1/stream')