From b6d917a22b6855cb66a1f258419479af12bdeac6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 24 Jan 2017 16:08:13 -0700 Subject: [PATCH 01/11] fixed translation key, enforced moderation setting schema --- client/coral-plugin-commentbox/CommentBox.js | 2 +- graph/typeDefs.js | 7 ++++++- 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 3028103a7..f368953a7 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -120,7 +120,7 @@ class CommentBox extends Component { cStyle={!length || (charCount && length > charCount) ? 'lightGrey' : 'darkGrey'} className={`${name}-button`} onClick={this.postComment}> - {lang.t('POST')} + {lang.t('post')} ) } diff --git a/graph/typeDefs.js b/graph/typeDefs.js index c6804408a..0665a6132 100644 --- a/graph/typeDefs.js +++ b/graph/typeDefs.js @@ -97,8 +97,13 @@ type ActionSummary { current_user: Action } +enum MODERATION_MODE { + PRE + POST +} + type Settings { - moderation: String + moderation: MODERATION_MODE! infoBoxEnable: Boolean infoBoxContent: String closeTimeout: Int From 26b7d48f1fb3981babfa5be0501340e94a4616c8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 24 Jan 2017 17:06:40 -0700 Subject: [PATCH 02/11] Comment ordering fixed, banned users can't do anything --- client/coral-framework/helpers/validate.js | 2 +- graph/index.js | 4 ++ graph/loaders/comments.js | 48 +++++++++++++--------- graph/mutators/action.js | 2 +- graph/mutators/comment.js | 2 +- graph/mutators/user.js | 2 +- models/user.js | 27 ++++++++++++ 7 files changed, 64 insertions(+), 23 deletions(-) diff --git a/client/coral-framework/helpers/validate.js b/client/coral-framework/helpers/validate.js index 6e5db3662..b267fb7c0 100644 --- a/client/coral-framework/helpers/validate.js +++ b/client/coral-framework/helpers/validate.js @@ -1,5 +1,5 @@ export default { - email: email => (/^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)), + email: email => (/^([A-Za-z0-9_\-\.\+])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)), password: pass => (/^(?=.{8,}).*$/.test(pass)), confirmPassword: () => true, displayName: displayName => (/^[a-zA-Z0-9_]+$/.test(displayName)) diff --git a/graph/index.js b/graph/index.js index 7fddce2eb..f8695afbb 100644 --- a/graph/index.js +++ b/graph/index.js @@ -12,3 +12,7 @@ module.exports = { context: new Context(req) }) }; + +process.on('unhandledRejection', (err) => { + console.error(err); +}); diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 35463dfd0..77c5d172c 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -7,31 +7,41 @@ const CommentModel = require('../../models/comment'); const CommentsService = require('../../services/comments'); /** - * Retrieves comments by an array of asset id's. + * Retrieves comments by an array of asset id's, results are returned in reverse + * chronological order. * @param {Array} ids array of ids to lookup */ -const genCommentsByAssetID = (context, ids) => CommentModel.find({ - asset_id: { - $in: ids - }, - parent_id: null, - status: { - $in: [null, 'ACCEPTED'] - } -}).then(util.arrayJoinBy(ids, 'asset_id')); +const genCommentsByAssetID = (context, ids) => { + return CommentModel.find({ + asset_id: { + $in: ids + }, + parent_id: null, + status: { + $in: [null, 'ACCEPTED'] + } + }) + .sort({created_at: -1}) + .then(util.arrayJoinBy(ids, 'asset_id')); +}; /** - * Retrieves comments by an array of parent ids. + * Retrieves comments by an array of parent ids, results are returned in + * chronological order. * @param {Array} ids array of ids to lookup */ -const genCommentsByParentID = (context, ids) => CommentModel.find({ - parent_id: { - $in: ids - }, - status: { - $in: [null, 'ACCEPTED'] - } -}).then(util.arrayJoinBy(ids, 'parent_id')); +const genCommentsByParentID = (context, ids) => { + return CommentModel.find({ + parent_id: { + $in: ids + }, + status: { + $in: [null, 'ACCEPTED'] + } + }) + .sort({created_at: 1}) + .then(util.arrayJoinBy(ids, 'parent_id')); +}; const getCommentsByStatusAndAssetID = (context, {status = null, asset_id = null}) => { diff --git a/graph/mutators/action.js b/graph/mutators/action.js index cf80c496d..ab51c9a20 100644 --- a/graph/mutators/action.js +++ b/graph/mutators/action.js @@ -37,7 +37,7 @@ module.exports = (context) => { // TODO: refactor to something that'll return an error in the event an attempt // is made to mutate state while not logged in. There's got to be a better way // to do this. - if (context.user) { + if (context.user && context.user.can('mutation:createAction', 'mutation:deleteAction')) { return { Action: { create: (action) => createAction(context, action), diff --git a/graph/mutators/comment.js b/graph/mutators/comment.js index 6bd7cea26..cc62e48df 100644 --- a/graph/mutators/comment.js +++ b/graph/mutators/comment.js @@ -144,7 +144,7 @@ module.exports = (context) => { // TODO: refactor to something that'll return an error in the event an attempt // is made to mutate state while not logged in. There's got to be a better way // to do this. - if (context.user) { + if (context.user && context.user.can('mutation:createComment')) { return { Comment: { create: (comment) => createPublicComment(context, comment) diff --git a/graph/mutators/user.js b/graph/mutators/user.js index 22837caae..96049bbab 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -15,7 +15,7 @@ module.exports = (context) => { // TODO: refactor to something that'll return an error in the event an attempt // is made to mutate state while not logged in. There's got to be a better way // to do this. - if (context.user) { + if (context.user && context.user.can('mutation:updateUserSettings')) { return { User: { updateSettings: (settings) => updateUserSettings(context, settings) diff --git a/models/user.js b/models/user.js index 0b8edf0fd..9c447fa5d 100644 --- a/models/user.js +++ b/models/user.js @@ -130,6 +130,33 @@ UserSchema.method('hasRoles', function(...roles) { }); }); +/** + * All the graph operations that are available for a user. + * @type {Array} + */ +const USER_GRAPH_OPERATIONS = [ + 'mutation:createComment', + 'mutation:createAction', + 'mutation:deleteAction', + 'mutation:updateUserSettings' +]; + +/** + * Can returns true if the user is allowed to perform a specific graph + * operation. + */ +UserSchema.method('can', function(...actions) { + if (actions.some((action) => USER_GRAPH_OPERATIONS.indexOf(action) === -1)) { + throw new Error(`invalid actions: ${actions}`); + } + + if (this.status === 'BANNED') { + return false; + } + + return true; +}); + // Create the User model. const UserModel = mongoose.model('User', UserSchema); From eb469df573f222ed0ecba36f078d2ae00f8437ab Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 24 Jan 2017 17:08:12 -0700 Subject: [PATCH 03/11] Removed debugging tool --- graph/index.js | 4 ---- 1 file changed, 4 deletions(-) diff --git a/graph/index.js b/graph/index.js index f8695afbb..7fddce2eb 100644 --- a/graph/index.js +++ b/graph/index.js @@ -12,7 +12,3 @@ module.exports = { context: new Context(req) }) }; - -process.on('unhandledRejection', (err) => { - console.error(err); -}); From 373f119ac244faf817e48a40b0c9cef6a1b0be3a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 24 Jan 2017 17:22:29 -0700 Subject: [PATCH 04/11] Added more comment sorting --- graph/loaders/comments.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 77c5d172c..9a67bb621 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -62,7 +62,7 @@ const getCommentsByActionTypeAndAssetID = (context, {action_type, asset_id = nul id: { $in: actions.map((action) => action.item_id) } - }); + }).sort({created_at: 1}); if (asset_id) { comments = comments.where({asset_id}); @@ -72,11 +72,15 @@ const getCommentsByActionTypeAndAssetID = (context, {action_type, asset_id = nul }); }; -const genCommentsByAuthorID = (context, authorIDs) => CommentModel.find({ - author_id: { - $in: authorIDs - } -}).then(util.arrayJoinBy(authorIDs, 'author_id')); +const genCommentsByAuthorID = (context, authorIDs) => { + return CommentModel.find({ + author_id: { + $in: authorIDs + } + }) + .sort({created_at: -1}) + .then(util.arrayJoinBy(authorIDs, 'author_id')); +}; /** * Creates a set of loaders based on a GraphQL context. From ad9684acd9d4a7a6f95266b07e945aa543fbbb2e Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 25 Jan 2017 14:08:19 -0500 Subject: [PATCH 05/11] Create load stream by id route --- .../src/graphql/queries/index.js | 15 ++++++-- routes/assets/index.js | 31 +++++++++++++++++ routes/index.js | 8 +---- views/article.ejs | 34 +++++++++++++------ 4 files changed, 67 insertions(+), 21 deletions(-) create mode 100644 routes/assets/index.js diff --git a/client/coral-embed-stream/src/graphql/queries/index.js b/client/coral-embed-stream/src/graphql/queries/index.js index ae01ea9f4..221d78c4d 100644 --- a/client/coral-embed-stream/src/graphql/queries/index.js +++ b/client/coral-embed-stream/src/graphql/queries/index.js @@ -1,9 +1,18 @@ import {graphql} from 'react-apollo'; import STREAM_QUERY from './streamQuery.graphql'; -import pym from 'coral-framework/PymConnection'; -let url = pym.parentUrl.split('#')[0] || 'http://localhost:3000/'; +function getQueryVariable(variable) { + let query = window.location.search.substring(1); + let vars = query.split('&'); + for (let i = 0; i < vars.length; i++) { + let pair = vars[i].split('='); + if (decodeURIComponent(pair[0]) === variable) { + return decodeURIComponent(pair[1]); + } + } + console.log('Query variable %s not found', variable); +} export const queryStream = graphql(STREAM_QUERY, { - options: {variables: {asset_url: url}} + options: {variables: {asset_url: getQueryVariable('asset_url')}} }); diff --git a/routes/assets/index.js b/routes/assets/index.js new file mode 100644 index 000000000..46ad378e5 --- /dev/null +++ b/routes/assets/index.js @@ -0,0 +1,31 @@ +const express = require('express'); +const router = express.Router(); + +const Assets = require('../../services/assets'); + +const body = 'Lorem ipsum dolor sponge amet, consectetur adipiscing clam. Ut lobortis sollicitudin pillar a ornare. Curabitur dignissim vestibulum cay non rhoncus. Cras laoreet ante vel nunc hendrerit, shelf imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. Talk volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, sed vehicula mauris velit non lectus. Integer non trust nec neque congue faucibus porttitor sit amet elkhorn.'; + +router.get('/id/:asset_id', (req, res, next) => { + + return Assets.findById(req.params.asset_id) + .then(asset => { + res.render('article', { + title: asset.title, + asset_url: asset.url, + body: '', + basePath: '/client/embed/stream' + }); + }) + .catch((err) => next(err)); +}); + +router.get('/title/:asset_title', (req, res) => { + return res.render('article', { + title: req.params.asset_title.split('-').join(' '), + asset_url: '', + body: body, + basePath: '/client/embed/stream' + }); +}); + +module.exports = router; diff --git a/routes/index.js b/routes/index.js index 9ab0dff14..6684f5676 100644 --- a/routes/index.js +++ b/routes/index.js @@ -4,6 +4,7 @@ const router = express.Router(); router.use('/api/v1', require('./api')); router.use('/admin', require('./admin')); router.use('/embed', require('./embed')); +router.use('/assets', require('./assets')); router.get('/', (req, res) => { return res.render('article', { @@ -12,11 +13,4 @@ router.get('/', (req, res) => { }); }); -router.get('/assets/:asset_title', (req, res) => { - return res.render('article', { - title: req.params.asset_title.split('-').join(' '), - basePath: '/client/embed/stream' - }); -}); - module.exports = router; diff --git a/views/article.ejs b/views/article.ejs index c63acc62b..f5f56420e 100644 --- a/views/article.ejs +++ b/views/article.ejs @@ -20,15 +20,7 @@

<%= title %>

-

- Lorem ipsum dolor sponge amet, consectetur adipiscing clam. - Ut lobortis sollicitudin pillar a ornare. Curabitur dignissim - vestibulum cay non rhoncus. Cras laoreet ante vel nunc hendrerit, - shelf imperdiet neque egestas. Suspendisse aliquet iaculis fermentum. - Talk volutpat, tellus posuere laoreet consequat, mi lacus laoreet massa, - sed vehicula mauris velit non lectus. Integer non trust nec neque congue - faucibus porttitor sit amet elkhorn. -

+

<%= body %>

Visit the moderation console

@@ -36,13 +28,33 @@