From c7d2a9a94f2d5322fa02c2c7e7e8cd15d2e6c94b Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 17 Nov 2016 14:52:08 -0500 Subject: [PATCH 01/40] Adding padding for notifications at the bottom of the comment stream. --- client/coral-embed-stream/style/default.css | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/style/default.css b/client/coral-embed-stream/style/default.css index 2f7394d4d..86fc66026 100644 --- a/client/coral-embed-stream/style/default.css +++ b/client/coral-embed-stream/style/default.css @@ -3,7 +3,9 @@ body { font-family: 'Open Sans', sans-serif; width: 100%; font-size: 12px; - margin: 0; + margin: 0px; + padding: 0px 0px 50px 0px; + } button { From 243c36a306f37a13f259848800c565f013db99f9 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 17 Nov 2016 17:45:44 -0500 Subject: [PATCH 02/40] Switching Permalink props on replies to avoid null errors. --- client/coral-embed-stream/src/CommentStream.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index ecfc6d7d2..1ed51a5d9 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -187,8 +187,8 @@ class CommentStream extends Component { updateItem={this.props.updateItem} currentUser={this.props.auth.user}/> ; From 53fa63b269c9134ba93cbea1df87a39f30ccea32 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 18 Nov 2016 11:30:38 -0500 Subject: [PATCH 03/40] Fixing bug where reply buttons in replies to not operate as expected. --- client/coral-embed-stream/src/CommentStream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 1ed51a5d9..72e4e5919 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -165,7 +165,7 @@ class CommentStream extends Component {
+ parent_id={reply.parent_id}/> Date: Fri, 18 Nov 2016 11:50:12 -0500 Subject: [PATCH 04/40] Addressing but where flag increases like count. --- models/action.js | 12 +++++------- tests/models/action.js | 16 ++++++++++++++-- 2 files changed, 19 insertions(+), 9 deletions(-) diff --git a/models/action.js b/models/action.js index 574f3439f..43e38795c 100644 --- a/models/action.js +++ b/models/action.js @@ -45,25 +45,23 @@ ActionSchema.statics.getActionSummaries = function(item_ids) { return ActionSchema.statics.findByItemIdArray(item_ids).then((rawActions) => { // Create an object with a count of each action type for each item const actionSummaries = rawActions.reduce((actionObj, action) => { - if (!actionObj[action.item_id]) { - actionObj[action.item_id] = { + if (!actionObj[`${action.item_id}_${action.action_type}`]) { + actionObj[`${action.item_id}_${action.action_type}`] = { id: action.id, + item_id: action.item_id, item_type: action.item_type, action_type: action.action_type, count: 1, current_user: false //Update this later when we have authentication }; } else { - actionObj[action.item_id].count ++; + actionObj[`${action.item_id}_${action.action_type}`].count ++; } return actionObj; }, {}); - // Return an array extracted from the actionSummaries object return Object.keys(actionSummaries).reduce((actions, key) => { - let actionSummary = actionSummaries[key]; - actionSummary.item_id = key; - actions.push(actionSummary); + actions.push(actionSummaries[key]); return actions; }, []); }); diff --git a/tests/models/action.js b/tests/models/action.js index 257c191d6..917191690 100644 --- a/tests/models/action.js +++ b/tests/models/action.js @@ -22,6 +22,10 @@ describe('Action: models', () => { action_type: 'flag', item_id: '123', item_type: 'comments' + }, { + action_type: 'like', + item_id: '123', + item_type: 'comments' }]).then((actions) => { mockActions = actions; }); @@ -39,7 +43,7 @@ describe('Action: models', () => { describe('#findByItemIdArray()', () => { it('should find an array of actions from an array of item_ids', () => { return Action.findByItemIdArray(['123', '456']).then((result) => { - expect(result).to.have.length(3); + expect(result).to.have.length(4); }); }); }); @@ -47,10 +51,11 @@ describe('Action: models', () => { describe('#getActionSummaries()', () => { it('should return properly formatted summaries from an array of item_ids', () => { return Action.getActionSummaries(['123', '789']).then((result) => { - expect(result).to.have.length(2); + expect(result).to.have.length(3); const sorted = result.sort((a, b) => a.count - b.count); delete sorted[0].id; delete sorted[1].id; + delete sorted[2].id; expect(sorted[0]).to.deep.equal({ action_type: 'like', count: 1, @@ -59,6 +64,13 @@ describe('Action: models', () => { current_user: false }); expect(sorted[1]).to.deep.equal({ + action_type: 'like', + count: 1, + item_id: '123', + item_type: 'comments', + current_user: false + }); + expect(sorted[2]).to.deep.equal({ action_type: 'flag', count: 2, item_id: '123', From b14bd02de61d63efcd9f3bb351042972f02a7777 Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 18 Nov 2016 11:51:08 -0500 Subject: [PATCH 05/40] Addressing style bug in FlagButton. --- client/coral-plugin-flags/FlagButton.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index a4869c486..1b1d114c6 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -31,7 +31,7 @@ const FlagButton = ({flag, id, postAction, deleteAction, addItem, updateItem, ad : {lang.t('flag')} } flag
; From fe4f8bc4646ec9a2c5b85ed33e1a4a9e5648e4f1 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 18 Nov 2016 09:56:52 -0700 Subject: [PATCH 06/40] Replaced query with aggregation --- models/action.js | 73 +++++++++++++++++++++++++++++++++--------------- 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/models/action.js b/models/action.js index 43e38795c..8173db9f9 100644 --- a/models/action.js +++ b/models/action.js @@ -42,29 +42,58 @@ ActionSchema.statics.findByItemIdArray = function(item_ids) { * @param {String} ids array of user identifiers (uuid) */ ActionSchema.statics.getActionSummaries = function(item_ids) { - return ActionSchema.statics.findByItemIdArray(item_ids).then((rawActions) => { - // Create an object with a count of each action type for each item - const actionSummaries = rawActions.reduce((actionObj, action) => { - if (!actionObj[`${action.item_id}_${action.action_type}`]) { - actionObj[`${action.item_id}_${action.action_type}`] = { - id: action.id, - item_id: action.item_id, - item_type: action.item_type, - action_type: action.action_type, - count: 1, - current_user: false //Update this later when we have authentication - }; - } else { - actionObj[`${action.item_id}_${action.action_type}`].count ++; + return Action.aggregate([ + { + + // only grab items that match the specified item id's + $match: { + item_id: { + $in: item_ids + } } - return actionObj; - }, {}); - // Return an array extracted from the actionSummaries object - return Object.keys(actionSummaries).reduce((actions, key) => { - actions.push(actionSummaries[key]); - return actions; - }, []); - }); + }, + { + $group: { + + // group unique documents by these properties, we are leveraging the + // fact that each uuid is completly unique. + _id: { + item_id: '$item_id', + action_type: '$action_type' + }, + + // and sum up all actions matching the above grouping criteria + count: { + $sum: 1 + }, + + // we are leveraging the fact that each uuid is completly unique and + // just grabbing the last instance of the item type here. + item_type: { + $last: '$item_type' + } + } + }, + { + $project: { + + // suppress the _id field + _id: false, + + // map the fields from the _id grouping down a level + item_id: '$_id.item_id', + action_type: '$_id.action_type', + + // map the field directly + count: '$count', + item_type: '$item_type', + + // set the current user to false here + current_user: {$literal: false} + } + } + ]) + .exec(); }; /* From 21d96f2210de78b6a77b327e651a975100fd1aa0 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 18 Nov 2016 10:12:22 -0700 Subject: [PATCH 07/40] Added fix for error view --- app.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 6512680aa..f5714fe6e 100644 --- a/app.js +++ b/app.js @@ -97,7 +97,7 @@ app.use('/api', (err, req, res, next) => { res.status(err.status || 500); res.json({ message: err.message, - error: app.get('env') === 'development' ? err : null + error: app.get('env') === 'development' ? err : {} }); }); @@ -109,7 +109,7 @@ app.use('/', (err, req, res, next) => { res.status(err.status || 500); res.render('error', { message: err.message, - error: app.get('env') === 'development' ? err : null + error: app.get('env') === 'development' ? err : {} }); }); From 88f963b56411195db9c42223c1c6c7a5bbf13dc0 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 18 Nov 2016 10:18:44 -0700 Subject: [PATCH 08/40] Removed unneeded lines in test --- tests/models/action.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tests/models/action.js b/tests/models/action.js index 917191690..80a8ae814 100644 --- a/tests/models/action.js +++ b/tests/models/action.js @@ -52,10 +52,9 @@ describe('Action: models', () => { it('should return properly formatted summaries from an array of item_ids', () => { return Action.getActionSummaries(['123', '789']).then((result) => { expect(result).to.have.length(3); + const sorted = result.sort((a, b) => a.count - b.count); - delete sorted[0].id; - delete sorted[1].id; - delete sorted[2].id; + expect(sorted[0]).to.deep.equal({ action_type: 'like', count: 1, @@ -63,6 +62,7 @@ describe('Action: models', () => { item_type: 'comments', current_user: false }); + expect(sorted[1]).to.deep.equal({ action_type: 'like', count: 1, @@ -70,6 +70,7 @@ describe('Action: models', () => { item_type: 'comments', current_user: false }); + expect(sorted[2]).to.deep.equal({ action_type: 'flag', count: 2, From da07b737b2d27750e02bbcc7d9a47b1c44cbb16b Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 18 Nov 2016 12:19:58 -0500 Subject: [PATCH 09/40] Allowing commentstream to be rendered outside of embed. --- client/coral-embed-stream/src/CommentStream.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 6fc48a826..f5ac55db8 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -61,8 +61,8 @@ class CommentStream extends Component { // Set up messaging between embedded Iframe an parent component // Using recommended Pym init code which violates .eslint standards const pym = new Pym.Child({polling: 100}); - const path = /https?\:\/\/([^?]+)/.exec(pym.parentUrl)[1]; - this.props.getStream(path); + const path = /https?\:\/\/([^?]+)/.exec(pym.parentUrl); + this.props.getStream(path && path[1] || window.location); } render () { From a0db48ea224059905ab79c27980346d384c3817c Mon Sep 17 00:00:00 2001 From: David Jay Date: Fri, 18 Nov 2016 13:10:59 -0500 Subject: [PATCH 10/40] Adding author id to posted comments. --- client/coral-embed-stream/src/CommentStream.js | 2 +- client/coral-plugin-commentbox/CommentBox.js | 11 ++++++----- swagger.yaml | 2 +- 3 files changed, 8 insertions(+), 7 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index cb5fcd89a..7d8669d38 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -105,7 +105,7 @@ class CommentStream extends Component { id={rootItemId} premod={this.props.config.moderation} reply={false} - canPost={loggedIn} + authorId={user.id} /> {!loggedIn && } diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index e712d8e24..be03177f8 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -12,7 +12,8 @@ class CommentBox extends Component { id: PropTypes.string, comments: PropTypes.array, reply: PropTypes.bool, - canPost: PropTypes.bool + canPost: PropTypes.bool, + currentUser: PropTypes.object } state = { @@ -21,11 +22,11 @@ class CommentBox extends Component { } postComment = () => { - const {postItem, updateItem, id, parent_id, addNotification, appendItemArray, premod} = this.props; + const {postItem, updateItem, id, parent_id, addNotification, appendItemArray, premod, authorId} = this.props; let comment = { body: this.state.body, asset_id: id, - username: this.state.username + user_id: authorId.id }; let related; let parent_type; @@ -52,7 +53,7 @@ class CommentBox extends Component { } render () { - const {styles, reply, canPost} = this.props; + const {styles, reply, authorId} = this.props; // How to handle language in plugins? Should we have a dependency on our central translation file? return
- { canPost && ( + { authorId && ( + { + passwordRequestSuccess + ?

{passwordRequestSuccess}

+ : null + } + { + passwordRequestFailure + ?

{passwordRequestFailure}

+ : null + } + +
+ {lang.t('signIn.needAnAccount')} changeView('SIGNUP')}>{lang.t('signIn.register')} + {lang.t('signIn.alreadyHaveAnAccount')} changeView('SIGNIN')}>{lang.t('signIn.signIn')} +
- - -
- {lang.t('signIn.needAnAccount')} changeView('SIGNUP')}>{lang.t('signIn.register')} - {lang.t('signIn.alreadyHaveAnAccount')} changeView('SIGNIN')}>{lang.t('signIn.signIn')} -
-
-); + ); + } +} export default ForgotContent; diff --git a/client/coral-sign-in/components/styles.css b/client/coral-sign-in/components/styles.css index e8458f314..1561f557b 100644 --- a/client/coral-sign-in/components/styles.css +++ b/client/coral-sign-in/components/styles.css @@ -129,3 +129,14 @@ input.error{ .action { margin-top: 15px; } + +.passwordRequestSuccess { + border: 1px solid green; + background-color: lightgreen; + padding: 10px; +} + +.passwordRequestFailure { + border: 1px solid orange; + background-color: 1px solid coral +} diff --git a/init.js b/init.js index 9c3c2bda5..768d02999 100644 --- a/init.js +++ b/init.js @@ -1,6 +1,15 @@ const Setting = require('./models/setting'); +const wordlist = require('./services/wordlist'); -const defaults = {id: '1', moderation: 'pre'}; -module.exports = Setting.init(defaults); +module.exports = () => Promise.all([ -// presumably this file will grow, which is why I've broken it out. + // Upsert the settings object. + Setting + .init({id: '1', moderation: 'pre'}) + .then(() => { + + // Load in the wordlist now that settings have been init'd. + return wordlist.init(); + }) + +]); diff --git a/models/action.js b/models/action.js index 8173db9f9..91378b0b0 100644 --- a/models/action.js +++ b/models/action.js @@ -117,9 +117,7 @@ ActionSchema.statics.findCommentsIdByActionType = function(action_type, item_typ return Action.find({ 'action_type': action_type, 'item_type': item_type - }, - 'item_id' - ); + }, 'item_id'); }; const Action = mongoose.model('Action', ActionSchema); diff --git a/models/comment.js b/models/comment.js index d17e98860..2ac130978 100644 --- a/models/comment.js +++ b/models/comment.js @@ -17,7 +17,6 @@ const CommentSchema = new Schema({ }, asset_id: String, author_id: String, - username: String, status: { type: String, enum: ['accepted', 'rejected', ''], @@ -31,19 +30,6 @@ const CommentSchema = new Schema({ } }); -//============================================================================== -// New Statics -//============================================================================== - -/** - * Create a comment. - * @param {String} body content of comment -*/ -CommentSchema.statics.new = function(body, author_id, asset_id, parent_id, status, username) { - let comment = new Comment({body, author_id, asset_id, parent_id, status, username}); - return comment.save(); -}; - //============================================================================== // Find Statics //============================================================================== @@ -51,7 +37,8 @@ CommentSchema.statics.new = function(body, author_id, asset_id, parent_id, statu /** * 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}); }; @@ -59,7 +46,8 @@ CommentSchema.statics.findById = function(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}); }; @@ -68,7 +56,8 @@ CommentSchema.statics.findByAssetId = function(asset_id) { * 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'}); }; @@ -76,7 +65,8 @@ CommentSchema.statics.findAcceptedByAssetId = function(asset_id) { /** * 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', '']}}); }; @@ -84,7 +74,8 @@ CommentSchema.statics.findAcceptedAndNewByAssetId = function(asset_id) { /** * 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') @@ -99,50 +90,54 @@ CommentSchema.statics.findByActionType = function(action_type) { * 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.findByStatusByActionType = function(status, action_type) { return Action .findCommentsIdByActionType(action_type, 'comment') .then((actions) => { - return Comment.find({ - 'status': status, - 'id': { - '$in': actions.map(a => { - return a.item_id; - }) + 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 = function(status) { - return Comment.find({'status': status}); + return Comment.find({ + status: status === 'new' ? '' : status + }); }; /** * Find comments that need to be moderated (aka moderation queue). * @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){ + // Pre-moderation: New comments are shown in the moderator queues immediately. case 'pre': return Comment.findByStatus('').then((comments) => { return comments; }); + // 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; }); + default: - throw new Error('Moderation setting not found.'); + return Promise.reject(Error('Moderation setting not found.')); } }; @@ -154,16 +149,18 @@ CommentSchema.statics.moderationQueue = function(moderation) { * 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.changeStatus = function(id, status) { - return Comment.findOneAndUpdate({'id': id}, {$set: {'status': status}}, {upsert: false, new: true}); + return Comment.findOneAndUpdate({'id': id}, {$set: {'status': status}}); }; /** * Add an action to the comment. * @param {String} id identifier of the comment (uuid) * @param {String} action the new action to the comment -*/ + * @return {Promise} + */ CommentSchema.statics.addAction = function(id, user_id, action_type) { // check that the comment exist let action = new Action({ @@ -183,7 +180,8 @@ CommentSchema.statics.addAction = function(id, user_id, action_type) { * 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}); }; @@ -193,7 +191,8 @@ CommentSchema.statics.removeById = function(id) { * @param {String} id identifier of the comment (uuid) * @param {String} action_type the type of the action to be removed * @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, @@ -203,6 +202,15 @@ CommentSchema.statics.removeAction = function(item_id, user_id, action_type) { }); }; +/** + * Returns all the comments in the collection. + * @return {Promise} + */ +CommentSchema.statics.all = () => { + return Comment.find(); +}; + +// Comment model. const Comment = mongoose.model('Comment', CommentSchema); module.exports = Comment; diff --git a/models/setting.js b/models/setting.js index fb35e1eef..f0c6a1abc 100644 --- a/models/setting.js +++ b/models/setting.js @@ -2,7 +2,7 @@ const mongoose = require('../mongoose'); const Schema = mongoose.Schema; /** - * this Schema 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 @@ -12,7 +12,8 @@ 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: ''} + infoBoxContent: {type: String, default: ''}, + wordlist: [String] }, { timestamps: { createdAt: 'created_at', diff --git a/package.json b/package.json index fb83055b6..9300c347f 100644 --- a/package.json +++ b/package.json @@ -16,13 +16,8 @@ "config": { "pre-git": { "commit-msg": [], - "pre-commit": [ - "npm run lint", - "npm test" - ], - "pre-push": [ - "npm test" - ], + "pre-commit": ["npm run lint", "npm test"], + "pre-push": ["npm test"], "post-commit": [], "post-merge": [] } @@ -31,12 +26,7 @@ "type": "git", "url": "git+https://github.com/coralproject/talk.git" }, - "keywords": [ - "talk", - "coral", - "coralproject", - "ask" - ], + "keywords": ["talk", "coral", "coralproject", "ask"], "author": "", "license": "Apache-2.0", "bugs": { @@ -56,13 +46,13 @@ "helmet": "^3.1.0", "jsonwebtoken": "^7.1.9", "lodash": "^4.16.6", - "lodash.debounce": "^4.0.8", "mongoose": "^4.6.5", "morgan": "^1.7.0", "nodemailer": "^2.6.4", "passport": "^0.3.2", "passport-facebook": "^2.1.1", "passport-local": "^1.0.0", + "natural": "^0.4.0", "prompt": "^1.0.0", "react-linkify": "^0.1.3", "redis": "^2.6.3", diff --git a/routes/admin/index.js b/routes/admin/index.js index 2b967708a..9d3cbd0a9 100644 --- a/routes/admin/index.js +++ b/routes/admin/index.js @@ -5,9 +5,12 @@ router.get('/embed/stream/preview', (req, res) => { res.render('embed-stream', {basePath: '/client/embed/stream'}); }); -router.get('/password-reset/:token', (req, res, next) => { - // render a page or something? - res.send('ok'); +// this route is expecting there to be a token in the hash +// see /views/password-reset-email.ejs +router.get('/password-reset', (req, res, next) => { + // TODO: store the redirect uri in the token or something fancy + // admins and regular users should probably be redirected to different places. + res.render('password-reset', {redirectUri: process.env.TALK_ROOT_URL}); }); router.get('*', (req, res) => { diff --git a/routes/api/actions/index.js b/routes/api/actions/index.js new file mode 100644 index 000000000..f2f9be390 --- /dev/null +++ b/routes/api/actions/index.js @@ -0,0 +1,19 @@ +const express = require('express'); +const Action = require('../../../models/action'); + +const router = express.Router(); + +router.delete('/:action_id', (req, res, next) => { + Action + .findOneAndRemove({ + id: req.params.action_id + }) + .then(() => { + res.status(204).end(); + }) + .catch(error => { + next(error); + }); +}); + +module.exports = router; diff --git a/routes/api/comments/index.js b/routes/api/comments/index.js index 99957e987..d318857d6 100644 --- a/routes/api/comments/index.js +++ b/routes/api/comments/index.js @@ -1,147 +1,111 @@ const express = require('express'); const Comment = require('../../../models/comment'); +const wordlist = require('../../../services/wordlist'); const router = express.Router(); -//============================================================================== -// Get Routes -//============================================================================== - router.get('/', (req, res, next) => { - Comment.find({}).then((comments) => { - res.status(200).json(comments); + let query; + + 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 = Comment.all(); + } + + query.then(comments => { + res.json(comments); }) - .catch(next); + .catch((err) => { + next(err); + }); +}); + +router.post('/', wordlist.filter('body'), (req, res, next) => { + + const { + body, + asset_id, + parent_id, + author_id + } = req.body; + + Comment + .create({ + body, + asset_id, + parent_id, + status: req.wordlist.matched ? 'rejected' : '', + author_id + }) + .then((comment) => { + + res.status(201).send(comment); + }) + .catch((err) => { + next(err); + }); }); router.get('/:comment_id', (req, res, next) => { Comment .findById(req.params.comment_id) .then(comment => { + if (!comment) { + res.status(404).end(); + return; + } + res.status(200).json(comment); }) - .catch(next); -}); - -// Get all the comments that have an action_type over them. -router.get('/action/:action_type', (req, res, next) => { - Comment - .findByActionType(req.params.action_type) - .then((comments) => { - res.status(200).json(comments); - }) - .catch(next); -}); - -// Get all the comments that were rejected. -router.get('/status/rejected', (req, res, next) => { - Comment.findByStatus('rejected').then(comments => { - res.status(200).json(comments); - }) - .catch(next); -}); - -// Get all the comments that were accepted. -router.get('/status/accepted', (req, res, next) => { - Comment.findByStatus('accepted').then((comments) => { - res.status(200).json(comments); - }) - .catch(error => { - next(error); - }); -}); - -// Get all the not moderated comments. -router.get('/status/new', (req, res, next) => { - Comment.findByStatus('').then((comments) => { - res.status(200).json(comments); - }) - .catch(error => { - next(error); - }); -}); - -//============================================================================== -// Post Routes -//============================================================================== - -router.post('/', (req, res, next) => { - - const {body, author_id, asset_id, parent_id, status, username} = req.body; - - Comment - .new(body, author_id, asset_id, parent_id, status, username) - .then((comment) => { - res.status(200).send({'id': comment.id}); - }) - .catch(error => { - next(error); + .catch((err) => { + next(err); }); }); -router.post('/:comment_id', (req, res, next) => { - Comment - .findById(req.params.comment_id) - .then((comment) => { - comment.body = req.body.body; - comment.author_id = req.body.author_id; - comment.asset_id = req.body.asset_id; - comment.parent_id = req.body.parent_id; - comment.status = req.body.status; - return comment.save(); - }) - .then((comment) => { - res.status(200).send(comment); - }) - .catch(error => { - next(error); - }); -}); - -router.post('/:comment_id/status', (req, res, next) => { - - Comment - .changeStatus(req.params.comment_id, req.body.status) - .then(comment => res.status(200).send(comment)) - .catch(error => next(error)); - -}); - -router.post('/:comment_id/actions', (req, res, next) => { - Comment - .addAction(req.params.comment_id, req.body.user_id, req.body.action_type) - .then((action) => { - res.status(200).send(action); - }) - .catch(error => { - next(error); - }); -}); - -//============================================================================== -// Delete Routes -//============================================================================== - router.delete('/:comment_id', (req, res, next) => { Comment .removeById(req.params.comment_id) .then(() => { - res.status(201).send({}); + res.status(204).end(); }) - .catch(error => { - next(error); + .catch((err) => { + next(err); }); }); -router.delete('/:comment_id/actions', (req, res, next) => { - console.log(req.params); +router.put('/:comment_id/status', (req, res, next) => { + + const { + status + } = req.body; + Comment - .removeAction(req.params.comment_id, req.body.user_id, req.body.action_type) + .changeStatus(req.params.comment_id, status) .then(() => { - res.status(201).send({}); + res.status(204).end(); }) - .catch(error => { - next(error); + .catch((err) => { + next(err); + }); +}); + +router.post('/:comment_id/actions', (req, res, next) => { + + const { + user_id, + action_type + } = req.body; + + Comment + .addAction(req.params.comment_id, user_id, action_type) + .then((action) => { + res.status(201).json(action); + }) + .catch((err) => { + next(err); }); }); diff --git a/routes/api/index.js b/routes/api/index.js index c49e52c9d..7192a1324 100644 --- a/routes/api/index.js +++ b/routes/api/index.js @@ -9,5 +9,6 @@ router.use('/queue', require('./queue')); router.use('/settings', require('./settings')); router.use('/stream', require('./stream')); router.use('/user', require('./user')); +router.use('/actions', require('./actions')); module.exports = router; diff --git a/routes/api/stream/index.js b/routes/api/stream/index.js index b06e3566c..acbfe3d77 100644 --- a/routes/api/stream/index.js +++ b/routes/api/stream/index.js @@ -1,4 +1,5 @@ const express = require('express'); +const _ = require('lodash'); const Comment = require('../../../models/comment'); const User = require('../../../models/user'); @@ -25,9 +26,9 @@ router.get('/', (req, res, next) => { case 'pre': return Promise.all([Comment.findAcceptedByAssetId(asset.id), asset]); case 'post': - return Promise.all([Comment.findAcceptedAndNewByAssetId(asset.id), asset]); + return Promise.all([Comment.findAcceptedAndNewByAssetId(asset.id), asset]); default: - throw new Error('Moderation setting not found.'); + return Promise.reject(new Error('Moderation setting not found.')); } }) // Get all the users and actions for those comments. @@ -35,8 +36,12 @@ router.get('/', (req, res, next) => { return Promise.all([ [asset], comments, - User.findPublicByIdArray(comments.map((comment) => comment.author_id)), - Action.getActionSummaries(comments.map((comment) => comment.id)) + User.findByIdArray(_.uniq(comments.map((comment) => comment.author_id))), + Action.getActionSummaries(_.uniq([ + asset.id, + ...comments.map((comment) => comment.id), + ...comments.map((comment) => comment.author_id) + ])) ]); }) .then(([assets, comments, users, actions]) => { diff --git a/routes/api/user/index.js b/routes/api/user/index.js index 39bb8a5c9..3d5b49d93 100644 --- a/routes/api/user/index.js +++ b/routes/api/user/index.js @@ -79,6 +79,10 @@ router.post('/', (req, res, next) => { router.post('/update-password', (req, res, next) => { const {token, password} = req.body; + if (!password || password.length < 8) { + return res.status(400).send('Password must be at least 8 characters'); + } + User.verifyPasswordResetToken(token) .then(user => { return User.changePassword(user.id, password); @@ -100,7 +104,7 @@ router.post('/request-password-reset', (req, res, next) => { const {email} = req.body; if (!email) { - return next(); + return next('you must submit an email when requesting a password.'); } User diff --git a/services/wordlist.js b/services/wordlist.js new file mode 100644 index 000000000..e6f2ad668 --- /dev/null +++ b/services/wordlist.js @@ -0,0 +1,164 @@ +const debug = require('debug')('talk:services:wordlist'); +const _ = require('lodash'); +const natural = require('natural'); +const tokenizer = new natural.WordTokenizer(); +const Setting = require('../models/setting'); + +/** + * The root wordlist object. + * @type {Object} + */ +const wordlist = { + list: [], + enabled: false +}; + +/** + * Loads wordlists in from the naughty-words package based on languages + * selected. + * @param {Array} languages language codes to add to the wordlist + */ +wordlist.init = () => { + return Setting + .getSettings() + .then((settings) => { + + // Insert the settings wordlist. + wordlist.insert(settings.wordlist); + }); +}; + +/** + * Inserts the wordlist data and enables the wordlist. + * @param {Array} list list of words to be added to the wordlist + */ +wordlist.insert = (list) => { + + // Add the words to this array, but also lowercase the words so that an + // easy comparison can take place. + wordlist.list = _.uniq(wordlist.list.concat(list.map((word) => { + return tokenizer.tokenize(word.toLowerCase()); + }))); + + debug(`Added ${list.length} words to the wordlist, now the wordlist is ${wordlist.list.length} entries long.`); + + // Enable the wordlist. + wordlist.enabled = true; + + return Promise.resolve(wordlist); +}; + +/** + * Tests the phrase to see if it contains any of the defined blockwords. + * @param {String} phrase value to check for blockwords. + * @return {Boolean} true if a blockword is found, false otherwise. + */ +wordlist.match = (phrase) => { + + // Lowercase the word to ensure that we don't miss a match due to + // capitalization. + let lowerPhraseWords = tokenizer.tokenize(phrase.toLowerCase()); + + // This will return true in the event that at least one blockword is found + // in the phrase. + return wordlist.list.some((blockphrase) => { + + // First, let's see if we can find the first word in the blockphrase in the + // source phrase. + let idx = lowerPhraseWords.indexOf(blockphrase[0]); + + if (idx === -1) { + + // The first blockword in the blockphrase did not match the source phrase + // anywhere. + return false; + } + + // Here we'll quick respond with true in the event that the blockphrase was + // just a single word. + if (blockphrase.length === 1) { + return true; + } + + // We found the first word in the source phrase! Lets ensure it matches the + // rest of the blockphrase... + + // Check to see if it even has the length to support this word! + if (lowerPhraseWords.length < idx + blockphrase.length - 1) { + + // We couldn't possibly have the entire phrase here because we don't have + // enough entries! + return false; + } + + for (let i = 1; i < blockphrase.length; i++) { + + // Check to see if the next word also matches! + if (lowerPhraseWords[idx + i] !== blockphrase[i]) { + return false; + } + } + + // We've walked over all the words of the blockphrase, and haven't had a + // mismatch... It does contain the whole word! + return true; + }); +}; + +// ErrContainsProfanity is returned in the event that the middleware detects +// profanity/wordlisted words in the payload. +const ErrContainsProfanity = new Error('contains profanity'); +ErrContainsProfanity.status = 400; + +/** + * Connect middleware for scanning request bodies for wordlisted words and + * attaching a ErrContainsProfanity to the req.wordlisted parameter, otherwise + * it will just set that parameter to false. + * @param {Array} fields selectors for the body to extract the fields to be + * tested + * @return {Function} the Connect middleware + */ +wordlist.filter = (...fields) => (req, res, next) => { + + // Start with the sensible default that the content does not contain + // profanity. + req.wordlist = { + matched: false + }; + + // If the wordlist isn't enabled, then don't actually perform checking and + // forward the request! + if (!wordlist.enabled) { + return next(); + } + + // Loop over all the fields from the body that we want to check. + const containsProfanity = fields.some((field) => { + let phrase = _.get(req.body, field, false); + + // If the field doesn't exist in the body, then it can't be profane! + if (!phrase) { + + // Return that there wasn't a profane word here. + return false; + } + + // Check if the field contains a profane word. + if (wordlist.match(phrase)) { + debug(`the field "${field}" contained a phrase "${phrase}" which contained a wordlisted word/phrase`); + return true; + } + + return false; + }); + + // The body could contain some profanity, address that here. + if (containsProfanity) { + req.wordlist.matched = ErrContainsProfanity; + } + + next(); +}; + +module.exports = wordlist; +module.exports.ErrContainsProfanity = ErrContainsProfanity; diff --git a/swagger.yaml b/swagger.yaml index 9f25c6f42..fd3b0012b 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -7,221 +7,320 @@ host: talk-stg.coralproject.net schemes: - https basePath: /api/v1 +consumes: + - application/json produces: - application/json + paths: /comments: - # get: - # tags: - # - Comments - # produces: - # - application/json - # summary: Comment Types - # description: | - # This endpoint retrieves comments - # parameters: - # - name: id - # in: query - # description: Comment by id - # required: false - # type: string - # responses: - # 200: - # description: An array of comments - # schema: - # type: array - # items: - # $ref: '#/definitions/Comment' + get: + tags: + - Comments + parameters: + - name: status + in: query + description: Performs a search based on the comment's status. + type: string + enum: + - flag + - name: action_type + in: query + description: Performs a search based on the actions that have been added to it. + type: string + enum: + - rejected + - accepted + - new + responses: + 200: + description: Comments matching the query. + schema: + type: array + items: + - $ref: '#/definitions/Comment' + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' post: - description: Add a new comment + tags: + - Comments parameters: - name: body in: body - description: Body - required: true + description: The comment to create. schema: $ref: '#/definitions/Comment' responses: 201: - description: "OK: Comment Added" + description: The comment that was created. schema: - $ref: '#/definitions/Comment' + $ref: '#/definitions/Comment' 500: - description: "Error" + description: An error occured. + schema: + $ref: '#/definitions/Error' + /comments/{comment_id}: + get: + tags: + - Comments + parameters: + - name: comment_id + in: path + description: The id of the comment to retrieve. + type: string + required: true + responses: + 200: + description: The comment was found. + schema: + $ref: '#/definitions/Comment' + 404: + description: The comment was not found. + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' + delete: + tags: + - Comments + parameters: + - name: comment_id + in: path + description: The id of the comment to delete. + type: string + required: true + responses: + 204: + description: The comment was deleted. + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' + /comments/{comment_id}/status: + put: + tags: + - Comments + - Moderation + parameters: + - name: comment_id + in: path + description: The id of the comment to retrieve. + type: string + required: true + - name: body + in: body + description: The status to update to. + required: true + schema: + type: object + properties: + status: + type: string + description: The status to update to. + responses: + 204: + description: The comment status was updated. + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' /comments/{comment_id}/actions: post: tags: - Comments - description: Add a action + - Actions parameters: - name: comment_id in: path - description: Comment ID - required: true + description: The id of the comment to retrieve. type: string + required: true - name: body in: body - description: comment + description: The action to add. required: true schema: - $ref: '#/definitions/Action' + type: object + properties: + action_type: + type: string + description: The action to add responses: 201: - description: Action Added + description: The action created. schema: - type: array - items: - $ref: '#/definitions/Comment' - /comments/{comment_id}/status: - post: + $ref: '#/definitions/Action' + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' + /actions/{action_id}: + delete: tags: - - Comments - description: Add a new status + - Actions parameters: - - name: comment_id + - name: action_id in: path - description: Comment ID - required: true + description: The id of the action to delete. type: string - - name: body - in: body - description: comment required: true - schema: - $ref: '#/definitions/ModerationAction' responses: 204: - description: ModerationAction Added - /queue: + description: The action was deleted. + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' + /auth: get: tags: - - Queue - description: Queue - parameters: - - name: type - in: query - description: - "pending: no status | flagged: flagged action + no status | rejected: rejected status" - required: true - type: string - enum: - - pending - - flagged - - rejected - - name: limit - in: query - description: Queue limit - required: false - type: integer - - name: skip - in: query - description: Skip - required: false - type: integer + - Auth + description: Retrieves the current authentication credentials. responses: 200: - description: ModerationAction Added + description: The current user. + schema: + $ref: '#/definitions/User' + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' + delete: + tags: + - Auth + description: Logs out the current authenticated user. + responses: + 204: + description: The current user has been logged out. + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' + /auth/local: + post: + tags: + - Auth + parameters: + - name: body + in: body + required: true + description: The login credentials. + schema: + type: object + properties: + email: + type: string + description: The email address of the current user. + password: + type: string + description: The password of the current user. + responses: + 200: + description: The user has authenticated sucesfully. + schema: + $ref: '#/definitions/User' + 401: + description: The authentication error. + schema: + $ref: '#/definitions/Error' + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' + /auth/facebook: + get: + tags: + - Auth + responses: + 302: + description: Redirects the user to perform external facebook authentication. + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' + /queue/comments/pending: + get: + tags: + - Comments + - Moderation + responses: + 200: + description: The comments that are not moderated. schema: type: array items: - $ref: '#/definitions/ModerationAction' + - $ref: '#/definitions/Comment' + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' /stream: get: tags: - - Stream - description: Stream + - Actions + - Assets + - Comments + - Users parameters: - - name: asset_id + - name: asset_url in: query - description: Description - required: true + description: The asset url to get the comment stream from. type: string + format: url responses: 200: - description: OK + description: The comment stream. schema: - type: array - items: - $ref: '#/definitions/Item' + type: object + properties: + assets: + type: array + items: + - $ref: '#/definitions/Asset' + comments: + type: array + items: + - $ref: '#/definitions/Comment' + users: + type: array + items: + - $ref: '#/definitions/User' + actions: + type: array + items: + - $ref: '#/definitions/Actions' + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' /settings: get: - tags: - - Settings - description: Settings responses: 200: - description: Get Setting + description: The settings. schema: - type: array - items: - $ref: '#/definitions/Setting' + $ref: '#/definitions/Settings' + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' put: - tags: - - Settings - description: Settings responses: 204: - description: OK - - /user/request-password-reset: - post: - tags: - - Users - description: trigger a reset password email. sends a success code whether email was found or no. - responses: - 204: - description: OK - - /user/update-password: - post: - tags: - - Users - description: Update existing user password - parameters: - - name: token - type: string - in: body - description: JSON Web token taken taken from emailed link - required: true - - name: password - type: string - in: body - description: new password to be settings - required: true - responses: - 204: - description: OK - - /asset: - get: - tags: - - Asset - description: Get an asset by id. - responses: - 200: - description: OK - put: - tags: - - Asset - description: Upsert an asset. - responses: - 204: - description: OK - /asset?url={url}: - get: - tags: - - Asset - parameters: - - name: url - in: query - description: The url of the asset. - required: true - description: Get an asset by its url. - responses: - 200: - description: OK - + description: The settings were updated. + 500: + description: An error occured. + schema: + $ref: '#/definitions/Error' definitions: + Error: + type: object + properties: + message: + type: string + description: The error that occured. Item: type: object ModerationAction: @@ -314,5 +413,10 @@ definitions: type: string description: An array of the authors for this asset. publication_date: - type: date - desctipion: When this asset was published. + type: string + format: datetime + description: When this asset was published. + User: + type: object + Settings: + type: object diff --git a/tests/client/coral-framework/store/itemActions.spec.js b/tests/client/coral-framework/store/itemActions.spec.js index a6f14b65f..340ec9549 100644 --- a/tests/client/coral-framework/store/itemActions.spec.js +++ b/tests/client/coral-framework/store/itemActions.spec.js @@ -175,7 +175,7 @@ describe('itemActions', () => { fetchMock.delete('*', {}); return actions.deleteAction('abc', 'flag', '123', 'comments')(store.dispatch) .then(response => { - expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/comments/abc/actions'); + expect(fetchMock.calls().matched[0][0]).to.equal('/api/v1/actions/abc'); expect(response).to.deep.equal({}); }); }); diff --git a/tests/routes/api/comments/index.js b/tests/routes/api/comments/index.js index cde4b9efb..ec9e05d03 100644 --- a/tests/routes/api/comments/index.js +++ b/tests/routes/api/comments/index.js @@ -10,6 +10,7 @@ const expect = chai.expect; chai.should(); chai.use(require('chai-http')); +const wordlist = require('../../../../services/wordlist'); const Comment = require('../../../../models/comment'); const Action = require('../../../../models/action'); const User = require('../../../../models/user'); @@ -64,13 +65,13 @@ describe('Get /comments', () => { ]); }); - it('should return all the comments', function(done){ - chai.request(app) + it('should return all the comments', () => { + return chai.request(app) .get('/api/v1/comments') - .end(function(err, res){ - expect(err).to.be.null; + .then((res) => { + expect(res).to.have.status(200); - done(); + }); }); }); @@ -122,48 +123,42 @@ describe('Get comments by status and action', () => { ]); }); - it('should return all the rejected comments', function(done){ - chai.request(app) - .get('/api/v1/comments/status/rejected') - .end(function(err, res){ - expect(err).to.be.null; + it('should return all the rejected comments', () => { + return chai.request(app) + .get('/api/v1/comments?status=rejected') + .then((res) => { expect(res).to.have.status(200); expect(res.body[0]).to.have.property('id', 'abc'); - done(); }); }); - it('should return all the approved comments', function(done){ - chai.request(app) - .get('/api/v1/comments/status/accepted') - .end(function(err, res){ - expect(err).to.be.null; + it('should return all the approved comments', () => { + return chai.request(app) + .get('/api/v1/comments?status=accepted') + .then((res) => { expect(res).to.have.status(200); expect(res.body[0]).to.have.property('id', 'hij'); - done(); }); }); - it('should return all the new comments', function(done){ - chai.request(app) - .get('/api/v1/comments/status/new') - .end(function(err, res){ - expect(err).to.be.null; + it('should return all the new comments', () => { + return chai.request(app) + .get('/api/v1/comments?status=new') + .then((res) => { expect(res).to.have.status(200); expect(res.body[0]).to.have.property('id', 'def'); - done(); }); }); - it('should return all the flagged comments', function(done){ - chai.request(app) - .get('/api/v1/comments/action/flag') - .end(function(err, res){ + it('should return all the flagged comments', () => { + return chai.request(app) + .get('/api/v1/comments?action_type=flag') + .then((res) => { expect(res).to.have.status(200); - expect(err).to.be.null; + expect(res.body.length).to.equal(1); expect(res.body[0]).to.have.property('id', 'abc'); - done(); + }); }); }); @@ -190,18 +185,31 @@ describe('Post /comments', () => { beforeEach(() => { return Promise.all([ User.createLocalUsers(users), - Action.create(actions) + Action.create(actions), + wordlist.insert([ + 'bad words' + ]) ]); }); - it('it should create a comment', function(done) { - chai.request(app) + it('should create a comment', () => { + return chai.request(app) .post('/api/v1/comments') .send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) - .end(function(err, res){ - expect(res).to.have.status(200); + .then((res) => { + expect(res).to.have.status(201); expect(res.body).to.have.property('id'); - done(); + }); + }); + + it('should create a comment with a rejected status if it contains a bad word', () => { + return chai.request(app) + .post('/api/v1/comments') + .send({'body': 'bad words are the baddest', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) + .then((res) => { + expect(res).to.have.status(201); + expect(res.body).to.have.property('id'); + expect(res.body).to.have.property('status', 'rejected'); }); }); }); @@ -251,72 +259,14 @@ describe('Get /:comment_id', () => { ]); }); - it('should return the right comment for the comment_id', function(done){ - chai.request(app) + it('should return the right comment for the comment_id', () => { + return chai.request(app) .get('/api/v1/comments/abc') - .end(function(err, res){ - expect(err).to.be.null; + .then((res) => { expect(res).to.have.status(200); expect(res).to.have.property('body'); expect(res.body).to.have.property('body', 'comment 10'); - done(); - }); - }); -}); -describe('Put /:comment_id', () => { - - 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: 'hij', - body: 'comment 30', - asset_id: '456' - }]; - - 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' - }, { - action_type: 'like', - item_id: 'hij' - }]; - - beforeEach(() => { - return Promise.all([ - Comment.create(comments), - User.createLocalUsers(users), - Action.create(actions) - ]); - }); - - it('it should update comment', function(done) { - chai.request(app) - .post('/api/v1/comments/abc') - .send({'body': 'Something body.', 'author_id': '123', 'asset_id': '1', 'parent_id': ''}) - .end(function(err, res){ - expect(err).to.be.null; - expect(res).to.have.status(200); - expect(res.body).to.have.property('body', 'Something body.'); - done(); }); }); }); @@ -369,7 +319,7 @@ describe('Remove /:comment_id', () => { return chai.request(app) .delete('/api/v1/comments/abc') .then((res) => { - expect(res).to.have.status(201); + expect(res).to.have.status(204); return Comment.findById('abc'); }) @@ -384,7 +334,7 @@ process.on('unhandledRejection', (reason) => { console.error(reason); }); -describe('Post /:comment_id/status', () => { +describe('Put /:comment_id/status', () => { const comments = [{ id: 'abc', @@ -433,12 +383,11 @@ describe('Post /:comment_id/status', () => { it('it should update status', function() { return chai.request(app) - .post('/api/v1/comments/abc/status') + .put('/api/v1/comments/abc/status') .send({status: 'accepted'}) .then((res) => { - expect(res).to.have.status(200); - expect(res).to.have.body; - expect(res.body).to.have.property('status', 'accepted'); + expect(res).to.have.status(204); + expect(res.body).to.be.empty; }); }); }); @@ -495,7 +444,7 @@ describe('Post /:comment_id/actions', () => { .post('/api/v1/comments/abc/actions') .send({'user_id': '456', 'action_type': 'flag'}) .then((res) => { - expect(res).to.have.status(200); + 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'); diff --git a/tests/services/wordlist.js b/tests/services/wordlist.js new file mode 100644 index 000000000..0ae76c176 --- /dev/null +++ b/tests/services/wordlist.js @@ -0,0 +1,119 @@ +const expect = require('chai').expect; + +const wordlist = require('../../services/wordlist'); + +describe('wordlist: services', () => { + + before(() => wordlist.insert([ + 'BAD', + 'bad', + 'how to murder', + 'how to kill' + ])); + + beforeEach(() => { + expect(wordlist.list).to.not.be.empty; + expect(wordlist.enabled).to.be.true; + }); + + describe('#init', () => { + + it('has entries', () => { + expect(wordlist.list).to.not.be.empty; + expect(wordlist.enabled).to.be.true; + }); + + }); + + describe('#match', () => { + + it('does match on a bad word', () => { + [ + 'how to kill', + 'what is bad', + 'bad', + 'BAD.', + 'how to murder', + 'How To mUrDer' + ].forEach((word) => { + expect(wordlist.match(word)).to.be.true; + }); + }); + + it('does not match on a good word', () => { + [ + 'how to', + 'kill', + 'bads', + 'how to be a great person?', + 'how to not kill?' + ].forEach((word) => { + expect(wordlist.match(word)).to.be.false; + }); + }); + + }); + + describe('#filter', () => { + + it('matches on bodies containing bad words', (done) => { + + let req = { + body: { + content: 'how to kill?' + } + }; + + wordlist.filter('content')(req, {}, (err) => { + expect(err).to.be.undefined; + expect(req).to.have.property('wordlist'); + expect(req.wordlist).to.have.property('matched'); + expect(req.wordlist.matched).to.be.equal(wordlist.ErrContainsProfanity); + + done(); + }); + + }); + + it('does not match on bodies not containing bad words', (done) => { + + let req = { + body: { + content: 'how to be a great person?' + } + }; + + wordlist.filter('content')(req, {}, (err) => { + expect(err).to.be.undefined; + expect(req).to.have.property('wordlist'); + expect(req.wordlist).to.have.property('matched'); + expect(req.wordlist.matched).to.be.false; + + done(); + }); + + }); + + it('does not match on bodies not containing the bad word field', (done) => { + + let req = { + body: { + author: 'how to kill?', + content: 'how to be a great person?' + } + }; + + wordlist.filter('content')(req, {}, (err) => { + expect(err).to.be.undefined; + expect(req).to.have.property('wordlist'); + expect(req.wordlist).to.have.property('matched'); + expect(req.wordlist.matched).to.be.false; + + done(); + }); + + }); + + }); + +}); diff --git a/views/password-reset-email.ejs b/views/password-reset-email.ejs index 0637b6bb2..17ed9e39b 100644 --- a/views/password-reset-email.ejs +++ b/views/password-reset-email.ejs @@ -1,6 +1,6 @@

We received a request to reset your password. If you did not request this change, you can ignore this email.
-If you did, please click here to reset password.

+If you did, please click here to reset password.

<% if (process.env.NODE_ENV !== 'production') { %>

<%= token %>

<% } %> diff --git a/views/password-reset.ejs b/views/password-reset.ejs new file mode 100644 index 000000000..de23bc16d --- /dev/null +++ b/views/password-reset.ejs @@ -0,0 +1,135 @@ + + + + + + Password Reset + + + + + + +
+
+ Set new password + + + + +
foo
+
+
+ + + + From b74a1bece3f3083f2036622241e3c8cd008d012d Mon Sep 17 00:00:00 2001 From: Bel Date: Tue, 22 Nov 2016 08:10:46 -0300 Subject: [PATCH 21/40] Link Routes --- client/coral-admin/src/components/Header.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js index aa3ba6708..311100074 100644 --- a/client/coral-admin/src/components/Header.js +++ b/client/coral-admin/src/components/Header.js @@ -10,14 +10,14 @@ export default (props) => (
- {lang.t('configure.moderate')} - {lang.t('Configure')} + {lang.t('configure.moderate')} + {lang.t('Configure')}
- {lang.t('configure.moderate')} - {lang.t('configure.Configure')} + {lang.t('configure.moderate')} + {lang.t('configure.Configure')} {props.children} From f7daa35c41c792c37c8b5c01024a76b3e16d50ee Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 22 Nov 2016 08:32:26 -0300 Subject: [PATCH 22/40] Active Routes --- client/coral-admin/src/AppRouter.js | 2 +- client/coral-admin/src/components/Header.js | 27 ------------------- .../coral-admin/src/components/ui/Header.js | 8 +++--- 3 files changed, 5 insertions(+), 32 deletions(-) delete mode 100644 client/coral-admin/src/components/Header.js diff --git a/client/coral-admin/src/AppRouter.js b/client/coral-admin/src/AppRouter.js index 5d21fc819..a0b43361d 100644 --- a/client/coral-admin/src/AppRouter.js +++ b/client/coral-admin/src/AppRouter.js @@ -8,7 +8,7 @@ import CommunityContainer from 'containers/Community/CommunityContainer'; import LayoutContainer from 'containers/LayoutContainer'; const routes = ( - + diff --git a/client/coral-admin/src/components/Header.js b/client/coral-admin/src/components/Header.js deleted file mode 100644 index 311100074..000000000 --- a/client/coral-admin/src/components/Header.js +++ /dev/null @@ -1,27 +0,0 @@ -import React from 'react'; -import {Layout, Navigation, Drawer, Header} from 'react-mdl'; -import {Link} from 'react-router'; -import styles from './Header.css'; -import I18n from 'coral-framework/modules/i18n/i18n'; -import translations from '../translations.json'; - -// App header. If we add a navbar it should be here -export default (props) => ( - -
- - {lang.t('configure.moderate')} - {lang.t('Configure')} - -
- - - {lang.t('configure.moderate')} - {lang.t('configure.Configure')} - - - {props.children} -
-); - -const lang = new I18n(translations); diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index 4a9c09de5..af08eb024 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -1,6 +1,6 @@ import React from 'react'; import {Navigation, Header} from 'react-mdl'; -import {Link} from 'react-router'; +import {Link, IndexLink} from 'react-router'; import styles from './Header.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../../translations.json'; @@ -10,9 +10,9 @@ export default () => (
- {lang.t('configure.moderate')} - {lang.t('configure.community')} - {lang.t('configure.configure')} + {lang.t('configure.moderate')} + {lang.t('configure.community')} + {lang.t('configure.configure')} {`v${process.env.VERSION}`} From 6e6681a5532f6e75829fc566b7f7627ed3435271 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 22 Nov 2016 09:08:08 -0300 Subject: [PATCH 23/40] Styles and refactor errors --- client/coral-admin/src/components/Comment.js | 13 +++++++------ .../coral-admin/src/components/CommentList.js | 6 +++--- client/coral-admin/src/components/ui/Header.css | 17 +++++++++++++++++ client/coral-admin/src/components/ui/Header.js | 12 ++++++------ 4 files changed, 33 insertions(+), 15 deletions(-) diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 154d68134..1106435b0 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -1,6 +1,6 @@ import React from 'react'; -import {Button, Icon} from 'react-mdl'; +import {FABButton, Icon} from 'react-mdl'; import timeago from 'timeago.js'; import styles from './CommentList.css'; import I18n from 'coral-framework/modules/i18n/i18n'; @@ -14,7 +14,7 @@ export default props => { const links = linkify.getMatches(props.comment.get('body')); return ( -
  • +
  • person @@ -26,12 +26,13 @@ export default props => { {links ? Contains Link : null}
    - {props.actions.map(action => canShowAction(action, props.comment) ? ( - + ) : null)}
    diff --git a/client/coral-admin/src/components/CommentList.js b/client/coral-admin/src/components/CommentList.js index 819de62b0..e4682252d 100644 --- a/client/coral-admin/src/components/CommentList.js +++ b/client/coral-admin/src/components/CommentList.js @@ -112,15 +112,15 @@ export default class CommentList extends React.Component { } render () { - const {singleView, commentIds, comments, hideActive} = this.props; + const {singleView, commentIds, comments, hideActive, key} = this.props; const {active} = this.state; return ( -
      +
        {commentIds.map((commentId, index) => ( { if (el && commentId === active) { this._active = el; } }} - key={`${index }comment`} + key={index} index={index} onClickAction={this.onClickAction} actions={this.props.actions} diff --git a/client/coral-admin/src/components/ui/Header.css b/client/coral-admin/src/components/ui/Header.css index b80dca496..3d4e7dc77 100644 --- a/client/coral-admin/src/components/ui/Header.css +++ b/client/coral-admin/src/components/ui/Header.css @@ -2,3 +2,20 @@ background: #505050; overflow: hidden; } + +.header > div { + position: relative; + padding: 0; + width: 1170px; + margin: 0 auto; +} + +.active { + background: #232323; +} + +.version { + position: absolute; + right: 0; + width: 50px; +} diff --git a/client/coral-admin/src/components/ui/Header.js b/client/coral-admin/src/components/ui/Header.js index af08eb024..7ba88d25a 100644 --- a/client/coral-admin/src/components/ui/Header.js +++ b/client/coral-admin/src/components/ui/Header.js @@ -10,13 +10,13 @@ export default () => (
        - {lang.t('configure.moderate')} - {lang.t('configure.community')} - {lang.t('configure.configure')} - - {`v${process.env.VERSION}`} - + {lang.t('configure.moderate')} + {lang.t('configure.community')} + {lang.t('configure.configure')} +
        + {`v${process.env.VERSION}`} +
        ); From c8d7585e908ba14b51e5225d50f125d579006ba1 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 22 Nov 2016 09:34:46 -0300 Subject: [PATCH 24/40] Added FabButton to CoralUI --- client/coral-admin/src/components/Comment.js | 9 ++++----- client/coral-ui/components/FabButton.css | 9 +++++++++ client/coral-ui/components/FabButton.js | 11 +++++++++++ client/coral-ui/index.js | 1 + 4 files changed, 25 insertions(+), 5 deletions(-) create mode 100644 client/coral-ui/components/FabButton.css create mode 100644 client/coral-ui/components/FabButton.js diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 1106435b0..80c67aebd 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -1,11 +1,11 @@ import React from 'react'; -import {FABButton, Icon} from 'react-mdl'; import timeago from 'timeago.js'; import styles from './CommentList.css'; import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations.json'; import Linkify from 'react-linkify'; +import {FabButton} from 'coral-ui'; const linkify = new Linkify(); @@ -27,12 +27,11 @@ export default props => { Contains Link : null}
        {props.actions.map((action, i) => canShowAction(action, props.comment) ? ( - props.onClickAction(props.actionsMap[action].status, props.comment.get('id'))} - colored ripple> - - + /> ) : null)}
    diff --git a/client/coral-ui/components/FabButton.css b/client/coral-ui/components/FabButton.css new file mode 100644 index 000000000..688190407 --- /dev/null +++ b/client/coral-ui/components/FabButton.css @@ -0,0 +1,9 @@ +.type--approve { + background: #00796b; + color: rgba(255, 255, 255, 0.901961); +} + +.type--reject { + background: #d32f2f ; + color: rgba(255, 255, 255, 0.901961); +} diff --git a/client/coral-ui/components/FabButton.js b/client/coral-ui/components/FabButton.js new file mode 100644 index 000000000..df48af55d --- /dev/null +++ b/client/coral-ui/components/FabButton.js @@ -0,0 +1,11 @@ +import React from 'react'; +import styles from './FabButton.css'; +import {FABButton, Icon} from 'react-mdl'; + +const FabButton = ({cStyle = 'local', icon, className, ...props}) => ( + + + +); + +export default FabButton; diff --git a/client/coral-ui/index.js b/client/coral-ui/index.js index 8efb5dc7f..416f89671 100644 --- a/client/coral-ui/index.js +++ b/client/coral-ui/index.js @@ -1,2 +1,3 @@ export {default as Dialog} from './components/Dialog'; export {default as CoralLogo} from './components/CoralLogo'; +export {default as FabButton} from './components/FabButton'; From 8108ca09efece918a83a5714f17e74aad10e043e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Tue, 22 Nov 2016 11:04:56 -0300 Subject: [PATCH 25/40] Permissions, views and somestyling --- client/coral-admin/src/actions/auth.js | 7 +++++-- client/coral-admin/src/components/NotFound.css | 12 ++++++++++++ client/coral-admin/src/components/NotFound.js | 13 +++++++++++++ .../src/components/PermissionRequired.js | 13 +++++++++++++ .../coral-admin/src/containers/LayoutContainer.js | 14 +++++++++++++- .../coral-admin/src/containers/ModerationQueue.css | 6 +++++- .../coral-admin/src/containers/ModerationQueue.js | 4 ++-- client/coral-admin/src/helpers/response.js | 13 ++++++++----- client/coral-admin/src/reducers/auth.js | 4 +++- routes/api/auth/index.js | 2 +- 10 files changed, 75 insertions(+), 13 deletions(-) create mode 100644 client/coral-admin/src/components/NotFound.css create mode 100644 client/coral-admin/src/components/NotFound.js create mode 100644 client/coral-admin/src/components/PermissionRequired.js diff --git a/client/coral-admin/src/actions/auth.js b/client/coral-admin/src/actions/auth.js index c7643cc08..c5a03132f 100644 --- a/client/coral-admin/src/actions/auth.js +++ b/client/coral-admin/src/actions/auth.js @@ -4,13 +4,16 @@ import {base, handleResp, getInit} from '../helpers/response'; // Check Login const checkLoginRequest = () => ({type: actions.CHECK_LOGIN_REQUEST}); -const checkLoginSuccess = user => ({type: actions.CHECK_LOGIN_SUCCESS, user}); +const checkLoginSuccess = (user, isAdmin) => ({type: actions.CHECK_LOGIN_SUCCESS, user, isAdmin}); const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error}); export const checkLogin = () => dispatch => { dispatch(checkLoginRequest()); fetch(`${base}/auth`, getInit('GET')) .then(handleResp) - .then(user => dispatch(checkLoginSuccess(user))) + .then(user => { + const isAdmin = !!user.roles.filter(i => i === 'admin').length; + dispatch(checkLoginSuccess(user, isAdmin)); + }) .catch(error => dispatch(checkLoginFailure(error))); }; diff --git a/client/coral-admin/src/components/NotFound.css b/client/coral-admin/src/components/NotFound.css new file mode 100644 index 000000000..fc9aedc6b --- /dev/null +++ b/client/coral-admin/src/components/NotFound.css @@ -0,0 +1,12 @@ +.layout { + max-width: 800px; + margin: 0 auto; +} + +.layout h1 { + font-size: 40px; +} + +.layout img { + width: 100%; +} diff --git a/client/coral-admin/src/components/NotFound.js b/client/coral-admin/src/components/NotFound.js new file mode 100644 index 000000000..fc9089ba3 --- /dev/null +++ b/client/coral-admin/src/components/NotFound.js @@ -0,0 +1,13 @@ +import React from 'react'; +import {Layout} from 'react-mdl'; +import styles from './NotFound.css'; + +export const NotFound = () => ( + +
    +

    Page Not Found

    +

    The communicorn feels your pain.

    + Communicorn +
    +
    +); diff --git a/client/coral-admin/src/components/PermissionRequired.js b/client/coral-admin/src/components/PermissionRequired.js new file mode 100644 index 000000000..c61ab1cef --- /dev/null +++ b/client/coral-admin/src/components/PermissionRequired.js @@ -0,0 +1,13 @@ +import React from 'react'; +import {Layout} from 'react-mdl'; +import styles from './NotFound.css'; + +export const PermissionRequired = () => ( + +
    +

    Permission Required

    +

    We’re sorry, but you don’t have access to that page.

    + Communicorn +
    +
    +); diff --git a/client/coral-admin/src/containers/LayoutContainer.js b/client/coral-admin/src/containers/LayoutContainer.js index 429f2cc4d..5f3cb0cff 100644 --- a/client/coral-admin/src/containers/LayoutContainer.js +++ b/client/coral-admin/src/containers/LayoutContainer.js @@ -1,13 +1,25 @@ import React, {Component} from 'react'; import {connect} from 'react-redux'; import {Layout} from '../components/ui/Layout'; -import {checkLogin} from 'coral-framework/actions/auth'; +import {checkLogin} from '../actions/auth'; +import {NotFound} from '../components/NotFound'; +import {PermissionRequired} from '../components/PermissionRequired'; class LayoutContainer extends Component { componentWillMount () { this.props.checkLogin(); } render () { + const {isAdmin, loggedIn} = this.props.auth; + + if (!loggedIn) { + return ; + } + + if (!isAdmin && loggedIn) { + return ; + } + return ; } } diff --git a/client/coral-admin/src/containers/ModerationQueue.css b/client/coral-admin/src/containers/ModerationQueue.css index 02f9eb9e9..6d40b5935 100644 --- a/client/coral-admin/src/containers/ModerationQueue.css +++ b/client/coral-admin/src/containers/ModerationQueue.css @@ -1,4 +1,3 @@ - @custom-media --big-viewport (min-width: 780px); .listContainer { @@ -6,8 +5,13 @@ margin: 0 auto; } +.tabBar { + background: #9E9E9E; +} + .tab { flex: 1; + color: white; } @media (--big-viewport) { diff --git a/client/coral-admin/src/containers/ModerationQueue.js b/client/coral-admin/src/containers/ModerationQueue.js index 7a30b8487..88150012a 100644 --- a/client/coral-admin/src/containers/ModerationQueue.js +++ b/client/coral-admin/src/containers/ModerationQueue.js @@ -61,9 +61,9 @@ class ModerationQueue extends React.Component { return (
    -
    +
    this.onTabClick('pending')} - className={`mdl-tabs__tab is-active ${styles.tab}`}>{lang.t('modqueue.pending')} + className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.pending')} this.onTabClick('rejected')} className={`mdl-tabs__tab ${styles.tab}`}>{lang.t('modqueue.rejected')} this.onTabClick('flagged')} diff --git a/client/coral-admin/src/helpers/response.js b/client/coral-admin/src/helpers/response.js index 439ba5a8a..bccfc5a04 100644 --- a/client/coral-admin/src/helpers/response.js +++ b/client/coral-admin/src/helpers/response.js @@ -1,12 +1,15 @@ export const base = '/api/v1'; export const getInit = (method, body) => { - const headers = new Headers({ - 'Content-Type': 'application/json', - 'Accept': 'application/json' - }); + let init = { + method, + headers: new Headers({ + 'Content-Type': 'application/json', + 'Accept': 'application/json' + }), + credentials: 'same-origin' + }; - const init = {method, headers}; if (method.toLowerCase() !== 'get') { init.body = JSON.stringify(body); } diff --git a/client/coral-admin/src/reducers/auth.js b/client/coral-admin/src/reducers/auth.js index bdb2bb074..59dccac5e 100644 --- a/client/coral-admin/src/reducers/auth.js +++ b/client/coral-admin/src/reducers/auth.js @@ -3,7 +3,8 @@ import * as actions from '../constants/auth'; const initialState = Map({ loggedIn: false, - user: null + user: null, + isAdmin: false }); export default function auth (state = initialState, action) { @@ -15,6 +16,7 @@ export default function auth (state = initialState, action) { case actions.CHECK_LOGIN_SUCCESS: return state .set('loggedIn', true) + .set('isAdmin', action.isAdmin) .set('user', action.user); case actions.LOGOUT_SUCCESS: return state diff --git a/routes/api/auth/index.js b/routes/api/auth/index.js index 87ffb62c8..ae6fba01c 100644 --- a/routes/api/auth/index.js +++ b/routes/api/auth/index.js @@ -8,7 +8,7 @@ const router = express.Router(); * This returns the user if they are logged in. */ router.get('/', authorization.needed(), (req, res) => { - res.json(req.user); + res.json(req.user.toObject()); }); /** From 5b4b9c544ef86b58fb590889ed523a3a6d7f8366 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 22 Nov 2016 08:15:38 -0700 Subject: [PATCH 26/40] Removed .idea folder --- .idea/vcs.xml | 6 ------ 1 file changed, 6 deletions(-) delete mode 100644 .idea/vcs.xml diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 94a25f7f4..000000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file From 8eacb01484fbbcf8e2cd0847d6135b604ec0afaf Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 22 Nov 2016 09:31:54 -0700 Subject: [PATCH 27/40] Added fixes for prod --- app.js | 2 +- client/coral-admin/src/services/talk-adapter.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app.js b/app.js index 6512680aa..cff8f6294 100644 --- a/app.js +++ b/app.js @@ -21,7 +21,7 @@ if (app.get('env') !== 'test') { // APP MIDDLEWARE //============================================================================== -app.set('trust proxy', 'loopback'); +app.set('trust proxy', 1); app.use(helmet()); app.use(bodyParser.json()); app.use('/client', express.static(path.join(__dirname, 'dist'))); diff --git a/client/coral-admin/src/services/talk-adapter.js b/client/coral-admin/src/services/talk-adapter.js index 5345159e2..86878723e 100644 --- a/client/coral-admin/src/services/talk-adapter.js +++ b/client/coral-admin/src/services/talk-adapter.js @@ -52,7 +52,7 @@ Promise.all([ const updateComment = (store, comment) => { fetch(`/api/v1/comments/${comment.get('id')}/status`, { - method: 'POST', + method: 'PUT', headers: jsonHeader, body: JSON.stringify({status: comment.get('status')}) }) From 1eb2d041361510a386bc2b5ad0ec0862b9257871 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 22 Nov 2016 12:35:43 -0800 Subject: [PATCH 28/40] Icon was missing --- client/coral-admin/src/components/Comment.js | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-admin/src/components/Comment.js b/client/coral-admin/src/components/Comment.js index 80c67aebd..7cba85788 100644 --- a/client/coral-admin/src/components/Comment.js +++ b/client/coral-admin/src/components/Comment.js @@ -6,6 +6,7 @@ import I18n from 'coral-framework/modules/i18n/i18n'; import translations from '../translations.json'; import Linkify from 'react-linkify'; import {FabButton} from 'coral-ui'; +import {Icon} from 'react-mdl'; const linkify = new Linkify(); From d04812fd360bc953e6ac568bc83fc8f24762d2d6 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 22 Nov 2016 16:09:36 -0500 Subject: [PATCH 29/40] Adding https and accessability to embed code. --- client/coral-admin/src/containers/Configure.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-admin/src/containers/Configure.js b/client/coral-admin/src/containers/Configure.js index d5b4ce516..2058c66c8 100644 --- a/client/coral-admin/src/containers/Configure.js +++ b/client/coral-admin/src/containers/Configure.js @@ -100,7 +100,7 @@ class Configure extends React.Component { } getEmbed () { - const embedText = `
    `; + const embedText = `
    `; return From dea62d8895b361baace2c55a8ef4f958338236a4 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 22 Nov 2016 16:16:10 -0500 Subject: [PATCH 30/40] Fixing bug with reply author name. --- client/coral-embed-stream/src/CommentStream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 9887b46ac..2139df3d9 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -162,7 +162,7 @@ class CommentStream extends Component { let reply = this.props.items.comments[replyId]; return

    - +
    From 6e6ba89706ad07be942ad2d95b60476e380320b0 Mon Sep 17 00:00:00 2001 From: David Jay Date: Tue, 22 Nov 2016 16:51:22 -0500 Subject: [PATCH 31/40] Hiding reply box on post. --- client/coral-embed-stream/src/CommentStream.js | 17 +++++++++++++++-- client/coral-plugin-commentbox/CommentBox.js | 4 ++-- client/coral-plugin-replies/ReplyButton.js | 2 +- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 2139df3d9..a0eaca044 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -121,7 +121,8 @@ class CommentStream extends Component {
    + id={commentId} + showReply={comment.showReply}/> + id={replyId} + showReply={reply.showReply}/>
    +
    ; }) } diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 76f7e43be..79641d856 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -22,7 +22,7 @@ class CommentBox extends Component { } postComment = () => { - const {postItem, updateItem, id, parent_id, addNotification, appendItemArray, premod, author} = this.props; + const {postItem, updateItem, id, parent_id, child_id, addNotification, appendItemArray, premod, author} = this.props; let comment = { body: this.state.body, asset_id: id, @@ -38,7 +38,7 @@ class CommentBox extends Component { related = 'comments'; parent_type = 'assets'; } - updateItem(parent_id, 'showReply', false, 'comments'); + updateItem(child_id || parent_id, 'showReply', false, 'comments'); postItem(comment, 'comments') .then((comment_id) => { if (premod === 'pre') { diff --git a/client/coral-plugin-replies/ReplyButton.js b/client/coral-plugin-replies/ReplyButton.js index 4fbfd5f60..8e39af663 100644 --- a/client/coral-plugin-replies/ReplyButton.js +++ b/client/coral-plugin-replies/ReplyButton.js @@ -6,7 +6,7 @@ const name = 'coral-plugin-replies'; const ReplyButton = (props) =>