From 17849898aeec8f0cc4cc2ac7f3ac7bbcf770106d Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 18 Jan 2017 00:09:26 -0700 Subject: [PATCH 001/107] First pass --- app.js | 2 + package.json | 4 ++ routes/api/graph/index.js | 26 ++++++++++ routes/api/graph/loaders.js | 97 +++++++++++++++++++++++++++++++++++ routes/api/graph/mutators.js | 65 +++++++++++++++++++++++ routes/api/graph/resolvers.js | 52 +++++++++++++++++++ routes/api/graph/typeDefs.js | 66 ++++++++++++++++++++++++ 7 files changed, 312 insertions(+) create mode 100644 routes/api/graph/index.js create mode 100644 routes/api/graph/loaders.js create mode 100644 routes/api/graph/mutators.js create mode 100644 routes/api/graph/resolvers.js create mode 100644 routes/api/graph/typeDefs.js diff --git a/app.js b/app.js index 9cdad4ea3..acc262aad 100644 --- a/app.js +++ b/app.js @@ -77,6 +77,8 @@ app.use(session(session_opts)); app.use(passport.initialize()); app.use(passport.session()); +app.use('/api/v1/graph', require('./routes/api/graph')); + //============================================================================== // CSRF MIDDLEWARE //============================================================================== diff --git a/package.json b/package.json index e6e211d07..85712bf5a 100644 --- a/package.json +++ b/package.json @@ -54,12 +54,16 @@ "commander": "^2.9.0", "connect-redis": "^3.1.0", "csurf": "^1.9.0", + "dataloader": "^1.2.0", "debug": "^2.2.0", "dotenv": "^4.0.0", "ejs": "^2.5.2", "env-rewrite": "^1.0.2", "express": "^4.14.0", "express-session": "^1.14.2", + "graphql": "^0.8.2", + "graphql-server-express": "^0.5.0", + "graphql-tools": "^0.9.0", "helmet": "^3.1.0", "jsonwebtoken": "^7.1.9", "kue": "^0.11.5", diff --git a/routes/api/graph/index.js b/routes/api/graph/index.js new file mode 100644 index 000000000..e97cfcadb --- /dev/null +++ b/routes/api/graph/index.js @@ -0,0 +1,26 @@ +const express = require('express'); +const apollo = require('graphql-server-express'); +const tools = require('graphql-tools'); +const resolvers = require('./resolvers'); +const typeDefs = require('./typeDefs'); +const loaders = require('./loaders'); +const mutators = require('./mutators'); + +const schema = tools.makeExecutableSchema({typeDefs, resolvers}); +const router = express.Router(); + +router.use('/ql', apollo.graphqlExpress((req) => { + + let context = {req}; + + context.loaders = loaders(context); + context.mutators = mutators(context); + + return { + schema, + context + }; +})); +router.use('/iql', apollo.graphiqlExpress({endpointURL: '/api/v1/graph/ql'})); + +module.exports = router; diff --git a/routes/api/graph/loaders.js b/routes/api/graph/loaders.js new file mode 100644 index 000000000..2d7ed4f69 --- /dev/null +++ b/routes/api/graph/loaders.js @@ -0,0 +1,97 @@ +const DataLoader = require('dataloader'); +const _ = require('lodash'); + +const Comment = require('../../../models/comment'); +const User = require('../../../models/user'); +const Action = require('../../../models/action'); +const Asset = require('../../../models/asset'); +const Settings = require('../../../models/setting'); + +class SingletonResolver { + constructor(resolver) { + this._cache = null; + this._resolver = resolver; + } + + load() { + if (this._cache) { + return this._cache; + } + + let promise = this._resolver(arguments).then((result) => { + return result; + }); + + // Set the promise on the cache. + this._cache = promise; + + return promise; + } +} + +const arrayJoinBy = (ids, key) => (items) => { + const itemsByKey = _.groupBy(items, key); + return ids.map((id) => { + if (id in itemsByKey) { + return itemsByKey[id]; + } + + return []; + }); +}; + +const singleJoinBy = (ids, key) => (items) => { + const itemsByKey = _.groupBy(items, key); + return ids.map((id) => { + if (id in itemsByKey) { + return itemsByKey[id][0]; + } + + return null; + }); +}; + +const genAssetByID = (ids) => Asset.find({ + id: { + $in: ids + } +}).then(singleJoinBy(ids, 'id')); + +const genActionsByID = (ids, user = {}) => Action.getActionSummaries(ids, user.id).then(arrayJoinBy(ids, 'item_id')); + +const genCommentsByAssetID = (ids) => Comment.find({ + asset_id: { + $in: ids + }, + parent_id: null, + status: { + $in: [null, 'accepted'] + } +}).then(arrayJoinBy(ids, 'asset_id')); + +const genCommentsByParentID = (ids) => Comment.find({ + parent_id: { + $in: ids + }, + status: { + $in: [null, 'accepted'] + } +}).then(arrayJoinBy(ids, 'parent_id')); + +module.exports = (context) => ({ + Comments: { + getByParentID: new DataLoader((ids) => genCommentsByParentID(ids)), + getByAssetID: new DataLoader((ids) => genCommentsByAssetID(ids)), + }, + Actions: { + getByID: new DataLoader((ids) => genActionsByID(ids, context.req.user)), + }, + Users: { + getByID: new DataLoader((ids) => User.findByIdArray(ids)) + }, + Assets: { + getByID: new DataLoader((ids) => genAssetByID(ids)), + getAll: new SingletonResolver(() => Asset.find({})) + }, + Settings: new SingletonResolver(() => Settings.retrieve()) +}); diff --git a/routes/api/graph/mutators.js b/routes/api/graph/mutators.js new file mode 100644 index 000000000..157d715db --- /dev/null +++ b/routes/api/graph/mutators.js @@ -0,0 +1,65 @@ +const errors = require('../../../errors'); + +const Asset = require('../../../models/asset'); +const Comment = require('../../../models/comment'); + +const createComment = (context, {body, asset_id, parent_id}, wordlist = {}) => { + + // 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; + + if (wordlist.banned) { + status = Promise.resolve('rejected'); + } else { + status = Asset + .rectifySettings(Asset.findById(asset_id).then((asset) => { + if (!asset) { + return Promise.reject(errors.ErrNotFound); + } + + // Check to see if the asset has closed commenting... + if (asset.isClosed) { + + // They have, ensure that we send back an error. + return Promise.reject(new errors.ErrAssetCommentingClosed(asset.closedMessage)); + } + + return asset; + })) + + // 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, charCountEnable, charCount}) => { + + // Reject if the comment is too long + if (charCountEnable && body.length > charCount) { + return 'rejected'; + } + return moderation === 'pre' ? 'premod' : null; + }); + } + + return status.then((status) => Comment.publicCreate({ + body, + asset_id, + parent_id, + status, + author_id: context.req.user.id + })) + .then((comment) => { + if (wordlist.suspect) { + return Comment + .addAction(comment.id, null, 'flag', {field: 'body', details: 'Matched suspect word filters.'}) + .then(() => comment); + } + + return comment; + }); +}; + +module.exports = (context) => ({ + createComment: (comment) => createComment(context, comment) +}); diff --git a/routes/api/graph/resolvers.js b/routes/api/graph/resolvers.js new file mode 100644 index 000000000..4c53eb366 --- /dev/null +++ b/routes/api/graph/resolvers.js @@ -0,0 +1,52 @@ +module.exports = { + Query: { + assets(_, args, {loaders}) { + return loaders.Assets.getAll.load(); + }, + asset(_, {id}, {loaders}) { + return loaders.Assets.getByID.load(id); + }, + settings(_, args, {loaders}) { + return loaders.Settings.load(); + } + }, + Mutation: { + createComment(_, {asset_id, parent_id, body}, {mutators}) { + return mutators.createComment({asset_id, parent_id, body}); + } + }, + Asset: { + comments({id}, _, {loaders}) { + return loaders.Comments.getByAssetID.load(id); + }, + settings({settings = null}, _, {loaders}) { + return loaders.Settings.load() + .then((globalSettings) => { + + if (settings) { + settings = Object.assign({}, settings, globalSettings); + } else { + settings = globalSettings; + } + + return settings; + }); + } + }, + User: { + actions({id}, _, {loaders}) { + return loaders.Actions.getByID.load(id); + } + }, + Comment: { + user({author_id}, _, {loaders}) { + return loaders.Users.getByID.load(author_id); + }, + replies({id}, _, {loaders}) { + return loaders.Comments.getByParentID.load(id); + }, + actions({id}, _, {loaders}) { + return loaders.Actions.getByID.load(id); + } + } +}; diff --git a/routes/api/graph/typeDefs.js b/routes/api/graph/typeDefs.js new file mode 100644 index 000000000..ba4a5c7f1 --- /dev/null +++ b/routes/api/graph/typeDefs.js @@ -0,0 +1,66 @@ +const typeDefs = [` +type UserSettings { + bio: String +} + +type User { + id: ID! + displayName: String! + actions: [Action] + settings: UserSettings +} + +type Comment { + id: ID! + body: String! + user: User + replies(limit: Int = 3): [Comment] + actions: [Action] +} + +type Action { + id: ID! + item_id: ID! + action_type: String! + count: Int + current_user: Action + updated_at: String + created_at: String +} + +type Settings { + moderation: String + infoBoxEnable: Boolean + infoBoxContent: String + closeTimeout: Int + closedMessage: String + charCountEnable: Boolean + charCount: Int + requireEmailConfirmation: Boolean +} + +type Asset { + id: ID! + title: String + url: String + comments: [Comment] + settings: Settings! +} + +type Query { + settings: Settings + assets: [Asset] + asset(id: ID!): Asset! +} + +type Mutation { + createComment(asset_id: ID!, parent_id: ID, body: String!): Comment +} + +schema { + query: Query + mutation: Mutation +} +`]; + +module.exports = typeDefs; From 52ed18a9b8fb37ea6233a1e5e616033dd322a8e9 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 18 Jan 2017 11:23:47 -0700 Subject: [PATCH 002/107] Added comments --- routes/api/graph/index.js | 9 ++++---- routes/api/graph/loaders.js | 42 +++++++++++++++++++++++++++++++++++- routes/api/graph/mutators.js | 32 +++++++++++++++++++++++++-- routes/api/graph/schema.js | 8 +++++++ routes/api/graph/typeDefs.js | 2 +- 5 files changed, 85 insertions(+), 8 deletions(-) create mode 100644 routes/api/graph/schema.js diff --git a/routes/api/graph/index.js b/routes/api/graph/index.js index e97cfcadb..1e737fd53 100644 --- a/routes/api/graph/index.js +++ b/routes/api/graph/index.js @@ -1,14 +1,13 @@ const express = require('express'); const apollo = require('graphql-server-express'); -const tools = require('graphql-tools'); -const resolvers = require('./resolvers'); -const typeDefs = require('./typeDefs'); + const loaders = require('./loaders'); const mutators = require('./mutators'); +const schema = require('./schema'); -const schema = tools.makeExecutableSchema({typeDefs, resolvers}); const router = express.Router(); +// GraphQL endpoint. router.use('/ql', apollo.graphqlExpress((req) => { let context = {req}; @@ -21,6 +20,8 @@ router.use('/ql', apollo.graphqlExpress((req) => { context }; })); + +// Interactive graphiql interface. router.use('/iql', apollo.graphiqlExpress({endpointURL: '/api/v1/graph/ql'})); module.exports = router; diff --git a/routes/api/graph/loaders.js b/routes/api/graph/loaders.js index 2d7ed4f69..d6534450c 100644 --- a/routes/api/graph/loaders.js +++ b/routes/api/graph/loaders.js @@ -7,6 +7,9 @@ const Action = require('../../../models/action'); const Asset = require('../../../models/asset'); const Settings = require('../../../models/setting'); +/** + * SingletonResolver is a cached loader for a single result. + */ class SingletonResolver { constructor(resolver) { this._cache = null; @@ -29,6 +32,13 @@ class SingletonResolver { } } +/** + * This joins a set of results with a specific keys and sets an empty array in + * place if it was not found. + * @param {Array} ids ids to locate + * @param {String} key key to group by + * @return {Array} array of results + */ const arrayJoinBy = (ids, key) => (items) => { const itemsByKey = _.groupBy(items, key); return ids.map((id) => { @@ -40,6 +50,13 @@ const arrayJoinBy = (ids, key) => (items) => { }); }; +/** + * This joins a set of results with a specific keys and sets null in place if it + * was not found. + * @param {Array} ids ids to locate + * @param {String} key key to group by + * @return {Array} array of results + */ const singleJoinBy = (ids, key) => (items) => { const itemsByKey = _.groupBy(items, key); return ids.map((id) => { @@ -51,14 +68,26 @@ const singleJoinBy = (ids, key) => (items) => { }); }; +/** + * Retrieves assets by an array of ids. + * @param {Array} ids array of ids to lookup + */ const genAssetByID = (ids) => Asset.find({ id: { $in: ids } }).then(singleJoinBy(ids, 'id')); +/** + * Retrieves actions by an array of ids. + * @param {Array} ids array of ids to lookup + */ const genActionsByID = (ids, user = {}) => Action.getActionSummaries(ids, user.id).then(arrayJoinBy(ids, 'item_id')); +/** + * Retrieves comments by an array of asset id's. + * @param {Array} ids array of ids to lookup + */ const genCommentsByAssetID = (ids) => Comment.find({ asset_id: { $in: ids @@ -69,6 +98,10 @@ const genCommentsByAssetID = (ids) => Comment.find({ } }).then(arrayJoinBy(ids, 'asset_id')); +/** + * Retrieves comments by an array of parent ids. + * @param {Array} ids array of ids to lookup + */ const genCommentsByParentID = (ids) => Comment.find({ parent_id: { $in: ids @@ -78,7 +111,12 @@ const genCommentsByParentID = (ids) => Comment.find({ } }).then(arrayJoinBy(ids, 'parent_id')); -module.exports = (context) => ({ +/** + * Creates a set of loaders based on a GraphQL context. + * @param {Object} context the context of the GraphQL request + * @return {Object} object of loaders + */ +const createLoaders = (context) => ({ Comments: { getByParentID: new DataLoader((ids) => genCommentsByParentID(ids)), getByAssetID: new DataLoader((ids) => genCommentsByAssetID(ids)), @@ -95,3 +133,5 @@ module.exports = (context) => ({ }, Settings: new SingletonResolver(() => Settings.retrieve()) }); + +module.exports = createLoaders; diff --git a/routes/api/graph/mutators.js b/routes/api/graph/mutators.js index 157d715db..3d97cc1bd 100644 --- a/routes/api/graph/mutators.js +++ b/routes/api/graph/mutators.js @@ -3,7 +3,18 @@ const errors = require('../../../errors'); const Asset = require('../../../models/asset'); const Comment = require('../../../models/comment'); -const createComment = (context, {body, asset_id, parent_id}, wordlist = {}) => { +const Wordlist = require('../../../services/wordlist'); + +/** + * Creates a new comment. + * @param {Object} context a GraphQL context + * @param {String} body body of the comment + * @param {String} asset_id asset for the comment + * @param {String} parent_id optional parent of the comment + * @param {Object} [wordlist={}] results for the wordlist analysis + * @return {Promise} resolves to the created comment + */ +const createComment = (context, {body, asset_id, parent_id = null}, wordlist = {}) => { // 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 @@ -60,6 +71,23 @@ const createComment = (context, {body, asset_id, parent_id}, wordlist = {}) => { }); }; +/** + * Filters the comment and outputs the wordlist results. + * @param {[type]} context [description] + * @param {[type]} comment [description] + * @return {[type]} [description] + */ +const filterNewComment = (context, comment) => { + + // Create a new instance of the Wordlist. + const wl = new Wordlist(); + + // Load the wordlist and filter the comment content. + return wl.load().then(() => wl.filter(comment, 'body')); +}; + module.exports = (context) => ({ - createComment: (comment) => createComment(context, comment) + createComment: (comment) => filterNewComment(context, comment).then((wordlist) => { + return createComment(context, comment, wordlist); + }) }); diff --git a/routes/api/graph/schema.js b/routes/api/graph/schema.js new file mode 100644 index 000000000..b4b42b809 --- /dev/null +++ b/routes/api/graph/schema.js @@ -0,0 +1,8 @@ +const tools = require('graphql-tools'); + +const resolvers = require('./resolvers'); +const typeDefs = require('./typeDefs'); + +const schema = tools.makeExecutableSchema({typeDefs, resolvers}); + +module.exports = schema; diff --git a/routes/api/graph/typeDefs.js b/routes/api/graph/typeDefs.js index ba4a5c7f1..51e74b5d3 100644 --- a/routes/api/graph/typeDefs.js +++ b/routes/api/graph/typeDefs.js @@ -50,7 +50,7 @@ type Asset { type Query { settings: Settings assets: [Asset] - asset(id: ID!): Asset! + asset(id: ID!): Asset } type Mutation { From 5b30fd89c8b077210c9b879a9a25a3ded3cdfe23 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 18 Jan 2017 11:28:39 -0700 Subject: [PATCH 003/107] More comments --- routes/api/graph/mutators.js | 12 ++++----- services/wordlist.js | 52 +++++++++++++++++++++++++++++------- 2 files changed, 48 insertions(+), 16 deletions(-) diff --git a/routes/api/graph/mutators.js b/routes/api/graph/mutators.js index 3d97cc1bd..2a9f9bc64 100644 --- a/routes/api/graph/mutators.js +++ b/routes/api/graph/mutators.js @@ -72,18 +72,18 @@ const createComment = (context, {body, asset_id, parent_id = null}, wordlist = { }; /** - * Filters the comment and outputs the wordlist results. - * @param {[type]} context [description] - * @param {[type]} comment [description] - * @return {[type]} [description] + * Filters the comment object and outputs wordlist results. + * @param {Object} context graphql context + * @param {String} body body of a comment + * @return {Object} resolves to the wordlist results */ -const filterNewComment = (context, comment) => { +const filterNewComment = (context, {body}) => { // Create a new instance of the Wordlist. const wl = new Wordlist(); // Load the wordlist and filter the comment content. - return wl.load().then(() => wl.filter(comment, 'body')); + return wl.load().then(() => wl.scan('body', body)); }; module.exports = (context) => ({ diff --git a/services/wordlist.js b/services/wordlist.js index e912abc43..6cb8449a6 100644 --- a/services/wordlist.js +++ b/services/wordlist.js @@ -118,6 +118,42 @@ class Wordlist { }); } + /** + * Scans a specific field for wordlist violations. + */ + scan(fieldName, phrase) { + let errors = {}; + + // 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 errors; + } + + // Check if the field contains a banned word. + if (this.match(this.lists.banned, phrase)) { + debug(`the field "${fieldName}" contained a phrase "${phrase}" which contained a banned word/phrase`); + + errors.banned = Errors.ErrContainsProfanity; + + // Stop looping through the fields now, we discovered the worst possible + // situation (a banned word). + return errors; + } + + // Check if the field contains a banned word. + if (this.match(this.lists.suspect, phrase)) { + debug(`the field "${fieldName}" contained a phrase "${phrase}" which contained a suspected word/phrase`); + + errors.suspect = Errors.ErrContainsProfanity; + + // Continue looping through the fields now, we discovered a possible bad + // word (suspect). + return errors; + } + } + /** * Perform the filtering based on the loaded wordlists. */ @@ -129,9 +165,9 @@ class Wordlist { // Loop over all the fields from the body that we want to check. for (let i = 0; i < fields.length; i++) { - let field = fields[i]; + let fieldName = fields[i]; - let phrase = _.get(body, field, false); + let phrase = _.get(body, fieldName, false); // If the field doesn't exist in the body, then it can't be profane! if (!phrase) { @@ -140,11 +176,10 @@ class Wordlist { continue; } - // Check if the field contains a banned word. - if (this.match(this.lists.banned, phrase)) { - debug(`the field "${field}" contained a phrase "${phrase}" which contained a banned word/phrase`); + errors = Object.assign(errors, this.scan(fieldName, phrase)); - errors.banned = Errors.ErrContainsProfanity; + // Check if the field contains a banned word. + if (errors.banned) { // Stop looping through the fields now, we discovered the worst possible // situation (a banned word). @@ -152,10 +187,7 @@ class Wordlist { } // Check if the field contains a banned word. - if (this.match(this.lists.suspect, phrase)) { - debug(`the field "${field}" contained a phrase "${phrase}" which contained a suspected word/phrase`); - - errors.suspect = Errors.ErrContainsProfanity; + if (errors.suspect) { // Continue looping through the fields now, we discovered a possible bad // word (suspect). From e36ecdfb33b20736850de17978782e558c0afb74 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 18 Jan 2017 15:58:36 -0500 Subject: [PATCH 004/107] Implement stream with data fetch --- client/coral-embed-stream/src/index.js | 18 +++++--- client/coral-plugin-stream/Stream.js | 57 ++++++++++++++++++++++++++ package.json | 3 ++ 3 files changed, 72 insertions(+), 6 deletions(-) create mode 100644 client/coral-plugin-stream/Stream.js diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index de14edbc7..c8982ffc5 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -1,11 +1,17 @@ import React from 'react'; import {render} from 'react-dom'; -import CommentStream from './CommentStream'; -import {Provider} from 'react-redux'; -import {store} from '../../coral-framework'; +import ApolloClient, {createNetworkInterface} from 'apollo-client'; +import {ApolloProvider} from 'react-apollo'; + +import Stream from '../../coral-plugin-stream/Stream'; + +const client = new ApolloClient({ + networkInterface: createNetworkInterface({uri: '/api/v1/graph/ql'}) +}); render( - - - + + + + , document.querySelector('#coralStream')); diff --git a/client/coral-plugin-stream/Stream.js b/client/coral-plugin-stream/Stream.js new file mode 100644 index 000000000..2b87c2a2c --- /dev/null +++ b/client/coral-plugin-stream/Stream.js @@ -0,0 +1,57 @@ +import React, {Component} from 'react'; +import {graphql} from 'react-apollo'; +import gql from 'graphql-tag'; + +const assetID = 'c82f9fbb-5cf6-4eeb-bde5-25bae78227d2'; + +// MyComponent is a "presentational" or apollo-unaware component, +// It could be a simple React class: +class Stream extends Component { + + constructor(props) { + super(props); + } + + componentWillUpdate() { + console.log(this.props); + } + + render() { + return
...
; + } +} + +// Initialize GraphQL queries or mutations with the `gql` tag +const StreamQuery = gql`fragment commentView on Comment { + body + user { + name: displayName + } + actions { + type: action_type + count + current: current_user { + id + created_at + } + } +} + +query AssetQuery($asset_id: ID!) { + asset(id: $asset_id) { + title + url + comments { + ...commentView + replies { + ...commentView + } + } + } +}`; + +// We then can use `graphql` to pass the query results returned by MyQuery +// to MyComponent as a prop (and update them as the results change) +const StreamWithData = graphql(StreamQuery, {options: {variables: {asset_id: assetID}}})(Stream); + +export default StreamWithData; diff --git a/package.json b/package.json index 85712bf5a..94cc8c504 100644 --- a/package.json +++ b/package.json @@ -48,6 +48,7 @@ }, "homepage": "https://github.com/coralproject/talk#readme", "dependencies": { + "apollo-client": "^0.7.3", "bcrypt": "^0.8.7", "body-parser": "^1.15.2", "cli-table": "^0.3.1", @@ -63,6 +64,7 @@ "express-session": "^1.14.2", "graphql": "^0.8.2", "graphql-server-express": "^0.5.0", + "graphql-tag": "^1.2.3", "graphql-tools": "^0.9.0", "helmet": "^3.1.0", "jsonwebtoken": "^7.1.9", @@ -78,6 +80,7 @@ "passport-facebook": "^2.1.1", "passport-local": "^1.0.0", "prompt": "^1.0.0", + "react-apollo": "^0.8.1", "redis": "^2.6.3", "uuid": "^2.0.3" }, From 347fe8bf2b8551fedd5cc3d827c0d1e34ed37562 Mon Sep 17 00:00:00 2001 From: David Erwin Date: Wed, 18 Jan 2017 16:15:40 -0500 Subject: [PATCH 005/107] Showing graphql data in console --- client/coral-plugin-stream/Stream.js | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/client/coral-plugin-stream/Stream.js b/client/coral-plugin-stream/Stream.js index 2b87c2a2c..14187c01c 100644 --- a/client/coral-plugin-stream/Stream.js +++ b/client/coral-plugin-stream/Stream.js @@ -2,7 +2,7 @@ import React, {Component} from 'react'; import {graphql} from 'react-apollo'; import gql from 'graphql-tag'; -const assetID = 'c82f9fbb-5cf6-4eeb-bde5-25bae78227d2'; +const assetID = ''; // MyComponent is a "presentational" or apollo-unaware component, // It could be a simple React class: @@ -12,11 +12,8 @@ class Stream extends Component { super(props); } - componentWillUpdate() { - console.log(this.props); - } - render() { + console.log(this.props); return
...
; } } From 89a188255f335ea56a62109bcece48928a08b370 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 18 Jan 2017 15:23:07 -0700 Subject: [PATCH 006/107] super amazing code. --- client/coral-embed-stream/src/index.js | 7 ++- .../RileysAwesomeCommentBox.js | 58 +++++++++++++++++++ client/coral-plugin-stream/Stream.js | 50 +++++++++++++++- 3 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 client/coral-plugin-stream/RileysAwesomeCommentBox.js diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index c8982ffc5..31185457c 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -6,7 +6,12 @@ import {ApolloProvider} from 'react-apollo'; import Stream from '../../coral-plugin-stream/Stream'; const client = new ApolloClient({ - networkInterface: createNetworkInterface({uri: '/api/v1/graph/ql'}) + networkInterface: createNetworkInterface({ + uri: '/api/v1/graph/ql', + opts: { + credentials: 'same-origin' + } + }) }); render( diff --git a/client/coral-plugin-stream/RileysAwesomeCommentBox.js b/client/coral-plugin-stream/RileysAwesomeCommentBox.js new file mode 100644 index 000000000..6ccd028ba --- /dev/null +++ b/client/coral-plugin-stream/RileysAwesomeCommentBox.js @@ -0,0 +1,58 @@ +import React, {Component} from 'react'; +import {graphql} from 'react-apollo'; +import gql from 'graphql-tag'; + +export class RileysAwesomeCommentBox extends Component { + + postComment() { + console.log(this.props); + console.log('postComment', this.props.asset_id); + this.props.mutate({ + variables: { + asset_id: this.props.asset_id, + body: this.textarea.value, + parent_id: null + } + }).then(({data}) => { + console.log('it workt'); + console.log(data); + }); + } + + render() { + return
+ + +
; + } +} + +const postComment = gql` + fragment commentView on Comment { + id + body + user { + name: displayName + } + actions { + type: action_type + count + current: current_user { + id + created_at + } + } + } + + mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { + createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { + ...commentView + } + } +`; + +const RileysAwesomeCommentBoxWithData = graphql( + postComment +)(RileysAwesomeCommentBox); + +export default RileysAwesomeCommentBoxWithData; diff --git a/client/coral-plugin-stream/Stream.js b/client/coral-plugin-stream/Stream.js index 14187c01c..79a3a6550 100644 --- a/client/coral-plugin-stream/Stream.js +++ b/client/coral-plugin-stream/Stream.js @@ -1,8 +1,10 @@ import React, {Component} from 'react'; import {graphql} from 'react-apollo'; import gql from 'graphql-tag'; +import {fetchSignIn} from 'coral-framework/actions/auth'; +import RileysAwesomeCommentBox from 'coral-plugin-stream/RileysAwesomeCommentBox'; -const assetID = ''; +const assetID = '6187a94b-0b6d-4a96-ac6b-62b529cd8410'; // MyComponent is a "presentational" or apollo-unaware component, // It could be a simple React class: @@ -12,14 +14,46 @@ class Stream extends Component { super(props); } + logMeIn() { + fetchSignIn({email: 'riley.davis@gmail.com', password: 'film2255'})(() => {}); + } + render() { console.log(this.props); - return
...
; + const {data} = this.props; + return
+ + { + data.loading + ? 'loading!' + :
+ +

Asset ID: {data.asset.id}

+
    + { + data.asset.comments.map(comment => { + return
  • + {comment.body} [{comment.id}] +
      + { + comment.replies.map(reply => { + return
    • {reply.body}
    • ; + }) + } +
    +
  • ; + }) + } +
+
+ } +
; } } // Initialize GraphQL queries or mutations with the `gql` tag const StreamQuery = gql`fragment commentView on Comment { + id body user { name: displayName @@ -36,6 +70,7 @@ const StreamQuery = gql`fragment commentView on Comment { query AssetQuery($asset_id: ID!) { asset(id: $asset_id) { + id title url comments { @@ -47,8 +82,17 @@ query AssetQuery($asset_id: ID!) { } }`; + // We then can use `graphql` to pass the query results returned by MyQuery // to MyComponent as a prop (and update them as the results change) -const StreamWithData = graphql(StreamQuery, {options: {variables: {asset_id: assetID}}})(Stream); +const StreamWithData = graphql( + StreamQuery, { + options: { + variables: { + asset_id: assetID + } + } + } +)(Stream); export default StreamWithData; From 58e7abbe0c0f20000e994342bfeea4d6fbc6093c Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 18 Jan 2017 15:25:48 -0700 Subject: [PATCH 007/107] sdfasdf --- client/coral-plugin-stream/Stream.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-plugin-stream/Stream.js b/client/coral-plugin-stream/Stream.js index 79a3a6550..21178d1aa 100644 --- a/client/coral-plugin-stream/Stream.js +++ b/client/coral-plugin-stream/Stream.js @@ -15,7 +15,7 @@ class Stream extends Component { } logMeIn() { - fetchSignIn({email: 'riley.davis@gmail.com', password: 'film2255'})(() => {}); + fetchSignIn({email: 'your@example.com', password: 'dfasidfaisdufoiausdfoiuaspdoifas'})(() => {}); } render() { From bbfd017998631a74625fa3a4308de57ee63daa15 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Wed, 18 Jan 2017 15:27:48 -0700 Subject: [PATCH 008/107] adding changes --- client/coral-embed-stream/src/index.js | 7 ++- .../RileysAwesomeCommentBox.js | 58 +++++++++++++++++++ client/coral-plugin-stream/Stream.js | 50 +++++++++++++++- 3 files changed, 111 insertions(+), 4 deletions(-) create mode 100644 client/coral-plugin-stream/RileysAwesomeCommentBox.js diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index c8982ffc5..31185457c 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -6,7 +6,12 @@ import {ApolloProvider} from 'react-apollo'; import Stream from '../../coral-plugin-stream/Stream'; const client = new ApolloClient({ - networkInterface: createNetworkInterface({uri: '/api/v1/graph/ql'}) + networkInterface: createNetworkInterface({ + uri: '/api/v1/graph/ql', + opts: { + credentials: 'same-origin' + } + }) }); render( diff --git a/client/coral-plugin-stream/RileysAwesomeCommentBox.js b/client/coral-plugin-stream/RileysAwesomeCommentBox.js new file mode 100644 index 000000000..6ccd028ba --- /dev/null +++ b/client/coral-plugin-stream/RileysAwesomeCommentBox.js @@ -0,0 +1,58 @@ +import React, {Component} from 'react'; +import {graphql} from 'react-apollo'; +import gql from 'graphql-tag'; + +export class RileysAwesomeCommentBox extends Component { + + postComment() { + console.log(this.props); + console.log('postComment', this.props.asset_id); + this.props.mutate({ + variables: { + asset_id: this.props.asset_id, + body: this.textarea.value, + parent_id: null + } + }).then(({data}) => { + console.log('it workt'); + console.log(data); + }); + } + + render() { + return
+ + +
; + } +} + +const postComment = gql` + fragment commentView on Comment { + id + body + user { + name: displayName + } + actions { + type: action_type + count + current: current_user { + id + created_at + } + } + } + + mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { + createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { + ...commentView + } + } +`; + +const RileysAwesomeCommentBoxWithData = graphql( + postComment +)(RileysAwesomeCommentBox); + +export default RileysAwesomeCommentBoxWithData; diff --git a/client/coral-plugin-stream/Stream.js b/client/coral-plugin-stream/Stream.js index 14187c01c..650a444b0 100644 --- a/client/coral-plugin-stream/Stream.js +++ b/client/coral-plugin-stream/Stream.js @@ -1,8 +1,10 @@ import React, {Component} from 'react'; import {graphql} from 'react-apollo'; import gql from 'graphql-tag'; +import {fetchSignIn} from 'coral-framework/actions/auth'; +import RileysAwesomeCommentBox from 'coral-plugin-stream/RileysAwesomeCommentBox'; -const assetID = ''; +const assetID = '6187a94b-0b6d-4a96-ac6b-62b529cd8410'; // MyComponent is a "presentational" or apollo-unaware component, // It could be a simple React class: @@ -12,14 +14,46 @@ class Stream extends Component { super(props); } + logMeIn() { + fetchSignIn({email: 'your@example.com', password: 'yourmom'})(() => {}); + } + render() { console.log(this.props); - return
...
; + const {data} = this.props; + return
+ + { + data.loading + ? 'loading!' + :
+ +

Asset ID: {data.asset.id}

+
    + { + data.asset.comments.map(comment => { + return
  • + {comment.body} [{comment.id}] +
      + { + comment.replies.map(reply => { + return
    • {reply.body}
    • ; + }) + } +
    +
  • ; + }) + } +
+
+ } +
; } } // Initialize GraphQL queries or mutations with the `gql` tag const StreamQuery = gql`fragment commentView on Comment { + id body user { name: displayName @@ -36,6 +70,7 @@ const StreamQuery = gql`fragment commentView on Comment { query AssetQuery($asset_id: ID!) { asset(id: $asset_id) { + id title url comments { @@ -47,8 +82,17 @@ query AssetQuery($asset_id: ID!) { } }`; + // We then can use `graphql` to pass the query results returned by MyQuery // to MyComponent as a prop (and update them as the results change) -const StreamWithData = graphql(StreamQuery, {options: {variables: {asset_id: assetID}}})(Stream); +const StreamWithData = graphql( + StreamQuery, { + options: { + variables: { + asset_id: assetID + } + } + } +)(Stream); export default StreamWithData; From d3c215a18d4172144d17a9a865cb88224ad782c3 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 18 Jan 2017 18:23:32 -0500 Subject: [PATCH 009/107] Switching bare-bones commentstream to graphql. --- .../coral-embed-stream/src/CommentStream.js | 356 ++++++++++-------- client/coral-embed-stream/src/index.js | 2 +- .../CommentCount.js | 17 +- client/coral-plugin-stream/Stream.js | 22 +- 4 files changed, 215 insertions(+), 182 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index bd60f8c32..e24471039 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -1,16 +1,18 @@ import React, {Component, PropTypes} from 'react'; import Pym from 'pym.js'; -import {connect} from 'react-redux'; +import {graphql} from 'react-apollo'; +import gql from 'graphql-tag'; import { - itemActions, - Notification, - notificationActions, + + // itemActions, + // Notification, + // notificationActions, authActions } from '../../coral-framework'; -import CommentBox from '../../coral-plugin-commentbox/CommentBox'; -import InfoBox from '../../coral-plugin-infobox/InfoBox'; +// import CommentBox from '../../coral-plugin-commentbox/CommentBox'; +// import InfoBox from '../../coral-plugin-infobox/InfoBox'; import Content from '../../coral-plugin-commentcontent/CommentContent'; import PubDate from '../../coral-plugin-pubdate/PubDate'; import Count from '../../coral-plugin-comment-count/CommentCount'; @@ -19,19 +21,23 @@ import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; import FlagComment from '../../coral-plugin-flags/FlagComment'; import LikeButton from '../../coral-plugin-likes/LikeButton'; import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; -import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; -import UserBox from '../../coral-sign-in/components/UserBox'; + +// import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; +// import UserBox from '../../coral-sign-in/components/UserBox'; import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui'; -import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; -import RestrictedContent from '../../coral-framework/components/RestrictedContent'; -import SuspendedAccount from '../../coral-framework/components/SuspendedAccount'; -import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer'; +// import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; +// import RestrictedContent from '../../coral-framework/components/RestrictedContent'; +// import SuspendedAccount from '../../coral-framework/components/SuspendedAccount'; -const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; -const {addNotification, clearNotification} = notificationActions; -const {logout, showSignInDialog} = authActions; +// import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer'; + +// const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; +// const {addNotification, clearNotification} = notificationActions; +const {/* logout, */showSignInDialog} = authActions; + +const assetID = 'bc7b4cef-1e14-46e1-9db4-66465192f168'; class CommentStream extends Component { @@ -52,9 +58,7 @@ class CommentStream extends Component { } static propTypes = { - items: PropTypes.object.isRequired, - addItem: PropTypes.func.isRequired, - updateItem: PropTypes.func.isRequired + data: PropTypes.object.isRequired, } componentDidMount () { @@ -68,7 +72,7 @@ class CommentStream extends Component { path = window.location.href.split('#')[0]; } - this.props.getStream(path || window.location); + // this.props.getStream(path || window.location); this.path = path; this.pym.sendMessage('childReady'); @@ -92,105 +96,109 @@ class CommentStream extends Component { } render () { - const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0]; - const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; - const {actions, users, comments} = this.props.items; - const {status, moderation, closedMessage, charCount, charCountEnable} = this.props.config; - const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; + + // const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0]; + // const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; + // const {actions, users, comments} = this.props.items; + // const {status, moderation, closedMessage, charCount, charCountEnable} = this.props.config; + // const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; const {activeTab} = this.state; - const banned = (this.props.userData.status === 'banned'); + + // const banned = (this.props.userData.status === 'banned'); + + const {loading, asset} = this.props.data; const expandForLogin = showSignInDialog ? { minHeight: document.body.scrollHeight + 150 } : {}; return
{ - rootItem - ?
+ loading ? + :
- + Settings - Configure Stream + Configure Stream - {loggedIn && } + {/* loggedIn && */} { - status === 'open' - ?
- - }> - - -
- :

{closedMessage}

+ + // status === 'open' + // ?
+ // + // }> + // + // + //
+ // :

{closedMessage}

} - {!loggedIn && } + {/* !loggedIn && */} { - rootItem.comments && rootItem.comments.map((commentId) => { - const comment = comments[commentId]; - return
+ asset.comments.map((comment) => { + return

+ currentUser={null/* this.props.auth.user*/}/>
+ banned={false/* banned*/}/> + currentUser={null/* this.props.auth.user*/} + banned={false/* banned*/}/>
+ banned={false/* banned*/} + currentUser={null/* this.props.auth.user*/}/>
+ id={asset.id} + author={null} + parent_id={comment.id} + premod={'post'} + currentUser={null/* user*/} + charCount={100/* charCountEnable && charCount*/} + showReply={false/* comment.showReply need a way to do these sorts of comment-level settings*/}/> { - comment.children && - comment.children.map((replyId) => { - let reply = this.props.items.comments[replyId]; - return
+ comment.replies.map((reply) => { + return

- +
+ banned={false/* banned*/}/>
+ banned={false/* banned*/} + currentUser={null}/>
- + { + + }
; }) } @@ -274,58 +282,98 @@ class CommentStream extends Component { - - - - - - - - - + { + + // + // + // + // + // + // + // + // + // + }
- : - }
; } } -const mapStateToProps = state => ({ - config: state.config.toJS(), - items: state.items.toJS(), - notification: state.notification.toJS(), - auth: state.auth.toJS(), - userData: state.user.toJS() -}); +// const mapStateToProps = state => ({ +// config: state.config.toJS(), +// items: state.items.toJS(), +// notification: state.notification.toJS(), +// auth: state.auth.toJS(), +// userData: state.user.toJS() +// }); +// +// const mapDispatchToProps = (dispatch) => ({ +// addItem: (item, item_id) => dispatch(addItem(item, item_id)), +// updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), +// postItem: (data, type, id) => dispatch(postItem(data, type, id)), +// getStream: (rootId) => dispatch(getStream(rootId)), +// addNotification: (type, text) => dispatch(addNotification(type, text)), +// clearNotification: () => dispatch(clearNotification()), +// postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)), +// showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), +// deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), +// appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), +// handleSignInDialog: () => dispatch(authActions.showSignInDialog()), +// logout: () => dispatch(logout()) +// }); +// Initialize GraphQL queries or mutations with the `gql` tag +const StreamQuery = gql`fragment commentView on Comment { + id + body + user { + id + name: displayName + } + actions { + type: action_type + count + current: current_user { + id + created_at + } + } +} -const mapDispatchToProps = (dispatch) => ({ - addItem: (item, item_id) => dispatch(addItem(item, item_id)), - updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), - postItem: (data, type, id) => dispatch(postItem(data, type, id)), - getStream: (rootId) => dispatch(getStream(rootId)), - addNotification: (type, text) => dispatch(addNotification(type, text)), - clearNotification: () => dispatch(clearNotification()), - postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)), - showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), - deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), - appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), - handleSignInDialog: () => dispatch(authActions.showSignInDialog()), - logout: () => dispatch(logout()) -}); +query AssetQuery($asset_id: ID!) { + asset(id: $asset_id) { + id + title + url + comments { + ...commentView + replies { + ...commentView + } + } + } +}`; -export default connect(mapStateToProps, mapDispatchToProps)(CommentStream); +export default graphql( + StreamQuery, { + options: { + variables: { + asset_id: assetID + } + } + } +)(CommentStream); diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 31185457c..11dcea5a1 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -3,7 +3,7 @@ import {render} from 'react-dom'; import ApolloClient, {createNetworkInterface} from 'apollo-client'; import {ApolloProvider} from 'react-apollo'; -import Stream from '../../coral-plugin-stream/Stream'; +import Stream from './CommentStream'; const client = new ApolloClient({ networkInterface: createNetworkInterface({ diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index 87f656c22..623274e53 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -1,24 +1,9 @@ import React from 'react'; import {I18n} from '../coral-framework'; import translations from './translations.json'; -import has from 'lodash/has'; -import reduce from 'lodash/reduce'; const name = 'coral-plugin-comment-count'; -const CommentCount = ({items, id}) => { - let count = 0; - if (has(items, `assets.${id}.comments`)) { - count += items.assets[id].comments.length; - } - - // lodash reduce works on {} - count += reduce(items.comments, (accum, comment) => { - if (comment.children) { - accum += comment.children.length; - } - return accum; - }, 0); - +const CommentCount = ({count}) => { return
{`${count} ${count === 1 ? lang.t('comment') : lang.t('comment-plural')}`}
; diff --git a/client/coral-plugin-stream/Stream.js b/client/coral-plugin-stream/Stream.js index 21178d1aa..377f9c037 100644 --- a/client/coral-plugin-stream/Stream.js +++ b/client/coral-plugin-stream/Stream.js @@ -3,8 +3,9 @@ import {graphql} from 'react-apollo'; import gql from 'graphql-tag'; import {fetchSignIn} from 'coral-framework/actions/auth'; import RileysAwesomeCommentBox from 'coral-plugin-stream/RileysAwesomeCommentBox'; +import CommentBody from 'coral-plugin-commentcontent/CommentContent'; -const assetID = '6187a94b-0b6d-4a96-ac6b-62b529cd8410'; +const assetID = 'bc7b4cef-1e14-46e1-9db4-66465192f168'; // MyComponent is a "presentational" or apollo-unaware component, // It could be a simple React class: @@ -29,23 +30,23 @@ class Stream extends Component { :

Asset ID: {data.asset.id}

-
    { data.asset.comments.map(comment => { - return
  • - {comment.body} [{comment.id}] -
      + return
      + { comment.replies.map(reply => { - return
    • {reply.body}
    • ; + return
      + +
      ; }) } -
    -
  • ; +
; }) } - -
+
}
; } @@ -82,7 +83,6 @@ query AssetQuery($asset_id: ID!) { } }`; - // We then can use `graphql` to pass the query results returned by MyQuery // to MyComponent as a prop (and update them as the results change) const StreamWithData = graphql( From 44f3f99b47d3dd666c860711677e75f53f347781 Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 18 Jan 2017 19:20:36 -0500 Subject: [PATCH 010/107] Adding ability to query the currently logged in user. --- routes/api/graph/resolvers.js | 3 +++ routes/api/graph/typeDefs.js | 2 ++ 2 files changed, 5 insertions(+) diff --git a/routes/api/graph/resolvers.js b/routes/api/graph/resolvers.js index 4c53eb366..58107e5b2 100644 --- a/routes/api/graph/resolvers.js +++ b/routes/api/graph/resolvers.js @@ -8,6 +8,9 @@ module.exports = { }, settings(_, args, {loaders}) { return loaders.Settings.load(); + }, + me(_, args, {req}) { + return req.user; } }, Mutation: { diff --git a/routes/api/graph/typeDefs.js b/routes/api/graph/typeDefs.js index 51e74b5d3..d2a9e7758 100644 --- a/routes/api/graph/typeDefs.js +++ b/routes/api/graph/typeDefs.js @@ -45,12 +45,14 @@ type Asset { url: String comments: [Comment] settings: Settings! + currentUser: User } type Query { settings: Settings assets: [Asset] asset(id: ID!): Asset + me: User } type Mutation { From a40971e376d43cdabc7b63c66d5daf8e0c8c5b3f Mon Sep 17 00:00:00 2001 From: David Jay Date: Wed, 18 Jan 2017 19:23:47 -0500 Subject: [PATCH 011/107] Adding thunk to apollog redux store. --- client/coral-embed-stream/src/index.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 11dcea5a1..1a3ca29f1 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -2,6 +2,8 @@ import React from 'react'; import {render} from 'react-dom'; import ApolloClient, {createNetworkInterface} from 'apollo-client'; import {ApolloProvider} from 'react-apollo'; +import thunk from 'redux-thunk'; +import {createStore, applyMiddleware, compose} from 'redux'; import Stream from './CommentStream'; @@ -14,8 +16,17 @@ const client = new ApolloClient({ }) }); +const store = createStore( + client.reducer(), + {}, // initial state + compose( + applyMiddleware(thunk), + window.devToolsExtension ? window.devToolsExtension() : f => f, + ) +); + render( - + From 2d414a5ab78ec254cf936bf4040303b03c233057 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 19 Jan 2017 10:01:43 -0300 Subject: [PATCH 012/107] Transport, Client, from coral-framework --- client/coral-embed-stream/src/index.js | 34 ++++++------------------- client/coral-framework/client.js | 7 +++++ client/coral-framework/store.js | 10 +++++--- client/coral-framework/subscriptions.js | 14 ++++++++++ client/coral-framework/transport.js | 11 ++++++++ 5 files changed, 47 insertions(+), 29 deletions(-) create mode 100644 client/coral-framework/client.js create mode 100644 client/coral-framework/subscriptions.js create mode 100644 client/coral-framework/transport.js diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 1a3ca29f1..67b4bad29 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -1,33 +1,15 @@ import React from 'react'; import {render} from 'react-dom'; -import ApolloClient, {createNetworkInterface} from 'apollo-client'; import {ApolloProvider} from 'react-apollo'; -import thunk from 'redux-thunk'; -import {createStore, applyMiddleware, compose} from 'redux'; + +import {client} from 'coral-framework/client'; +import store from 'coral-framework/store'; import Stream from './CommentStream'; -const client = new ApolloClient({ - networkInterface: createNetworkInterface({ - uri: '/api/v1/graph/ql', - opts: { - credentials: 'same-origin' - } - }) -}); - -const store = createStore( - client.reducer(), - {}, // initial state - compose( - applyMiddleware(thunk), - window.devToolsExtension ? window.devToolsExtension() : f => f, - ) -); - render( - - - - - , document.querySelector('#coralStream')); + + + + , document.querySelector('#coralStream') +); diff --git a/client/coral-framework/client.js b/client/coral-framework/client.js new file mode 100644 index 000000000..abd193b52 --- /dev/null +++ b/client/coral-framework/client.js @@ -0,0 +1,7 @@ +import ApolloClient, {addTypename} from 'apollo-client'; +import getNetworkInterface from './transport'; + +export const client = new ApolloClient({ + queryTransformer: addTypename, + networkInterface: getNetworkInterface() +}); diff --git a/client/coral-framework/store.js b/client/coral-framework/store.js index 8c3f9edc1..cad05505c 100644 --- a/client/coral-framework/store.js +++ b/client/coral-framework/store.js @@ -1,9 +1,13 @@ -import {createStore, applyMiddleware} from 'redux'; +import {createStore, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; import mainReducer from './reducers'; +import {client} from './client'; export default createStore( + client.reducer(), mainReducer, - window.devToolsExtension && window.devToolsExtension(), - applyMiddleware(thunk) + compose( + window.devToolsExtension && window.devToolsExtension(), + applyMiddleware(thunk) + ) ); diff --git a/client/coral-framework/subscriptions.js b/client/coral-framework/subscriptions.js new file mode 100644 index 000000000..f514ea18b --- /dev/null +++ b/client/coral-framework/subscriptions.js @@ -0,0 +1,14 @@ +import {print} from 'graphql-tag/printer'; + +// quick way to add the subscribe and unsubscribe functions to the network interface +const addGraphQLSubscriptions = (networkInterface, wsClient) => Object.assign(networkInterface, { + subscribe: (request, handler) => wsClient.subscribe({ + query: print(request.query), + variables: request.variables, + }, handler), + unsubscribe: (id) => { + wsClient.unsubscribe(id); + }, +}); + +export default addGraphQLSubscriptions; diff --git a/client/coral-framework/transport.js b/client/coral-framework/transport.js new file mode 100644 index 000000000..2bd6ac636 --- /dev/null +++ b/client/coral-framework/transport.js @@ -0,0 +1,11 @@ +import {createNetworkInterface} from 'apollo-client'; + +export default function getNetworkInterface(apiUrl = '/api/v1/graph/ql', headers = {}) { + return new createNetworkInterface({ + uri: apiUrl, + opts: { + credentials: 'same-origin', + headers, + }, + }); +} From b172e30e5b71391b146c8679101c43d8d8622a81 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Thu, 19 Jan 2017 11:30:30 -0300 Subject: [PATCH 013/107] By doing this we can refactor one by one and still having a working app --- client/coral-embed-stream/src/CommentStream.js | 1 - client/coral-framework/store.js | 9 ++++++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index e24471039..5ad82f1ad 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -4,7 +4,6 @@ import {graphql} from 'react-apollo'; import gql from 'graphql-tag'; import { - // itemActions, // Notification, // notificationActions, diff --git a/client/coral-framework/store.js b/client/coral-framework/store.js index cad05505c..1f3b011ef 100644 --- a/client/coral-framework/store.js +++ b/client/coral-framework/store.js @@ -1,11 +1,14 @@ -import {createStore, applyMiddleware, compose} from 'redux'; +import {createStore, applyMiddleware, compose, combineReducers} from 'redux'; import thunk from 'redux-thunk'; import mainReducer from './reducers'; import {client} from './client'; export default createStore( - client.reducer(), - mainReducer, + combineReducers({ + ...mainReducer, + apollo: client.reducer(), + }), + {}, // Initial State. We need to set this compose( window.devToolsExtension && window.devToolsExtension(), applyMiddleware(thunk) From d683b09b1dca2eef43cd3f0f9c067ef6d289fbc0 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 19 Jan 2017 10:39:29 -0700 Subject: [PATCH 014/107] Added more mutators --- routes/api/graph/index.js | 8 +++- routes/api/graph/loaders.js | 2 +- routes/api/graph/mutators.js | 88 +++++++++++++++++++++++++++++++---- routes/api/graph/resolvers.js | 46 ++++++++++++++++-- routes/api/graph/typeDefs.js | 50 +++++++++++++++++--- 5 files changed, 175 insertions(+), 19 deletions(-) diff --git a/routes/api/graph/index.js b/routes/api/graph/index.js index 1e737fd53..55f177aa9 100644 --- a/routes/api/graph/index.js +++ b/routes/api/graph/index.js @@ -10,9 +10,15 @@ const router = express.Router(); // GraphQL endpoint. router.use('/ql', apollo.graphqlExpress((req) => { - let context = {req}; + let context = {}; + // Load the current logged in user to `user`, otherwise this'll be null. + context.user = req.user; + + // Create the loaders. context.loaders = loaders(context); + + // Create the mutators. context.mutators = mutators(context); return { diff --git a/routes/api/graph/loaders.js b/routes/api/graph/loaders.js index d6534450c..f5f0046a0 100644 --- a/routes/api/graph/loaders.js +++ b/routes/api/graph/loaders.js @@ -122,7 +122,7 @@ const createLoaders = (context) => ({ getByAssetID: new DataLoader((ids) => genCommentsByAssetID(ids)), }, Actions: { - getByID: new DataLoader((ids) => genActionsByID(ids, context.req.user)), + getByID: new DataLoader((ids) => genActionsByID(ids, context.user)), }, Users: { getByID: new DataLoader((ids) => User.findByIdArray(ids)) diff --git a/routes/api/graph/mutators.js b/routes/api/graph/mutators.js index 2a9f9bc64..e1b65a09e 100644 --- a/routes/api/graph/mutators.js +++ b/routes/api/graph/mutators.js @@ -1,20 +1,22 @@ const errors = require('../../../errors'); +const Action = require('../../../models/action'); const Asset = require('../../../models/asset'); const Comment = require('../../../models/comment'); +const User = require('../../../models/user'); const Wordlist = require('../../../services/wordlist'); /** * Creates a new comment. - * @param {Object} context a GraphQL context + * @param {Object} user the user performing the request * @param {String} body body of the comment * @param {String} asset_id asset for the comment * @param {String} parent_id optional parent of the comment * @param {Object} [wordlist={}] results for the wordlist analysis * @return {Promise} resolves to the created comment */ -const createComment = (context, {body, asset_id, parent_id = null}, wordlist = {}) => { +const createComment = ({user}, {body, asset_id, parent_id = null}, wordlist = {}) => { // 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 @@ -58,7 +60,7 @@ const createComment = (context, {body, asset_id, parent_id = null}, wordlist = { asset_id, parent_id, status, - author_id: context.req.user.id + author_id: user.id })) .then((comment) => { if (wordlist.suspect) { @@ -86,8 +88,78 @@ const filterNewComment = (context, {body}) => { return wl.load().then(() => wl.scan('body', body)); }; -module.exports = (context) => ({ - createComment: (comment) => filterNewComment(context, comment).then((wordlist) => { - return createComment(context, comment, wordlist); - }) -}); +/** + * Creates an action on a item. + * @param {Object} user the user performing the request + * @param {String} item_id id of the item to add the action to + * @param {String} item_type type of the item + * @param {String} action_type type of the action + * @return {Promise} resolves to the action created + */ +const createAction = ({user}, {item_id, item_type, action_type}) => { + return Action.insertUserAction({ + item_id, + item_type, + user_id: user.id, + action_type + }); +}; + +/** + * Deletes an action based on the user id if the user owns that action. + * @param {Object} user the user performing the request + * @param {[type]} id [description] + * @return {[type]} [description] + */ +const deleteAction = ({user}, {id}) => { + return Action.remove({ + id, + user_id: user.id + }); +}; + +/** + * Updates a users settings. + * @param {[type]} user [description] + * @param {[type]} bio [description] + * @return {[type]} [description] + */ +const updateUserSettings = ({user}, {bio}) => { + return User.updateSettings(user.id, {bio}); +}; + +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) { + return { + Comment: { + create: (comment) => filterNewComment(context, comment).then((wordlist) => { + return createComment(context, comment, wordlist); + }) + }, + Action: { + create: (action) => createAction(context, action), + delete: (action) => deleteAction(context, action) + }, + User: { + updateSettings: (settings) => updateUserSettings(context, settings) + } + }; + } + + return { + Comment: { + create: () => {} + }, + Action: { + create: () => {}, + delete: () => {} + }, + User: { + updateSettings: () => {} + } + }; +}; diff --git a/routes/api/graph/resolvers.js b/routes/api/graph/resolvers.js index 58107e5b2..4c5f553eb 100644 --- a/routes/api/graph/resolvers.js +++ b/routes/api/graph/resolvers.js @@ -9,13 +9,22 @@ module.exports = { settings(_, args, {loaders}) { return loaders.Settings.load(); }, - me(_, args, {req}) { - return req.user; + me(_, args, {user}) { + return user; } }, Mutation: { createComment(_, {asset_id, parent_id, body}, {mutators}) { - return mutators.createComment({asset_id, parent_id, body}); + return mutators.Comment.create({asset_id, parent_id, body}); + }, + createAction(_, {action}, {mutators}) { + return mutators.Action.create(action); + }, + deleteAction(_, {id}, {mutators}) { + return mutators.Action.delete({id}); + }, + updateUserSettings(_, {settings}, {mutators}) { + return mutators.User.updateSettings(settings); } }, Asset: { @@ -36,6 +45,37 @@ module.exports = { }); } }, + Action: { + action_type({action_type}) { + + // TODO: remove once we cast the data model to have uppercase action + // types. + return action_type.toUpperCase(); + }, + item_type({item_type}) { + + // TODO: remove once we cast the data model to have uppercase item + // types. + return item_type.toUpperCase(); + }, + user({user_id}, _, {loaders}) { + return loaders.Users.getByID.load(user_id); + } + }, + ActionSummary: { + action_type({action_type}) { + + // TODO: remove once we cast the data model to have uppercase action + // types. + return action_type.toUpperCase(); + }, + item_type({item_type}) { + + // TODO: remove once we cast the data model to have uppercase item + // types. + return item_type.toUpperCase(); + } + }, User: { actions({id}, _, {loaders}) { return loaders.Actions.getByID.load(id); diff --git a/routes/api/graph/typeDefs.js b/routes/api/graph/typeDefs.js index d2a9e7758..e0cff85ee 100644 --- a/routes/api/graph/typeDefs.js +++ b/routes/api/graph/typeDefs.js @@ -6,7 +6,7 @@ type UserSettings { type User { id: ID! displayName: String! - actions: [Action] + actions: [ActionSummary] settings: UserSettings } @@ -15,19 +15,42 @@ type Comment { body: String! user: User replies(limit: Int = 3): [Comment] - actions: [Action] + actions: [ActionSummary] } -type Action { +enum ITEM_TYPE { + ASSETS + COMMENTS + USERS +} + +enum ACTION_TYPE { + LIKE + FLAG +} + +interface ActionInterface { + action_type: ACTION_TYPE! + item_type: ITEM_TYPE! +} + +type Action implements ActionInterface { id: ID! item_id: ID! - action_type: String! - count: Int - current_user: Action + action_type: ACTION_TYPE! + item_type: ITEM_TYPE! + user: User! updated_at: String created_at: String } +type ActionSummary implements ActionInterface { + action_type: ACTION_TYPE! + item_type: ITEM_TYPE! + count: Int + current_user: Action +} + type Settings { moderation: String infoBoxEnable: Boolean @@ -55,8 +78,23 @@ type Query { me: User } +input CreateActionInput { + action_type: ACTION_TYPE! + item_type: ITEM_TYPE! + item_id: ID! +} + +input UpdateUserSettingsInput { + bio: String! +} + type Mutation { createComment(asset_id: ID!, parent_id: ID, body: String!): Comment + createAction(action: CreateActionInput!): Action + deleteAction(id: ID!): Boolean + + updateUserSettings(settings: UpdateUserSettingsInput!): Boolean + } schema { From 03a5acc73601e47b5a88a30828effc0a56a59ab5 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 19 Jan 2017 11:01:24 -0700 Subject: [PATCH 015/107] Modularized reolvers --- routes/api/graph/resolvers.js | 95 -------------------- routes/api/graph/resolvers/action.js | 19 ++++ routes/api/graph/resolvers/action_summary.js | 16 ++++ routes/api/graph/resolvers/asset.js | 20 +++++ routes/api/graph/resolvers/comment.js | 13 +++ routes/api/graph/resolvers/index.js | 17 ++++ routes/api/graph/resolvers/mutation.js | 16 ++++ routes/api/graph/resolvers/query.js | 16 ++++ routes/api/graph/resolvers/user.js | 7 ++ 9 files changed, 124 insertions(+), 95 deletions(-) delete mode 100644 routes/api/graph/resolvers.js create mode 100644 routes/api/graph/resolvers/action.js create mode 100644 routes/api/graph/resolvers/action_summary.js create mode 100644 routes/api/graph/resolvers/asset.js create mode 100644 routes/api/graph/resolvers/comment.js create mode 100644 routes/api/graph/resolvers/index.js create mode 100644 routes/api/graph/resolvers/mutation.js create mode 100644 routes/api/graph/resolvers/query.js create mode 100644 routes/api/graph/resolvers/user.js diff --git a/routes/api/graph/resolvers.js b/routes/api/graph/resolvers.js deleted file mode 100644 index 4c5f553eb..000000000 --- a/routes/api/graph/resolvers.js +++ /dev/null @@ -1,95 +0,0 @@ -module.exports = { - Query: { - assets(_, args, {loaders}) { - return loaders.Assets.getAll.load(); - }, - asset(_, {id}, {loaders}) { - return loaders.Assets.getByID.load(id); - }, - settings(_, args, {loaders}) { - return loaders.Settings.load(); - }, - me(_, args, {user}) { - return user; - } - }, - Mutation: { - createComment(_, {asset_id, parent_id, body}, {mutators}) { - return mutators.Comment.create({asset_id, parent_id, body}); - }, - createAction(_, {action}, {mutators}) { - return mutators.Action.create(action); - }, - deleteAction(_, {id}, {mutators}) { - return mutators.Action.delete({id}); - }, - updateUserSettings(_, {settings}, {mutators}) { - return mutators.User.updateSettings(settings); - } - }, - Asset: { - comments({id}, _, {loaders}) { - return loaders.Comments.getByAssetID.load(id); - }, - settings({settings = null}, _, {loaders}) { - return loaders.Settings.load() - .then((globalSettings) => { - - if (settings) { - settings = Object.assign({}, settings, globalSettings); - } else { - settings = globalSettings; - } - - return settings; - }); - } - }, - Action: { - action_type({action_type}) { - - // TODO: remove once we cast the data model to have uppercase action - // types. - return action_type.toUpperCase(); - }, - item_type({item_type}) { - - // TODO: remove once we cast the data model to have uppercase item - // types. - return item_type.toUpperCase(); - }, - user({user_id}, _, {loaders}) { - return loaders.Users.getByID.load(user_id); - } - }, - ActionSummary: { - action_type({action_type}) { - - // TODO: remove once we cast the data model to have uppercase action - // types. - return action_type.toUpperCase(); - }, - item_type({item_type}) { - - // TODO: remove once we cast the data model to have uppercase item - // types. - return item_type.toUpperCase(); - } - }, - User: { - actions({id}, _, {loaders}) { - return loaders.Actions.getByID.load(id); - } - }, - Comment: { - user({author_id}, _, {loaders}) { - return loaders.Users.getByID.load(author_id); - }, - replies({id}, _, {loaders}) { - return loaders.Comments.getByParentID.load(id); - }, - actions({id}, _, {loaders}) { - return loaders.Actions.getByID.load(id); - } - } -}; diff --git a/routes/api/graph/resolvers/action.js b/routes/api/graph/resolvers/action.js new file mode 100644 index 000000000..6b42b84dd --- /dev/null +++ b/routes/api/graph/resolvers/action.js @@ -0,0 +1,19 @@ +const Action = { + action_type({action_type}) { + + // TODO: remove once we cast the data model to have uppercase action + // types. + return action_type.toUpperCase(); + }, + item_type({item_type}) { + + // TODO: remove once we cast the data model to have uppercase item + // types. + return item_type.toUpperCase(); + }, + user({user_id}, _, {loaders}) { + return loaders.Users.getByID.load(user_id); + } +}; + +module.exports = Action; diff --git a/routes/api/graph/resolvers/action_summary.js b/routes/api/graph/resolvers/action_summary.js new file mode 100644 index 000000000..662078500 --- /dev/null +++ b/routes/api/graph/resolvers/action_summary.js @@ -0,0 +1,16 @@ +const ActionSummary = { + action_type({action_type}) { + + // TODO: remove once we cast the data model to have uppercase action + // types. + return action_type.toUpperCase(); + }, + item_type({item_type}) { + + // TODO: remove once we cast the data model to have uppercase item + // types. + return item_type.toUpperCase(); + } +}; + +module.exports = ActionSummary; diff --git a/routes/api/graph/resolvers/asset.js b/routes/api/graph/resolvers/asset.js new file mode 100644 index 000000000..f6c055f42 --- /dev/null +++ b/routes/api/graph/resolvers/asset.js @@ -0,0 +1,20 @@ +const Asset = { + comments({id}, _, {loaders}) { + return loaders.Comments.getByAssetID.load(id); + }, + settings({settings = null}, _, {loaders}) { + return loaders.Settings.load() + .then((globalSettings) => { + + if (settings) { + settings = Object.assign({}, settings, globalSettings); + } else { + settings = globalSettings; + } + + return settings; + }); + } +}; + +module.exports = Asset; diff --git a/routes/api/graph/resolvers/comment.js b/routes/api/graph/resolvers/comment.js new file mode 100644 index 000000000..2fdf6f0dd --- /dev/null +++ b/routes/api/graph/resolvers/comment.js @@ -0,0 +1,13 @@ +const Comment = { + user({author_id}, _, {loaders}) { + return loaders.Users.getByID.load(author_id); + }, + replies({id}, _, {loaders}) { + return loaders.Comments.getByParentID.load(id); + }, + actions({id}, _, {loaders}) { + return loaders.Actions.getByID.load(id); + } +}; + +module.exports = Comment; diff --git a/routes/api/graph/resolvers/index.js b/routes/api/graph/resolvers/index.js new file mode 100644 index 000000000..d4de137d5 --- /dev/null +++ b/routes/api/graph/resolvers/index.js @@ -0,0 +1,17 @@ +const Action = require('./action'); +const ActionSummary = require('./action_summary'); +const Asset = require('./asset'); +const Comment = require('./comment'); +const Mutation = require('./mutation'); +const Query = require('./Query'); +const User = require('./user'); + +module.exports = { + Action, + ActionSummary, + Asset, + Comment, + Mutation, + Query, + User +}; diff --git a/routes/api/graph/resolvers/mutation.js b/routes/api/graph/resolvers/mutation.js new file mode 100644 index 000000000..ef95d0334 --- /dev/null +++ b/routes/api/graph/resolvers/mutation.js @@ -0,0 +1,16 @@ +const Mutation = { + createComment(_, {asset_id, parent_id, body}, {mutators}) { + return mutators.Comment.create({asset_id, parent_id, body}); + }, + createAction(_, {action}, {mutators}) { + return mutators.Action.create(action); + }, + deleteAction(_, {id}, {mutators}) { + return mutators.Action.delete({id}); + }, + updateUserSettings(_, {settings}, {mutators}) { + return mutators.User.updateSettings(settings); + } +}; + +module.exports = Mutation; diff --git a/routes/api/graph/resolvers/query.js b/routes/api/graph/resolvers/query.js new file mode 100644 index 000000000..89cbc18c9 --- /dev/null +++ b/routes/api/graph/resolvers/query.js @@ -0,0 +1,16 @@ +const Query = { + assets(_, args, {loaders}) { + return loaders.Assets.getAll.load(); + }, + asset(_, {id}, {loaders}) { + return loaders.Assets.getByID.load(id); + }, + settings(_, args, {loaders}) { + return loaders.Settings.load(); + }, + me(_, args, {user}) { + return user; + } +}; + +module.exports = Query; diff --git a/routes/api/graph/resolvers/user.js b/routes/api/graph/resolvers/user.js new file mode 100644 index 000000000..98d25f524 --- /dev/null +++ b/routes/api/graph/resolvers/user.js @@ -0,0 +1,7 @@ +const User = { + actions({id}, _, {loaders}) { + return loaders.Actions.getByID.load(id); + } +}; + +module.exports = User; From 840c91274d22418cdbd0e9e28e5d9a032ab670c4 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 19 Jan 2017 11:17:47 -0700 Subject: [PATCH 016/107] Added comments to typeDefs --- routes/api/graph/typeDefs.js | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/routes/api/graph/typeDefs.js b/routes/api/graph/typeDefs.js index e0cff85ee..5bacb1a2b 100644 --- a/routes/api/graph/typeDefs.js +++ b/routes/api/graph/typeDefs.js @@ -79,22 +79,33 @@ type Query { } input CreateActionInput { + # the type of action. action_type: ACTION_TYPE! + + # the type of the item. item_type: ITEM_TYPE! + + # the id of the item that is related to the action. item_id: ID! } input UpdateUserSettingsInput { + # user bio bio: String! } type Mutation { + # creates a comment on the asset. createComment(asset_id: ID!, parent_id: ID, body: String!): Comment + + # creates an action based on an input. createAction(action: CreateActionInput!): Action + + # delete an action based on the action id. deleteAction(id: ID!): Boolean + # updates a user's settings, it will return if the query was successful. updateUserSettings(settings: UpdateUserSettingsInput!): Boolean - } schema { From 672d1ee19ecc3fdfbc4ead923e5fe69ad0b72fe3 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 19 Jan 2017 13:39:46 -0500 Subject: [PATCH 017/107] Updating store to include auth and apollo. --- client/coral-framework/actions/auth.js | 13 ++++++++----- client/coral-framework/store.js | 11 +++++++---- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 0f65e14c2..31a8a728a 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -20,17 +20,20 @@ export const cleanState = () => ({type: actions.CLEAN_STATE}); // Sign In Actions const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST}); -const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin}); + +// const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin}); const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error}); export const fetchSignIn = (formData) => (dispatch) => { dispatch(signInRequest()); coralApi('/auth/local', {method: 'POST', body: formData}) - .then(({user}) => { - const isAdmin = !!user.roles.filter(i => i === 'admin').length; - dispatch(signInSuccess(user, isAdmin)); + .then(() => { + + // const isAdmin = !!user.roles.filter(i => i === 'admin').length; + // dispatch(signInSuccess(user, isAdmin)); dispatch(hideSignInDialog()); - dispatch(addItem(user, 'users')); + + // dispatch(addItem(user, 'users')); }) .catch(() => dispatch(signInFailure(lang.t('error.emailPasswordError')))); }; diff --git a/client/coral-framework/store.js b/client/coral-framework/store.js index cad05505c..766ed5b92 100644 --- a/client/coral-framework/store.js +++ b/client/coral-framework/store.js @@ -1,11 +1,14 @@ -import {createStore, applyMiddleware, compose} from 'redux'; +import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; -import mainReducer from './reducers'; +import authReducer from './reducers/auth'; import {client} from './client'; export default createStore( - client.reducer(), - mainReducer, + combineReducers({ + auth: authReducer, + apollo: client.reducer() + }), + {}, compose( window.devToolsExtension && window.devToolsExtension(), applyMiddleware(thunk) From e9846c654c8a4a32e7b0c8f86cfafe4832dfce08 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 19 Jan 2017 15:37:05 -0500 Subject: [PATCH 018/107] Enabling stream login with graphql. --- .../coral-embed-stream/src/CommentStream.js | 63 +++++++++++++------ client/coral-framework/actions/auth.js | 20 +++--- client/coral-framework/store.js | 12 +++- .../containers/SignInContainer.js | 7 ++- 4 files changed, 68 insertions(+), 34 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index e24471039..4bdf11b39 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -1,6 +1,7 @@ import React, {Component, PropTypes} from 'react'; import Pym from 'pym.js'; import {graphql} from 'react-apollo'; +import {connect} from 'react-redux'; import gql from 'graphql-tag'; import { @@ -22,8 +23,8 @@ import FlagComment from '../../coral-plugin-flags/FlagComment'; import LikeButton from '../../coral-plugin-likes/LikeButton'; import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; -// import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; -// import UserBox from '../../coral-sign-in/components/UserBox'; +import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; +import UserBox from '../../coral-sign-in/components/UserBox'; import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui'; @@ -35,7 +36,7 @@ import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui'; // const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; // const {addNotification, clearNotification} = notificationActions; -const {/* logout, */showSignInDialog} = authActions; +const {logout, showSignInDialog} = authActions; const assetID = 'bc7b4cef-1e14-46e1-9db4-66465192f168'; @@ -45,7 +46,8 @@ class CommentStream extends Component { super(props); this.state = { - activeTab: 0 + activeTab: 0, + showSignInDialog: false }; this.changeTab = this.changeTab.bind(this); @@ -101,12 +103,12 @@ class CommentStream extends Component { // const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; // const {actions, users, comments} = this.props.items; // const {status, moderation, closedMessage, charCount, charCountEnable} = this.props.config; - // const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; + const {isAdmin, showSignInDialog} = this.props.auth; const {activeTab} = this.state; // const banned = (this.props.userData.status === 'banned'); - const {loading, asset} = this.props.data; + const {loading, asset, currentUser, refetch} = this.props.data; const expandForLogin = showSignInDialog ? { minHeight: document.body.scrollHeight + 150 @@ -118,9 +120,9 @@ class CommentStream extends Component { Settings - Configure Stream + Configure Stream - {/* loggedIn && */} + {currentUser && } { @@ -147,7 +149,11 @@ class CommentStream extends Component { //
// :

{closedMessage}

} - {/* !loggedIn && */} + { + !currentUser && + } { asset.comments.map((comment) => { return
@@ -162,14 +168,14 @@ class CommentStream extends Component { deleteAction={this.props.deleteAction} addItem={this.props.addItem} updateItem={this.props.updateItem} - currentUser={null/* this.props.auth.user*/}/> + currentUser={currentUser}/>
@@ -196,7 +202,7 @@ class CommentStream extends Component { showSignInDialog={this.props.showSignInDialog} updateItem={this.props.updateItem} banned={false/* banned*/} - currentUser={null/* this.props.auth.user*/}/> + currentUser={currentUser}/> @@ -236,7 +242,7 @@ class CommentStream extends Component { addItem={this.props.addItem} showSignInDialog={this.props.showSignInDialog} updateItem={this.props.updateItem} - currentUser={this.props.auth.user} + currentUser={currentUser} banned={false/* banned*/}/>
@@ -279,11 +285,14 @@ class CommentStream extends Component {
; }) } - + { + + // + } { @@ -365,10 +374,14 @@ query AssetQuery($asset_id: ID!) { ...commentView } } + }, + currentUser: me { + id, + displayName } }`; -export default graphql( +const CommentStreamWithData = graphql( StreamQuery, { options: { variables: { @@ -377,3 +390,13 @@ export default graphql( } } )(CommentStream); + +export default connect( + state => state, + dispatch => { + return { + logout: () => dispatch(logout()), + showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), + }; + } +)(CommentStreamWithData); diff --git a/client/coral-framework/actions/auth.js b/client/coral-framework/actions/auth.js index 31a8a728a..8c17fe5a6 100644 --- a/client/coral-framework/actions/auth.js +++ b/client/coral-framework/actions/auth.js @@ -21,17 +21,17 @@ export const cleanState = () => ({type: actions.CLEAN_STATE}); const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST}); -// const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin}); +const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin}); const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error}); export const fetchSignIn = (formData) => (dispatch) => { dispatch(signInRequest()); - coralApi('/auth/local', {method: 'POST', body: formData}) - .then(() => { + return coralApi('/auth/local', {method: 'POST', body: formData}) + .then((user) => { - // const isAdmin = !!user.roles.filter(i => i === 'admin').length; - // dispatch(signInSuccess(user, isAdmin)); - dispatch(hideSignInDialog()); + const isAdmin = !!user.roles.filter(i => i === 'admin').length; + dispatch(signInSuccess(user, isAdmin)); + // dispatch(hideSignInDialog()); // dispatch(addItem(user, 'users')); }) @@ -78,7 +78,7 @@ const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error}); export const fetchSignUp = formData => (dispatch) => { dispatch(signUpRequest()); - coralApi('/users', {method: 'POST', body: formData}) + return coralApi('/users', {method: 'POST', body: formData}) .then(({user}) => { dispatch(signUpSuccess(user)); setTimeout(() =>{ @@ -98,7 +98,7 @@ const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILU export const fetchForgotPassword = email => (dispatch) => { dispatch(forgotPassowordRequest(email)); - coralApi('/account/password/reset', {method: 'POST', body: {email}}) + return coralApi('/account/password/reset', {method: 'POST', body: {email}}) .then(() => dispatch(forgotPassowordSuccess())) .catch(error => dispatch(forgotPassowordFailure(error))); }; @@ -111,7 +111,7 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE}); export const logout = () => dispatch => { dispatch(logOutRequest()); - coralApi('/auth', {method: 'DELETE'}) + return coralApi('/auth', {method: 'DELETE'}) .then(() => dispatch(logOutSuccess())) .catch(error => dispatch(logOutFailure(error))); }; @@ -129,7 +129,7 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error}); export const checkLogin = () => dispatch => { dispatch(checkLoginRequest()); - coralApi('/auth') + return coralApi('/auth') .then((result) => { if (!result.user) { throw new Error('Not logged in'); diff --git a/client/coral-framework/store.js b/client/coral-framework/store.js index 766ed5b92..af942ecdd 100644 --- a/client/coral-framework/store.js +++ b/client/coral-framework/store.js @@ -8,9 +8,15 @@ export default createStore( auth: authReducer, apollo: client.reducer() }), - {}, + { + apollo: { + data: { + loading: true, + } + } + }, compose( - window.devToolsExtension && window.devToolsExtension(), - applyMiddleware(thunk) + applyMiddleware(thunk), + window.devToolsExtension && window.devToolsExtension() ) ); diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 07f82a849..9f97a14c7 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -121,7 +121,12 @@ class SignInContainer extends Component { handleSignIn(e) { e.preventDefault(); - this.props.fetchSignIn(this.state.formData); + this.props.fetchSignIn(this.state.formData) + + // Using refetch to get data after the user has logged in. + // This is the equivalent of a page reload, we may want to use a more speficic mustation here. + // Also, we may want to find a way for this logic to live elsewhere. + .then(() => this.props.refetch()); } handleClose() { From 8bad6f7a7320336eaf6af1542bd45eda33edf776 Mon Sep 17 00:00:00 2001 From: David Jay Date: Thu, 19 Jan 2017 15:51:38 -0500 Subject: [PATCH 019/107] Enabling signup. --- client/coral-framework/helpers/validate.js | 2 +- client/coral-sign-in/components/SignUpContent.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-framework/helpers/validate.js b/client/coral-framework/helpers/validate.js index 7aa4ccc72..90cd650fe 100644 --- a/client/coral-framework/helpers/validate.js +++ b/client/coral-framework/helpers/validate.js @@ -1,6 +1,6 @@ export default { email: email => (/^([A-Za-z0-9_\-\.])+\@([A-Za-z0-9_\-\.])+\.([A-Za-z]{2,4})$/.test(email)), - password: pass => !(/^(?=.{8,}).*$/.test(pass)), + password: pass => /^(?=.{8,}).*$/.test(pass), confirmPassword: () => true, displayName: displayName => (/^[a-z0-9_]+$/.test(displayName)) }; diff --git a/client/coral-sign-in/components/SignUpContent.js b/client/coral-sign-in/components/SignUpContent.js index 2f9e9afe1..4144b1d2f 100644 --- a/client/coral-sign-in/components/SignUpContent.js +++ b/client/coral-sign-in/components/SignUpContent.js @@ -56,7 +56,7 @@ const SignUpContent = ({handleChange, formData, ...props}) => ( onChange={handleChange} minLength="8" /> - { !props.errors.password && Password must be at least 8 characters. } + { props.errors.password && Password must be at least 8 characters. } Date: Thu, 19 Jan 2017 18:06:06 -0700 Subject: [PATCH 020/107] Introduced some changes to the resolvers + mutators --- routes/api/graph/loaders.js | 38 +++++- routes/api/graph/mutators.js | 122 ++++++++++++------ routes/api/graph/resolvers/index.js | 8 +- routes/api/graph/resolvers/query.js | 16 --- .../{mutation.js => root_mutation.js} | 4 +- routes/api/graph/resolvers/root_query.js | 25 ++++ routes/api/graph/typeDefs.js | 12 +- 7 files changed, 160 insertions(+), 65 deletions(-) delete mode 100644 routes/api/graph/resolvers/query.js rename routes/api/graph/resolvers/{mutation.js => root_mutation.js} (88%) create mode 100644 routes/api/graph/resolvers/root_query.js diff --git a/routes/api/graph/loaders.js b/routes/api/graph/loaders.js index f5f0046a0..0bf11fcc8 100644 --- a/routes/api/graph/loaders.js +++ b/routes/api/graph/loaders.js @@ -1,5 +1,8 @@ const DataLoader = require('dataloader'); const _ = require('lodash'); +const url = require('url'); +const errors = require('../../../errors'); +const scraper = require('../../../services/scraper'); const Comment = require('../../../models/comment'); const User = require('../../../models/user'); @@ -72,7 +75,7 @@ const singleJoinBy = (ids, key) => (items) => { * Retrieves assets by an array of ids. * @param {Array} ids array of ids to lookup */ -const genAssetByID = (ids) => Asset.find({ +const genAssetsByID = (ids) => Asset.find({ id: { $in: ids } @@ -111,6 +114,32 @@ const genCommentsByParentID = (ids) => Comment.find({ } }).then(arrayJoinBy(ids, 'parent_id')); +/** + * This endpoint find or creates an asset at the given url when it is loaded. + * @param {String} asset_url the url passed in from the query + * @returns {Promise} resolves to the asset + */ +const findOrCreateAssetByURL = (asset_url) => { + + // Verify that the asset_url is parsable. + let parsed_asset_url = url.parse(asset_url); + if (!parsed_asset_url.protocol) { + return Promise.reject(errors.ErrInvalidAssetURL); + } + + return Asset.findOrCreateByUrl(asset_url) + .then((asset) => { + + // If the asset wasn't scraped before, scrape it! Otherwise just return + // the asset. + if (!asset.scraped) { + return scraper.create(asset).then(() => asset); + } + + return asset; + }); +}; + /** * Creates a set of loaders based on a GraphQL context. * @param {Object} context the context of the GraphQL request @@ -128,7 +157,12 @@ const createLoaders = (context) => ({ getByID: new DataLoader((ids) => User.findByIdArray(ids)) }, Assets: { - getByID: new DataLoader((ids) => genAssetByID(ids)), + + // TODO: decide whether we want to move these to mutators or not, as in fact + // this operation create a new asset if one isn't found. + getByURL: (url) => findOrCreateAssetByURL(url), + + getByID: new DataLoader((ids) => genAssetsByID(ids)), getAll: new SingletonResolver(() => Asset.find({})) }, Settings: new SingletonResolver(() => Settings.retrieve()) diff --git a/routes/api/graph/mutators.js b/routes/api/graph/mutators.js index e1b65a09e..b56ba8412 100644 --- a/routes/api/graph/mutators.js +++ b/routes/api/graph/mutators.js @@ -13,10 +13,44 @@ const Wordlist = require('../../../services/wordlist'); * @param {String} body body of the comment * @param {String} asset_id asset for the comment * @param {String} parent_id optional parent of the comment - * @param {Object} [wordlist={}] results for the wordlist analysis + * @param {String} [status=null] the status of the new comment * @return {Promise} resolves to the created comment */ -const createComment = ({user}, {body, asset_id, parent_id = null}, wordlist = {}) => { +const createComment = ({user}, {body, asset_id, parent_id = null}, status = null) => { + return Comment.publicCreate({ + body, + asset_id, + parent_id, + status, + author_id: user.id + }); +}; + +/** + * Filters the comment object and outputs wordlist results. + * @param {Object} context graphql context + * @param {String} body body of a comment + * @return {Object} resolves to the wordlist results + */ +const filterNewComment = (context, {body}) => { + + // Create a new instance of the Wordlist. + const wl = new Wordlist(); + + // Load the wordlist and filter the comment content. + return wl.load().then(() => wl.scan('body', body)); +}; + +/** + * This resolves a given comment's status to take into account moderator actions + * are applied. + * @param {Object} context graphql context + * @param {String} body body of the comment + * @param {String} asset_id asset for the comment + * @param {Object} [wordlist={}] the results of the wordlist scan + * @return {Promise} resolves to the comment's status + */ +const resolveNewCommentStatus = (context, {asset_id, body}, wordlist = {}) => { // 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 @@ -55,53 +89,71 @@ const createComment = ({user}, {body, asset_id, parent_id = null}, wordlist = {} }); } - return status.then((status) => Comment.publicCreate({ - body, - asset_id, - parent_id, - status, - author_id: user.id - })) - .then((comment) => { - if (wordlist.suspect) { - return Comment - .addAction(comment.id, null, 'flag', {field: 'body', details: 'Matched suspect word filters.'}) - .then(() => comment); - } - - return comment; - }); + return status; }; /** - * Filters the comment object and outputs wordlist results. - * @param {Object} context graphql context - * @param {String} body body of a comment - * @return {Object} resolves to the wordlist results + * createPublicComment is designed to create a comment from a public source. It + * validates the comment, and performs some automated moderator actions based on + * the settings. + * @param {Object} context the graphql context + * @param {Object} commentInput the new comment to be created + * @return {Promise} resolves to a new comment */ -const filterNewComment = (context, {body}) => { +const createPublicComment = (context, commentInput) => { - // Create a new instance of the Wordlist. - const wl = new Wordlist(); + // First we filter the comment contents to ensure that we note any validation + // issues. + return filterNewComment(context, commentInput) - // Load the wordlist and filter the comment content. - return wl.load().then(() => wl.scan('body', body)); + // We then take the wordlist and the comment into consideration when + // considering what status to assign the new comment, and resolve the new + // status to set the comment to. + .then((wordlist) => resolveNewCommentStatus(context, commentInput, wordlist) + + // Then we actually create the comment with the new status. + .then((status) => createComment(context, commentInput, status)) + .then((comment) => { + + // If the comment was flagged as being suspect, we need to add a + // flag to it to indicate that it needs to be looked at. + // Otherwise just return the new comment. + if (wordlist.suspect) { + + // TODO: this is kind of fragile, we should refactor this to resolve + // all these const's that we're using like 'comments', 'flag' to be + // defined in a checkable schema. + return createAction(null, { + item_id: comment.id, + item_type: 'comments', + action_type: 'flag', + metadata: { + field: 'body', + details: 'Matched suspect word filters.' + } + }).then(() => comment); + } + + // Finally, we return the comment. + return comment; + })); }; /** * Creates an action on a item. - * @param {Object} user the user performing the request + * @param {Object} user the user performing the request * @param {String} item_id id of the item to add the action to * @param {String} item_type type of the item * @param {String} action_type type of the action * @return {Promise} resolves to the action created */ -const createAction = ({user}, {item_id, item_type, action_type}) => { +const createAction = ({user = {}}, {item_id, item_type, action_type, metadata = {}}) => { return Action.insertUserAction({ item_id, item_type, user_id: user.id, - action_type + action_type, + metadata }); }; @@ -120,9 +172,9 @@ const deleteAction = ({user}, {id}) => { /** * Updates a users settings. - * @param {[type]} user [description] - * @param {[type]} bio [description] - * @return {[type]} [description] + * @param {Object} user the user performing the request + * @param {String} bio the new user bio + * @return {Promise} */ const updateUserSettings = ({user}, {bio}) => { return User.updateSettings(user.id, {bio}); @@ -136,9 +188,7 @@ module.exports = (context) => { if (context.user) { return { Comment: { - create: (comment) => filterNewComment(context, comment).then((wordlist) => { - return createComment(context, comment, wordlist); - }) + create: (comment) => createPublicComment(context, comment) }, Action: { create: (action) => createAction(context, action), diff --git a/routes/api/graph/resolvers/index.js b/routes/api/graph/resolvers/index.js index d4de137d5..6cb85a85f 100644 --- a/routes/api/graph/resolvers/index.js +++ b/routes/api/graph/resolvers/index.js @@ -2,8 +2,8 @@ const Action = require('./action'); const ActionSummary = require('./action_summary'); const Asset = require('./asset'); const Comment = require('./comment'); -const Mutation = require('./mutation'); -const Query = require('./Query'); +const RootMutation = require('./root_mutation'); +const RootQuery = require('./root_query'); const User = require('./user'); module.exports = { @@ -11,7 +11,7 @@ module.exports = { ActionSummary, Asset, Comment, - Mutation, - Query, + RootMutation, + RootQuery, User }; diff --git a/routes/api/graph/resolvers/query.js b/routes/api/graph/resolvers/query.js deleted file mode 100644 index 89cbc18c9..000000000 --- a/routes/api/graph/resolvers/query.js +++ /dev/null @@ -1,16 +0,0 @@ -const Query = { - assets(_, args, {loaders}) { - return loaders.Assets.getAll.load(); - }, - asset(_, {id}, {loaders}) { - return loaders.Assets.getByID.load(id); - }, - settings(_, args, {loaders}) { - return loaders.Settings.load(); - }, - me(_, args, {user}) { - return user; - } -}; - -module.exports = Query; diff --git a/routes/api/graph/resolvers/mutation.js b/routes/api/graph/resolvers/root_mutation.js similarity index 88% rename from routes/api/graph/resolvers/mutation.js rename to routes/api/graph/resolvers/root_mutation.js index ef95d0334..bf108671e 100644 --- a/routes/api/graph/resolvers/mutation.js +++ b/routes/api/graph/resolvers/root_mutation.js @@ -1,4 +1,4 @@ -const Mutation = { +const RootMutation = { createComment(_, {asset_id, parent_id, body}, {mutators}) { return mutators.Comment.create({asset_id, parent_id, body}); }, @@ -13,4 +13,4 @@ const Mutation = { } }; -module.exports = Mutation; +module.exports = RootMutation; diff --git a/routes/api/graph/resolvers/root_query.js b/routes/api/graph/resolvers/root_query.js new file mode 100644 index 000000000..4042ddb21 --- /dev/null +++ b/routes/api/graph/resolvers/root_query.js @@ -0,0 +1,25 @@ +const RootQuery = { + assets(_, args, {loaders}) { + return loaders.Assets.getAll.load(); + }, + asset(_, {id = null, url}, {loaders}) { + if (id) { + + // TODO: we may not always have a comment stream here, therefore, when we + // load it, we may also need to create with the url. This may also have to + // move the logic over to the mutators function as an upsert operation + // possibly. + return loaders.Assets.getByID.load(id); + } else { + return loaders.Assets.getByURL(url); + } + }, + settings(_, args, {loaders}) { + return loaders.Settings.load(); + }, + me(_, args, {user}) { + return user; + } +}; + +module.exports = RootQuery; diff --git a/routes/api/graph/typeDefs.js b/routes/api/graph/typeDefs.js index 5bacb1a2b..8e80f37a5 100644 --- a/routes/api/graph/typeDefs.js +++ b/routes/api/graph/typeDefs.js @@ -71,10 +71,12 @@ type Asset { currentUser: User } -type Query { +scalar URL + +type RootQuery { settings: Settings assets: [Asset] - asset(id: ID!): Asset + asset(id: ID, url: URL!): Asset me: User } @@ -94,7 +96,7 @@ input UpdateUserSettingsInput { bio: String! } -type Mutation { +type RootMutation { # creates a comment on the asset. createComment(asset_id: ID!, parent_id: ID, body: String!): Comment @@ -109,8 +111,8 @@ type Mutation { } schema { - query: Query - mutation: Mutation + query: RootQuery + mutation: RootMutation } `]; From f286263f1f06ecdb0fe350e908c95484a366498c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 19 Jan 2017 18:26:33 -0700 Subject: [PATCH 021/107] Adjusted placement of server side graph code. --- app.js | 19 ++++++++++- graph/index.js | 24 ++++++++++++++ {routes/api/graph => graph}/loaders.js | 14 ++++---- {routes/api/graph => graph}/mutators.js | 12 +++---- .../api/graph => graph}/resolvers/action.js | 0 .../resolvers/action_summary.js | 0 .../api/graph => graph}/resolvers/asset.js | 0 .../api/graph => graph}/resolvers/comment.js | 0 .../api/graph => graph}/resolvers/index.js | 0 .../resolvers/root_mutation.js | 0 .../graph => graph}/resolvers/root_query.js | 0 {routes/api/graph => graph}/resolvers/user.js | 0 {routes/api/graph => graph}/schema.js | 0 {routes/api/graph => graph}/typeDefs.js | 6 +++- routes/api/graph/index.js | 33 ------------------- 15 files changed, 60 insertions(+), 48 deletions(-) create mode 100644 graph/index.js rename {routes/api/graph => graph}/loaders.js (92%) rename {routes/api/graph => graph}/mutators.js (95%) rename {routes/api/graph => graph}/resolvers/action.js (100%) rename {routes/api/graph => graph}/resolvers/action_summary.js (100%) rename {routes/api/graph => graph}/resolvers/asset.js (100%) rename {routes/api/graph => graph}/resolvers/comment.js (100%) rename {routes/api/graph => graph}/resolvers/index.js (100%) rename {routes/api/graph => graph}/resolvers/root_mutation.js (100%) rename {routes/api/graph => graph}/resolvers/root_query.js (100%) rename {routes/api/graph => graph}/resolvers/user.js (100%) rename {routes/api/graph => graph}/schema.js (100%) rename {routes/api/graph => graph}/typeDefs.js (88%) delete mode 100644 routes/api/graph/index.js diff --git a/app.js b/app.js index acc262aad..9aa420e0e 100644 --- a/app.js +++ b/app.js @@ -10,6 +10,8 @@ const RedisStore = require('connect-redis')(session); const redis = require('./services/redis'); const csrf = require('csurf'); const errors = require('./errors'); +const graph = require('./graph'); +const apollo = require('graphql-server-express'); const app = express(); @@ -77,7 +79,22 @@ app.use(session(session_opts)); app.use(passport.initialize()); app.use(passport.session()); -app.use('/api/v1/graph', require('./routes/api/graph')); +//============================================================================== +// GraphQL Router +//============================================================================== + +// GraphQL endpoint. +app.use('/api/v1/graph/ql', apollo.graphqlExpress(graph.createGraphOptions)); + +// Only include the graphiql tool if we aren't in production mode. +if (app.get('env') !== 'production') { + + // Interactive graphiql interface. + app.use('/api/v1/graph/iql', apollo.graphiqlExpress({ + endpointURL: '/api/v1/graph/ql' + })); + +} //============================================================================== // CSRF MIDDLEWARE diff --git a/graph/index.js b/graph/index.js new file mode 100644 index 000000000..514df2d0a --- /dev/null +++ b/graph/index.js @@ -0,0 +1,24 @@ +const loaders = require('./loaders'); +const mutators = require('./mutators'); +const schema = require('./schema'); + +module.exports = { + createGraphOptions: (req) => { + + let context = {}; + + // Load the current logged in user to `user`, otherwise this'll be null. + context.user = req.user; + + // Create the loaders. + context.loaders = loaders(context); + + // Create the mutators. + context.mutators = mutators(context); + + return { + schema, + context + }; + } +}; diff --git a/routes/api/graph/loaders.js b/graph/loaders.js similarity index 92% rename from routes/api/graph/loaders.js rename to graph/loaders.js index 0bf11fcc8..4388af4a8 100644 --- a/routes/api/graph/loaders.js +++ b/graph/loaders.js @@ -1,14 +1,14 @@ const DataLoader = require('dataloader'); const _ = require('lodash'); const url = require('url'); -const errors = require('../../../errors'); -const scraper = require('../../../services/scraper'); +const errors = require('../errors'); +const scraper = require('../services/scraper'); -const Comment = require('../../../models/comment'); -const User = require('../../../models/user'); -const Action = require('../../../models/action'); -const Asset = require('../../../models/asset'); -const Settings = require('../../../models/setting'); +const Comment = require('../models/comment'); +const User = require('../models/user'); +const Action = require('../models/action'); +const Asset = require('../models/asset'); +const Settings = require('../models/setting'); /** * SingletonResolver is a cached loader for a single result. diff --git a/routes/api/graph/mutators.js b/graph/mutators.js similarity index 95% rename from routes/api/graph/mutators.js rename to graph/mutators.js index b56ba8412..11e727fad 100644 --- a/routes/api/graph/mutators.js +++ b/graph/mutators.js @@ -1,11 +1,11 @@ -const errors = require('../../../errors'); +const errors = require('../errors'); -const Action = require('../../../models/action'); -const Asset = require('../../../models/asset'); -const Comment = require('../../../models/comment'); -const User = require('../../../models/user'); +const Action = require('../models/action'); +const Asset = require('../models/asset'); +const Comment = require('../models/comment'); +const User = require('../models/user'); -const Wordlist = require('../../../services/wordlist'); +const Wordlist = require('../services/wordlist'); /** * Creates a new comment. diff --git a/routes/api/graph/resolvers/action.js b/graph/resolvers/action.js similarity index 100% rename from routes/api/graph/resolvers/action.js rename to graph/resolvers/action.js diff --git a/routes/api/graph/resolvers/action_summary.js b/graph/resolvers/action_summary.js similarity index 100% rename from routes/api/graph/resolvers/action_summary.js rename to graph/resolvers/action_summary.js diff --git a/routes/api/graph/resolvers/asset.js b/graph/resolvers/asset.js similarity index 100% rename from routes/api/graph/resolvers/asset.js rename to graph/resolvers/asset.js diff --git a/routes/api/graph/resolvers/comment.js b/graph/resolvers/comment.js similarity index 100% rename from routes/api/graph/resolvers/comment.js rename to graph/resolvers/comment.js diff --git a/routes/api/graph/resolvers/index.js b/graph/resolvers/index.js similarity index 100% rename from routes/api/graph/resolvers/index.js rename to graph/resolvers/index.js diff --git a/routes/api/graph/resolvers/root_mutation.js b/graph/resolvers/root_mutation.js similarity index 100% rename from routes/api/graph/resolvers/root_mutation.js rename to graph/resolvers/root_mutation.js diff --git a/routes/api/graph/resolvers/root_query.js b/graph/resolvers/root_query.js similarity index 100% rename from routes/api/graph/resolvers/root_query.js rename to graph/resolvers/root_query.js diff --git a/routes/api/graph/resolvers/user.js b/graph/resolvers/user.js similarity index 100% rename from routes/api/graph/resolvers/user.js rename to graph/resolvers/user.js diff --git a/routes/api/graph/schema.js b/graph/schema.js similarity index 100% rename from routes/api/graph/schema.js rename to graph/schema.js diff --git a/routes/api/graph/typeDefs.js b/graph/typeDefs.js similarity index 88% rename from routes/api/graph/typeDefs.js rename to graph/typeDefs.js index 8e80f37a5..4ce95fa77 100644 --- a/routes/api/graph/typeDefs.js +++ b/graph/typeDefs.js @@ -1,3 +1,7 @@ +// TODO: Adjust `RootQuery.asset(id: ID, url: URL)` to instead be +// `RootQuery.asset(id: ID, url: URL!)` because we'll always need the url, if +// this change is done now everything will likely break on the front end. + const typeDefs = [` type UserSettings { bio: String @@ -76,7 +80,7 @@ scalar URL type RootQuery { settings: Settings assets: [Asset] - asset(id: ID, url: URL!): Asset + asset(id: ID, url: URL): Asset me: User } diff --git a/routes/api/graph/index.js b/routes/api/graph/index.js deleted file mode 100644 index 55f177aa9..000000000 --- a/routes/api/graph/index.js +++ /dev/null @@ -1,33 +0,0 @@ -const express = require('express'); -const apollo = require('graphql-server-express'); - -const loaders = require('./loaders'); -const mutators = require('./mutators'); -const schema = require('./schema'); - -const router = express.Router(); - -// GraphQL endpoint. -router.use('/ql', apollo.graphqlExpress((req) => { - - let context = {}; - - // Load the current logged in user to `user`, otherwise this'll be null. - context.user = req.user; - - // Create the loaders. - context.loaders = loaders(context); - - // Create the mutators. - context.mutators = mutators(context); - - return { - schema, - context - }; -})); - -// Interactive graphiql interface. -router.use('/iql', apollo.graphiqlExpress({endpointURL: '/api/v1/graph/ql'})); - -module.exports = router; From 07b2f9c2e5c65dc8e815dac9de022765792173f6 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 20 Jan 2017 08:13:45 -0700 Subject: [PATCH 022/107] Update typeDefs.js --- graph/typeDefs.js | 1 - 1 file changed, 1 deletion(-) diff --git a/graph/typeDefs.js b/graph/typeDefs.js index 4ce95fa77..0d1b3a259 100644 --- a/graph/typeDefs.js +++ b/graph/typeDefs.js @@ -72,7 +72,6 @@ type Asset { url: String comments: [Comment] settings: Settings! - currentUser: User } scalar URL From cc740a4a5e223278e7ae771aa789facce2647992 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 12:52:35 -0300 Subject: [PATCH 023/107] Adding closedAt status commentStream --- .../coral-embed-stream/src/CommentStream.js | 19 +++++++++---------- graph/typeDefs.js | 1 + 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index c46785ec6..f1eb8743b 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -6,8 +6,7 @@ import gql from 'graphql-tag'; import { itemActions, - - // Notification, + Notification, notificationActions, authActions } from '../../coral-framework'; @@ -38,7 +37,7 @@ const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appen const {addNotification, clearNotification} = notificationActions; const {logout, showSignInDialog} = authActions; -const assetID = 'be97065d-f0eb-44a8-8efb-b416229afdc3'; +const assetID = '4807a33a-894a-4351-8eb5-a8e0091af568'; class CommentStream extends Component { @@ -128,7 +127,7 @@ class CommentStream extends Component { {currentUser && } { - status === 'open' + asset.closedAt === null ?
+ } { @@ -365,6 +363,7 @@ query AssetQuery($asset_id: ID!) { id title url + closedAt settings { moderation infoBoxEnable diff --git a/graph/typeDefs.js b/graph/typeDefs.js index 0d1b3a259..5b7bbaa60 100644 --- a/graph/typeDefs.js +++ b/graph/typeDefs.js @@ -72,6 +72,7 @@ type Asset { url: String comments: [Comment] settings: Settings! + closedAt: String } scalar URL From 7a5aa0a80c1d2585d3d599c138f5bfeb3202f64e Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 15:25:18 -0300 Subject: [PATCH 024/107] Changes --- .../coral-embed-stream/src/CommentStream.js | 71 +++++++++++-------- client/coral-framework/actions/items.js | 48 ++++++++++--- client/coral-framework/store.js | 7 ++ 3 files changed, 87 insertions(+), 39 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index f1eb8743b..4750f5880 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -1,6 +1,6 @@ import React, {Component, PropTypes} from 'react'; import Pym from 'pym.js'; -import {graphql} from 'react-apollo'; +import {graphql, compose} from 'react-apollo'; import {connect} from 'react-redux'; import gql from 'graphql-tag'; @@ -59,7 +59,7 @@ class CommentStream extends Component { } static propTypes = { - data: PropTypes.object.isRequired, + data: PropTypes.object.isRequired } componentDidMount () { @@ -324,24 +324,29 @@ class CommentStream extends Component { } const mapStateToProps = state => (state); +const mapDispatchToProps = (dispatch, ownProps) => ({}); -const mapDispatchToProps = (dispatch) => ({ - addItem: (item, item_id) => dispatch(addItem(item, item_id)), - updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), - postItem: (data, type, id) => dispatch(postItem(data, type, id)), - getStream: (rootId) => dispatch(getStream(rootId)), - addNotification: (type, text) => dispatch(addNotification(type, text)), - clearNotification: () => dispatch(clearNotification()), - postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)), - showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), - deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), - appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), - handleSignInDialog: () => dispatch(authActions.showSignInDialog()), - logout: () => dispatch(logout()), -}); +// const mapDispatchToProps = (dispatch, ownProps) => ({ +// addItem: (item, item_id) => dispatch(addItem(item, item_id)), +// updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), +// postItem: (data, type, id) => { +// console.log('postItem', dispatch, ownProps) +// // dispatch(postItem(data, type, id, ownProps)) +// }, +// getStream: (rootId) => dispatch(getStream(rootId)), +// addNotification: (type, text) => dispatch(addNotification(type, text)), +// clearNotification: () => dispatch(clearNotification()), +// postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)), +// showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), +// deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), +// appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), +// handleSignInDialog: () => dispatch(authActions.showSignInDialog()), +// logout: () => dispatch(logout()), +// }); // Initialize GraphQL queries or mutations with the `gql` tag -const StreamQuery = gql`fragment commentView on Comment { +const StreamQuery = gql` +fragment commentView on Comment { id body user { @@ -385,19 +390,27 @@ query AssetQuery($asset_id: ID!) { id, displayName } -}`; +} +`; -const CommentStreamWithData = graphql( - StreamQuery, { - options: { - variables: { - asset_id: assetID +const postComment = gql` + mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { + createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { + ...commentView } } - } -)(CommentStream); +`; -export default connect( - mapStateToProps, - mapDispatchToProps -)(CommentStreamWithData); +export default compose( + graphql(StreamQuery, { + options: { variables: { asset_id: assetID } }, + props: props => props, + }), + graphql(postComment, { + options: { variables: { asset_id: assetID } }, + props: ({ownProps, mutate}) => ({ + postComment: (data) => mutate(data) + }), + }), + connect(mapStateToProps, mapDispatchToProps) +)(CommentStream); diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index 48418b2a2..78e3f64b7 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -2,6 +2,8 @@ import coralApi from '../helpers/response'; import {fromJS} from 'immutable'; import {UPDATE_CONFIG} from '../constants/config'; +import gql from 'graphql-tag'; + /** * Action name constants */ @@ -189,17 +191,43 @@ export function getItemsArray (ids) { * The newly put item to the item store */ -export function postItem (item, type, id) { - return (dispatch) => { - if (id) { - item.id = id; + +const postComment = gql` + mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { + createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { + ...commentView } - return coralApi(`/${type}`, {method: 'POST', body: item}) - .then((json) => { - dispatch(addItem({...item, id:json.id}, type)); - return json; - }); - }; + } +`; + +export function postItem (item, type, id, mutate) { + console.log( + item, + type, + id, + mutate + ) + mutate({ + variables: { + asset_id: id, + body: item, + parent_id: null + } + }).then(({data}) => { + console.log('it workt'); + console.log(data); + }); + + // return (dispatch) => { + // if (id) { + // item.id = id; + // } + // return coralApi(`/${type}`, {method: 'POST', body: item}) + // .then((json) => { + // dispatch(addItem({...item, id:json.id}, type)); + // return json; + // }); + // }; } /* diff --git a/client/coral-framework/store.js b/client/coral-framework/store.js index 9b9ecfb21..af942ecdd 100644 --- a/client/coral-framework/store.js +++ b/client/coral-framework/store.js @@ -8,6 +8,13 @@ export default createStore( auth: authReducer, apollo: client.reducer() }), + { + apollo: { + data: { + loading: true, + } + } + }, compose( applyMiddleware(thunk), window.devToolsExtension && window.devToolsExtension() From c16f3b8fbeb4a110bc3a1a261cf66b6415bfce93 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 20 Jan 2017 11:54:18 -0700 Subject: [PATCH 025/107] change scalar URL type for a bit --- .../coral-embed-stream/src/CommentStream.js | 23 +++++++++++-------- graph/typeDefs.js | 4 +--- 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 4750f5880..9f08a2259 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -37,8 +37,6 @@ const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appen const {addNotification, clearNotification} = notificationActions; const {logout, showSignInDialog} = authActions; -const assetID = '4807a33a-894a-4351-8eb5-a8e0091af568'; - class CommentStream extends Component { constructor (props) { @@ -64,10 +62,10 @@ class CommentStream extends Component { componentDidMount () { - // Set up messaging between embedded Iframe an parent component - this.pym = new Pym.Child({polling: 100}); + return; + + // Set up messaging between embedded Iframe an parent component - let path = this.pym.parentUrl.split('#')[0]; if (!path) { path = window.location.href.split('#')[0]; @@ -363,8 +361,8 @@ fragment commentView on Comment { } } -query AssetQuery($asset_id: ID!) { - asset(id: $asset_id) { +query AssetQuery($asset_url: String!) { + asset(url: $asset_url) { id title url @@ -401,13 +399,20 @@ const postComment = gql` } `; +const pym = new Pym.Child({polling: 100}); + +let url = pym.parentUrl; + +console.log('pym.parentUrl', url); + export default compose( graphql(StreamQuery, { - options: { variables: { asset_id: assetID } }, + // pass in the assetURL at componentDidMount + options: { variables: { asset_url: url } }, props: props => props, }), graphql(postComment, { - options: { variables: { asset_id: assetID } }, + options: { variables: { asset_url: url } }, props: ({ownProps, mutate}) => ({ postComment: (data) => mutate(data) }), diff --git a/graph/typeDefs.js b/graph/typeDefs.js index 5b7bbaa60..aadc7f57d 100644 --- a/graph/typeDefs.js +++ b/graph/typeDefs.js @@ -75,12 +75,10 @@ type Asset { closedAt: String } -scalar URL - type RootQuery { settings: Settings assets: [Asset] - asset(id: ID, url: URL): Asset + asset(id: ID, url: String): Asset me: User } From d14fa33077000c73b0b28e7a95d8726e9f3ce72e Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 20 Jan 2017 12:36:33 -0700 Subject: [PATCH 026/107] rename files to make more sense --- client/coral-embed-stream/src/Comment.js | 42 ++++++ .../src/{CommentStream.js => Embed.js} | 141 ++---------------- client/coral-embed-stream/src/Stream.js | 0 client/coral-embed-stream/src/index.js | 4 +- .../CommentCount.js | 6 +- 5 files changed, 58 insertions(+), 135 deletions(-) create mode 100644 client/coral-embed-stream/src/Comment.js rename client/coral-embed-stream/src/{CommentStream.js => Embed.js} (56%) create mode 100644 client/coral-embed-stream/src/Stream.js diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js new file mode 100644 index 000000000..d13729f8f --- /dev/null +++ b/client/coral-embed-stream/src/Comment.js @@ -0,0 +1,42 @@ +// this component will +// render its children +// render a like button +// render a permalink button +// render a reply button +// render a flag button +// translate things? + +import React, {PropTypes} from 'react'; +import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton'; + +const Comment = ({comment}) => { + console.log('A Comment', comment); + return ( +
+
+ {comment.body} + +
+ ); +}; + +Comment.propTypes = { + comment: PropTypes.shape({ + depth: PropTypes.number, + actions: PropTypes.array.isRequired, + body: PropTypes.string.isRequired, + id: PropTypes.string.isRequired, + replies: PropTypes.arrayOf( + PropTypes.shape({ + body: PropTypes.string.isRequired, + id: PropTypes.string.isRequired + }) + ), + user: PropTypes.shape({ + id: PropTypes.string.isRequired, + name: PropTypes.string.isRequired + }).isRequired + }).isRequired +}; + +export default Comment; diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/Embed.js similarity index 56% rename from client/coral-embed-stream/src/CommentStream.js rename to client/coral-embed-stream/src/Embed.js index 9f08a2259..32f4628d4 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/Embed.js @@ -11,6 +11,8 @@ import { authActions } from '../../coral-framework'; +import Comment from './Comment'; + import CommentBox from '../../coral-plugin-commentbox/CommentBox'; import InfoBox from '../../coral-plugin-infobox/InfoBox'; import Content from '../../coral-plugin-commentcontent/CommentContent'; @@ -37,7 +39,7 @@ const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appen const {addNotification, clearNotification} = notificationActions; const {logout, showSignInDialog} = authActions; -class CommentStream extends Component { +class Embed extends Component { constructor (props) { super(props); @@ -57,11 +59,12 @@ class CommentStream extends Component { } static propTypes = { - data: PropTypes.object.isRequired + data: PropTypes.object.isRequired, + } componentDidMount () { - + // stream id, logged in user, settings return; // Set up messaging between embedded Iframe an parent component @@ -154,134 +157,8 @@ class CommentStream extends Component { showSignInDialog={showSignInDialog}/> } { - asset.comments.map((comment) => { - return
-
- - - -
- - -
-
- - -
- - { - comment.replies.map((reply) => { - return
-
- - - -
- - -
-
- - -
- { - - } -
; - }) - } -
; + asset.comments.map(comment => { + return ; }) } { @@ -418,4 +295,4 @@ export default compose( }), }), connect(mapStateToProps, mapDispatchToProps) -)(CommentStream); +)(Embed); diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js new file mode 100644 index 000000000..e69de29bb diff --git a/client/coral-embed-stream/src/index.js b/client/coral-embed-stream/src/index.js index 67b4bad29..f75146f0a 100644 --- a/client/coral-embed-stream/src/index.js +++ b/client/coral-embed-stream/src/index.js @@ -5,11 +5,11 @@ import {ApolloProvider} from 'react-apollo'; import {client} from 'coral-framework/client'; import store from 'coral-framework/store'; -import Stream from './CommentStream'; +import Embed from './Embed'; render( - + , document.querySelector('#coralStream') ); diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index 623274e53..764984634 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -1,4 +1,4 @@ -import React from 'react'; +import React, {PropTypes} from 'react'; import {I18n} from '../coral-framework'; import translations from './translations.json'; const name = 'coral-plugin-comment-count'; @@ -9,6 +9,10 @@ const CommentCount = ({count}) => {
; }; +CommentCount.propTypes = { + count: PropTypes.number.isRequired +}; + export default CommentCount; const lang = new I18n(translations); From f0f692fc26d91c2002f1740c7c99b1569f572f78 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 16:39:23 -0300 Subject: [PATCH 027/107] =?UTF-8?q?=C3=A1dding=20webpack=20loader=20and=20?= =?UTF-8?q?post=20comment?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../coral-embed-stream/src/CommentStream.js | 39 ++++++++++++++----- webpack.config.dev.js | 5 +++ 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/client/coral-embed-stream/src/CommentStream.js b/client/coral-embed-stream/src/CommentStream.js index 9f08a2259..d4b0b413e 100644 --- a/client/coral-embed-stream/src/CommentStream.js +++ b/client/coral-embed-stream/src/CommentStream.js @@ -33,7 +33,7 @@ import SuspendedAccount from '../../coral-framework/components/SuspendedAccount' // import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer'; -const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; +// const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; const {logout, showSignInDialog} = authActions; @@ -61,12 +61,9 @@ class CommentStream extends Component { } componentDidMount () { - return; // Set up messaging between embedded Iframe an parent component - - if (!path) { path = window.location.href.split('#')[0]; } @@ -137,7 +134,7 @@ class CommentStream extends Component { postItem={this.props.postItem} appendItemArray={this.props.appendItemArray} updateItem={this.props.updateItem} - id={asset.settings.rootItemId} + id={asset.id} premod={asset.settings.moderation} reply={false} currentUser={this.props.auth.user} @@ -392,6 +389,22 @@ query AssetQuery($asset_url: String!) { `; const postComment = gql` + fragment commentView on Comment { + id + body + user { + name: displayName + } + actions { + type: action_type + count + current: current_user { + id + created_at + } + } + } + mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { ...commentView @@ -407,15 +420,23 @@ console.log('pym.parentUrl', url); export default compose( graphql(StreamQuery, { - // pass in the assetURL at componentDidMount options: { variables: { asset_url: url } }, props: props => props, }), graphql(postComment, { - options: { variables: { asset_url: url } }, props: ({ownProps, mutate}) => ({ - postComment: (data) => mutate(data) - }), + postItem: ({asset_id, author_id, body}) => { + mutate({ + variables: { + asset_id, + body, + parent_id: null + } + }).then(({data}) => { + console.log('it workt'); + console.log(data); + }); + }}), }), connect(mapStateToProps, mapDispatchToProps) )(CommentStream); diff --git a/webpack.config.dev.js b/webpack.config.dev.js index 913ee35da..539233bef 100644 --- a/webpack.config.dev.js +++ b/webpack.config.dev.js @@ -70,6 +70,11 @@ module.exports = { { loader: 'url?limit=100000', test: /\.woff$/ + }, + { + test: /\.(graphql|gql)$/, + exclude: /node_modules/, + loader: 'graphql-tag/loader' } ] }, From 8f95e2931534a68829e7b5f610d725428a8acdae Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 20 Jan 2017 12:41:33 -0700 Subject: [PATCH 028/107] Stream is a separate component that renders comments --- client/coral-embed-stream/src/Embed.js | 8 ++------ client/coral-embed-stream/src/Stream.js | 20 ++++++++++++++++++++ 2 files changed, 22 insertions(+), 6 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 32f4628d4..69adcfed1 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -11,7 +11,7 @@ import { authActions } from '../../coral-framework'; -import Comment from './Comment'; +import Stream from './Stream'; import CommentBox from '../../coral-plugin-commentbox/CommentBox'; import InfoBox from '../../coral-plugin-infobox/InfoBox'; @@ -156,11 +156,7 @@ class Embed extends Component { refetch={refetch} showSignInDialog={showSignInDialog}/> } - { - asset.comments.map(comment => { - return ; - }) - } + { { + return ( +
+ { + comments.map(comment => { + return ; + }) + } +
+ ); +}; + +Stream.propTypes = { + comments: PropTypes.array.isRequired +}; + +export default Stream; From ae29d65c7578b18a9c0135471c6f678a07523015 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 17:40:33 -0300 Subject: [PATCH 029/107] mutations and queries in separate folders :tada: --- client/coral-embed-stream/src/Embed.js | 176 +++++------------- .../src/graphql/mutations/postComment.graphql | 22 +++ .../src/graphql/queries/streamQuery.graphql | 46 +++++ client/coral-framework/reducers/index.js | 10 +- client/coral-framework/store.js | 4 +- 5 files changed, 122 insertions(+), 136 deletions(-) create mode 100644 client/coral-embed-stream/src/graphql/mutations/postComment.graphql create mode 100644 client/coral-embed-stream/src/graphql/queries/streamQuery.graphql diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 6af6f2636..bd056e314 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -29,11 +29,11 @@ import UserBox from '../../coral-sign-in/components/UserBox'; import {TabBar, Tab, TabContent, Spinner} from '../../coral-ui'; -// import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; +import SettingsContainer from '../../coral-settings/containers/SettingsContainer'; import RestrictedContent from '../../coral-framework/components/RestrictedContent'; import SuspendedAccount from '../../coral-framework/components/SuspendedAccount'; -// import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer'; +import ConfigureStreamContainer from '../../coral-configure/containers/ConfigureStreamContainer'; // const {addItem, updateItem, postItem, getStream, postAction, deleteAction, appendItemArray} = itemActions; const {addNotification, clearNotification} = notificationActions; @@ -100,7 +100,7 @@ class Embed extends Component { // const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; // const {actions, users, comments} = this.props.items; - const {isAdmin, showSignInDialog} = this.props.auth; + const {isAdmin, showSignInDialog, loggedIn} = this.props.auth; const {activeTab} = this.state; // const banned = (this.props.userData.status === 'banned'); @@ -154,37 +154,32 @@ class Embed extends Component { showSignInDialog={showSignInDialog}/> } - { - - } + {/**/}
- { - - // - // - // - // - // - // - // - // - // - } + + + + + + + + + {/**/}
} ; @@ -192,111 +187,40 @@ class Embed extends Component { } const mapStateToProps = state => (state); -const mapDispatchToProps = (dispatch, ownProps) => ({}); -// const mapDispatchToProps = (dispatch, ownProps) => ({ -// addItem: (item, item_id) => dispatch(addItem(item, item_id)), -// updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), -// postItem: (data, type, id) => { -// console.log('postItem', dispatch, ownProps) -// // dispatch(postItem(data, type, id, ownProps)) -// }, -// getStream: (rootId) => dispatch(getStream(rootId)), -// addNotification: (type, text) => dispatch(addNotification(type, text)), -// clearNotification: () => dispatch(clearNotification()), -// postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)), -// showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), -// deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), -// appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), -// handleSignInDialog: () => dispatch(authActions.showSignInDialog()), -// logout: () => dispatch(logout()), -// }); +const mapDispatchToProps = (dispatch, ownProps) => ({ + // addItem: (item, item_id) => dispatch(addItem(item, item_id)), + // updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), + // postItem: (data, type, id) => { + // console.log('postItem', dispatch, ownProps) + // // dispatch(postItem(data, type, id, ownProps)) + // }, + // getStream: (rootId) => dispatch(getStream(rootId)), + addNotification: (type, text) => dispatch(addNotification(type, text)), + clearNotification: () => dispatch(clearNotification()), + // postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)), + showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), + // deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), + // appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), + // handleSignInDialog: () => dispatch(authActions.showSignInDialog()), + logout: () => dispatch(logout()), +}); // Initialize GraphQL queries or mutations with the `gql` tag -const StreamQuery = gql` -fragment commentView on Comment { - id - body - user { - id - name: displayName - } - actions { - type: action_type - count - current: current_user { - id - created_at - } - } -} - -query AssetQuery($asset_url: String!) { - asset(url: $asset_url) { - id - title - url - closedAt - settings { - moderation - infoBoxEnable - infoBoxContent - closeTimeout - closedMessage - charCountEnable - charCount - requireEmailConfirmation - } - comments { - ...commentView - replies { - ...commentView - } - } - }, - currentUser: me { - id, - displayName - } -} -`; - -const postComment = gql` - fragment commentView on Comment { - id - body - user { - name: displayName - } - actions { - type: action_type - count - current: current_user { - id - created_at - } - } - } - - mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { - createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { - ...commentView - } - } -`; - const pym = new Pym.Child({polling: 100}); let url = pym.parentUrl; -console.log('pym.parentUrl', url); +import STREAM_QUERY from './graphql/queries/streamQuery.graphql'; +import POST_COMMENT from './graphql/mutations/postComment.graphql'; +console.log(STREAM_QUERY, POST_COMMENT) export default compose( - graphql(StreamQuery, { + graphql(STREAM_QUERY, { options: { variables: { asset_url: url } }, props: props => props, }), - graphql(postComment, { + graphql(POST_COMMENT, { props: ({ownProps, mutate}) => ({ postItem: ({asset_id, author_id, body}) => { mutate({ diff --git a/client/coral-embed-stream/src/graphql/mutations/postComment.graphql b/client/coral-embed-stream/src/graphql/mutations/postComment.graphql new file mode 100644 index 000000000..e06c4f55c --- /dev/null +++ b/client/coral-embed-stream/src/graphql/mutations/postComment.graphql @@ -0,0 +1,22 @@ + + fragment commentView on Comment { + id + body + user { + name: displayName + } + actions { + type: action_type + count + current: current_user { + id + created_at + } + } + } + +mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { + createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { + ...commentView + } +} diff --git a/client/coral-embed-stream/src/graphql/queries/streamQuery.graphql b/client/coral-embed-stream/src/graphql/queries/streamQuery.graphql new file mode 100644 index 000000000..7ea7fbdb9 --- /dev/null +++ b/client/coral-embed-stream/src/graphql/queries/streamQuery.graphql @@ -0,0 +1,46 @@ + +fragment commentView on Comment { + id + body + user { + id + name: displayName + } + actions { + type: action_type + count + current: current_user { + id + created_at + } + } +} + +query AssetQuery($asset_url: String!) { + asset(url: $asset_url) { + id + title + url + closedAt + settings { + moderation + infoBoxEnable + infoBoxContent + closeTimeout + closedMessage + charCountEnable + charCount + requireEmailConfirmation + } + comments { + ...commentView + replies { + ...commentView + } + } + }, + currentUser: me { + id, + displayName + } +} diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js index a2f439028..e406a5524 100644 --- a/client/coral-framework/reducers/index.js +++ b/client/coral-framework/reducers/index.js @@ -1,5 +1,3 @@ -/* @flow */ - import {combineReducers} from 'redux'; import config from './config'; import items from './items'; @@ -7,14 +5,10 @@ import notification from './notification'; import auth from './auth'; import user from './user'; -/** - * Expose the combined main reducer - */ - -export default combineReducers({ +export default { config, items, notification, auth, user -}); +}; diff --git a/client/coral-framework/store.js b/client/coral-framework/store.js index af942ecdd..1bcfeddec 100644 --- a/client/coral-framework/store.js +++ b/client/coral-framework/store.js @@ -1,11 +1,11 @@ import {createStore, combineReducers, applyMiddleware, compose} from 'redux'; import thunk from 'redux-thunk'; -import authReducer from './reducers/auth'; +import mainReducer from './reducers'; import {client} from './client'; export default createStore( combineReducers({ - auth: authReducer, + ...mainReducer, apollo: client.reducer() }), { From 2615db8575a438807892fae96b9b54ae2f33cdeb Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 17:59:28 -0300 Subject: [PATCH 030/107] mutations and queries, lint errors --- client/coral-embed-stream/src/Comment.js | 1 - client/coral-embed-stream/src/Embed.js | 107 ++++++++++++----------- client/coral-embed-stream/src/Stream.js | 2 +- client/coral-framework/actions/items.js | 13 +-- client/coral-framework/reducers/index.js | 1 - graph/mutators.js | 2 +- 6 files changed, 57 insertions(+), 69 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index d13729f8f..71fe3723f 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -10,7 +10,6 @@ import React, {PropTypes} from 'react'; import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton'; const Comment = ({comment}) => { - console.log('A Comment', comment); return (

diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index bd056e314..a72d8c1ad 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -2,11 +2,14 @@ import React, {Component, PropTypes} from 'react'; import Pym from 'pym.js'; import {graphql, compose} from 'react-apollo'; import {connect} from 'react-redux'; -import gql from 'graphql-tag'; + +import STREAM_QUERY from './graphql/queries/streamQuery.graphql'; +import POST_COMMENT from './graphql/mutations/postComment.graphql'; import { - itemActions, - Notification, + + // itemActions, + // Notification, notificationActions, authActions } from '../../coral-framework'; @@ -15,14 +18,16 @@ import Stream from './Stream'; import CommentBox from '../../coral-plugin-commentbox/CommentBox'; import InfoBox from '../../coral-plugin-infobox/InfoBox'; -import Content from '../../coral-plugin-commentcontent/CommentContent'; -import PubDate from '../../coral-plugin-pubdate/PubDate'; + +// import Content from '../../coral-plugin-commentcontent/CommentContent'; +// import PubDate from '../../coral-plugin-pubdate/PubDate'; import Count from '../../coral-plugin-comment-count/CommentCount'; -import AuthorName from '../../coral-plugin-author-name/AuthorName'; -import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; -import FlagComment from '../../coral-plugin-flags/FlagComment'; -import LikeButton from '../../coral-plugin-likes/LikeButton'; -import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; + +// import AuthorName from '../../coral-plugin-author-name/AuthorName'; +// import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; +// import FlagComment from '../../coral-plugin-flags/FlagComment'; +// import LikeButton from '../../coral-plugin-likes/LikeButton'; +// import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; import UserBox from '../../coral-sign-in/components/UserBox'; @@ -63,35 +68,32 @@ class Embed extends Component { } componentDidMount () { + // stream id, logged in user, settings - return; // Set up messaging between embedded Iframe an parent component - if (!path) { - path = window.location.href.split('#')[0]; - } // this.props.getStream(path || window.location); - this.path = path; - - this.pym.sendMessage('childReady'); - - this.pym.onMessage('DOMContentLoaded', hash => { - const commentId = hash.replace('#', 'c_'); - let count = 0; - const interval = setInterval(() => { - if (document.getElementById(commentId)) { - window.clearInterval(interval); - this.pym.scrollParentToChildEl(commentId); - } - - if (++count > 100) { // ~10 seconds - // give up waiting for the comments to load. - // it would be weird for the page to jump after that long. - window.clearInterval(interval); - } - }, 100); - }); + // this.path = window.location.href.split('#')[0]; + // + // this.pym.sendMessage('childReady'); + // + // this.pym.onMessage('DOMContentLoaded', hash => { + // const commentId = hash.replace('#', 'c_'); + // let count = 0; + // const interval = setInterval(() => { + // if (document.getElementById(commentId)) { + // window.clearInterval(interval); + // this.pym.scrollParentToChildEl(commentId); + // } + // + // if (++count > 100) { // ~10 seconds + // // give up waiting for the comments to load. + // // it would be weird for the page to jump after that long. + // window.clearInterval(interval); + // } + // }, 100); + // }); } render () { @@ -154,11 +156,11 @@ class Embed extends Component { showSignInDialog={showSignInDialog}/> } - {/**/} + {/* */} - {/**/} + {/* */}
} ; @@ -188,7 +190,8 @@ class Embed extends Component { const mapStateToProps = state => (state); -const mapDispatchToProps = (dispatch, ownProps) => ({ +const mapDispatchToProps = dispatch => ({ + // addItem: (item, item_id) => dispatch(addItem(item, item_id)), // updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), // postItem: (data, type, id) => { @@ -198,8 +201,10 @@ const mapDispatchToProps = (dispatch, ownProps) => ({ // getStream: (rootId) => dispatch(getStream(rootId)), addNotification: (type, text) => dispatch(addNotification(type, text)), clearNotification: () => dispatch(clearNotification()), + // postAction: (item, itemType, action) => dispatch(postAction(item, itemType, action)), showSignInDialog: (offset) => dispatch(showSignInDialog(offset)), + // deleteAction: (item, action, user, itemType) => dispatch(deleteAction(item, action, user, itemType)), // appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), // handleSignInDialog: () => dispatch(authActions.showSignInDialog()), @@ -211,18 +216,14 @@ const pym = new Pym.Child({polling: 100}); let url = pym.parentUrl; -import STREAM_QUERY from './graphql/queries/streamQuery.graphql'; -import POST_COMMENT from './graphql/mutations/postComment.graphql'; - -console.log(STREAM_QUERY, POST_COMMENT) export default compose( graphql(STREAM_QUERY, { - options: { variables: { asset_url: url } }, + options: {variables: {asset_url: url}}, props: props => props, }), graphql(POST_COMMENT, { - props: ({ownProps, mutate}) => ({ - postItem: ({asset_id, author_id, body}) => { + props: ({mutate}) => ({ + postItem: ({asset_id, body}) => { mutate({ variables: { asset_id, @@ -233,7 +234,7 @@ export default compose( console.log('it workt'); console.log(data); }); - }}), + }}), }), connect(mapStateToProps, mapDispatchToProps) )(Embed); diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index 13dbaf35d..61cfcd29d 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -1,4 +1,4 @@ -import React, {Component, PropTypes} from 'react'; +import React, {PropTypes} from 'react'; import Comment from './Comment'; const Stream = ({comments}) => { diff --git a/client/coral-framework/actions/items.js b/client/coral-framework/actions/items.js index 78e3f64b7..e289e9f60 100644 --- a/client/coral-framework/actions/items.js +++ b/client/coral-framework/actions/items.js @@ -2,8 +2,6 @@ import coralApi from '../helpers/response'; import {fromJS} from 'immutable'; import {UPDATE_CONFIG} from '../constants/config'; -import gql from 'graphql-tag'; - /** * Action name constants */ @@ -191,22 +189,13 @@ export function getItemsArray (ids) { * The newly put item to the item store */ - -const postComment = gql` - mutation CreateComment ($asset_id: ID!, $parent_id: ID, $body: String!) { - createComment(asset_id:$asset_id, parent_id:$parent_id, body:$body) { - ...commentView - } - } -`; - export function postItem (item, type, id, mutate) { console.log( item, type, id, mutate - ) + ); mutate({ variables: { asset_id: id, diff --git a/client/coral-framework/reducers/index.js b/client/coral-framework/reducers/index.js index e406a5524..2c862ebbd 100644 --- a/client/coral-framework/reducers/index.js +++ b/client/coral-framework/reducers/index.js @@ -1,4 +1,3 @@ -import {combineReducers} from 'redux'; import config from './config'; import items from './items'; import notification from './notification'; diff --git a/graph/mutators.js b/graph/mutators.js index f5c600499..298a51404 100644 --- a/graph/mutators.js +++ b/graph/mutators.js @@ -118,7 +118,7 @@ const createPublicComment = (context, commentInput) => { // If the comment was flagged as being suspect, we need to add a // flag to it to indicate that it needs to be looked at. // Otherwise just return the new comment. - if (wordlist != null && wordlist.suspect) { + if (wordlist !== null && wordlist.suspect) { // TODO: this is kind of fragile, we should refactor this to resolve // all these const's that we're using like 'comments', 'flag' to be From 715faf321f6d4473a188817d84e4274b857f2828 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 18:04:30 -0300 Subject: [PATCH 031/107] Wordlist is undefined --- graph/mutators.js | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/graph/mutators.js b/graph/mutators.js index 298a51404..69fdcd92e 100644 --- a/graph/mutators.js +++ b/graph/mutators.js @@ -1,5 +1,6 @@ -const errors = require('../errors'); +/* eslint eqeqeq: ["error", "smart"]*/ +const errors = require('../errors'); const Action = require('../models/action'); const Asset = require('../models/asset'); const Comment = require('../models/comment'); @@ -118,7 +119,9 @@ const createPublicComment = (context, commentInput) => { // If the comment was flagged as being suspect, we need to add a // flag to it to indicate that it needs to be looked at. // Otherwise just return the new comment. - if (wordlist !== null && wordlist.suspect) { + + // TODO: Check why the wordlist is undefined + if (wordlist != null) { // TODO: this is kind of fragile, we should refactor this to resolve // all these const's that we're using like 'comments', 'flag' to be From f813a7719405dca0d8c2c3e096201a40624bb866 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 18:48:35 -0300 Subject: [PATCH 032/107] queries and mutations --- client/coral-embed-stream/src/Embed.js | 30 +++++-------------- .../src/graphql/mutations/index.js | 20 +++++++++++++ .../src/graphql/queries/index.js | 12 ++++++++ graph/resolvers/comment.js | 7 +++++ graph/typeDefs.js | 1 + 5 files changed, 47 insertions(+), 23 deletions(-) create mode 100644 client/coral-embed-stream/src/graphql/mutations/index.js create mode 100644 client/coral-embed-stream/src/graphql/queries/index.js diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index a72d8c1ad..c1690aeb7 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -1,10 +1,10 @@ import React, {Component, PropTypes} from 'react'; import Pym from 'pym.js'; -import {graphql, compose} from 'react-apollo'; +import {compose} from 'react-apollo'; import {connect} from 'react-redux'; -import STREAM_QUERY from './graphql/queries/streamQuery.graphql'; -import POST_COMMENT from './graphql/mutations/postComment.graphql'; +import {postComment} from './graphql/mutations' +import {queryStream} from './graphql/queries' import { @@ -209,6 +209,7 @@ const mapDispatchToProps = dispatch => ({ // appendItemArray: (item, property, value, addToFront, itemType) => dispatch(appendItemArray(item, property, value, addToFront, itemType)), // handleSignInDialog: () => dispatch(authActions.showSignInDialog()), logout: () => dispatch(logout()), + dispatch: d => dispatch(d) }); // Initialize GraphQL queries or mutations with the `gql` tag @@ -217,24 +218,7 @@ const pym = new Pym.Child({polling: 100}); let url = pym.parentUrl; export default compose( - graphql(STREAM_QUERY, { - options: {variables: {asset_url: url}}, - props: props => props, - }), - graphql(POST_COMMENT, { - props: ({mutate}) => ({ - postItem: ({asset_id, body}) => { - mutate({ - variables: { - asset_id, - body, - parent_id: null - } - }).then(({data}) => { - console.log('it workt'); - console.log(data); - }); - }}), - }), - connect(mapStateToProps, mapDispatchToProps) + connect(mapStateToProps, mapDispatchToProps), + postComment(), + queryStream() )(Embed); diff --git a/client/coral-embed-stream/src/graphql/mutations/index.js b/client/coral-embed-stream/src/graphql/mutations/index.js new file mode 100644 index 000000000..66cb97d21 --- /dev/null +++ b/client/coral-embed-stream/src/graphql/mutations/index.js @@ -0,0 +1,20 @@ +import { graphql } from 'react-apollo'; +import POST_COMMENT from './postComment.graphql'; + +export const postComment = () => + graphql(POST_COMMENT, { + props: ({mutate}) => ({ + postItem: ({asset_id, body}) => { + mutate({ + variables: { + asset_id, + body, + parent_id: null + } + }).then(({data}) => { + console.log('it workt'); + console.log(data); + }); + }}), + }); + diff --git a/client/coral-embed-stream/src/graphql/queries/index.js b/client/coral-embed-stream/src/graphql/queries/index.js new file mode 100644 index 000000000..d3b3bdb86 --- /dev/null +++ b/client/coral-embed-stream/src/graphql/queries/index.js @@ -0,0 +1,12 @@ +import { graphql } from 'react-apollo'; +import STREAM_QUERY from './streamQuery.graphql'; +import Pym from 'pym.js'; + +const pym = new Pym.Child({polling: 100}); +let url = pym.parentUrl; + +export const queryStream = () => + graphql(STREAM_QUERY, { + options: {variables: {asset_url: url}}, + props: props => props, + }); diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index 2fdf6f0dd..1b1c10046 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -7,6 +7,13 @@ const Comment = { }, actions({id}, _, {loaders}) { return loaders.Actions.getByID.load(id); + }, + status({status}) { + + // Because the status can be `null`, we do this check. + if (status) { + return status.toUpperCase(); + } } }; diff --git a/graph/typeDefs.js b/graph/typeDefs.js index aadc7f57d..9889815d5 100644 --- a/graph/typeDefs.js +++ b/graph/typeDefs.js @@ -20,6 +20,7 @@ type Comment { user: User replies(limit: Int = 3): [Comment] actions: [ActionSummary] + status: String } enum ITEM_TYPE { From b98b71189fcca2635485bbdc76d3d53ebad218d3 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 18:53:11 -0300 Subject: [PATCH 033/107] atempt to solution --- client/coral-embed-stream/src/Embed.js | 4 +-- .../src/graphql/mutations/index.js | 31 +++++++++---------- .../src/graphql/queries/index.js | 8 ++--- 3 files changed, 20 insertions(+), 23 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index c1690aeb7..746e7bd40 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -219,6 +219,6 @@ let url = pym.parentUrl; export default compose( connect(mapStateToProps, mapDispatchToProps), - postComment(), - queryStream() + postComment, + queryStream )(Embed); diff --git a/client/coral-embed-stream/src/graphql/mutations/index.js b/client/coral-embed-stream/src/graphql/mutations/index.js index 66cb97d21..1c74d2e69 100644 --- a/client/coral-embed-stream/src/graphql/mutations/index.js +++ b/client/coral-embed-stream/src/graphql/mutations/index.js @@ -1,20 +1,19 @@ import { graphql } from 'react-apollo'; import POST_COMMENT from './postComment.graphql'; -export const postComment = () => - graphql(POST_COMMENT, { - props: ({mutate}) => ({ - postItem: ({asset_id, body}) => { - mutate({ - variables: { - asset_id, - body, - parent_id: null - } - }).then(({data}) => { - console.log('it workt'); - console.log(data); - }); - }}), - }); +export const postComment = graphql(POST_COMMENT, { + props: ({dispatch, mutate}) => ({ + postItem: ({asset_id, body}) => { + mutate({ + variables: { + asset_id, + body, + parent_id: null + } + }).then(({data}) => { + console.log('it workt'); + console.log(data); + }); + }}), +}); diff --git a/client/coral-embed-stream/src/graphql/queries/index.js b/client/coral-embed-stream/src/graphql/queries/index.js index d3b3bdb86..0e2b3cf95 100644 --- a/client/coral-embed-stream/src/graphql/queries/index.js +++ b/client/coral-embed-stream/src/graphql/queries/index.js @@ -5,8 +5,6 @@ import Pym from 'pym.js'; const pym = new Pym.Child({polling: 100}); let url = pym.parentUrl; -export const queryStream = () => - graphql(STREAM_QUERY, { - options: {variables: {asset_url: url}}, - props: props => props, - }); +export const queryStream = graphql(STREAM_QUERY, { + options: {variables: {asset_url: url}} +}); From 39bf092ebb40bd061c045f97f131ae323513fe09 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 18:56:42 -0300 Subject: [PATCH 034/107] Correct Status --- .../coral-embed-stream/src/graphql/mutations/postComment.graphql | 1 + 1 file changed, 1 insertion(+) diff --git a/client/coral-embed-stream/src/graphql/mutations/postComment.graphql b/client/coral-embed-stream/src/graphql/mutations/postComment.graphql index e06c4f55c..a9496f1fa 100644 --- a/client/coral-embed-stream/src/graphql/mutations/postComment.graphql +++ b/client/coral-embed-stream/src/graphql/mutations/postComment.graphql @@ -2,6 +2,7 @@ fragment commentView on Comment { id body + status user { name: displayName } From 450d74848ecdc4ad0046cb23bea29d0558587b95 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 19:20:43 -0300 Subject: [PATCH 035/107] Notifications are back --- client/coral-embed-stream/src/Embed.js | 44 ++++++++++--------- .../src/graphql/mutations/index.js | 5 +-- .../modules/notification/Notification.js | 1 + client/coral-plugin-commentbox/CommentBox.js | 5 ++- 4 files changed, 28 insertions(+), 27 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 746e7bd40..87da4271c 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -9,19 +9,19 @@ import {queryStream} from './graphql/queries' import { // itemActions, - // Notification, + Notification, notificationActions, authActions -} from '../../coral-framework'; +} from 'coral-framework'; import Stream from './Stream'; -import CommentBox from '../../coral-plugin-commentbox/CommentBox'; -import InfoBox from '../../coral-plugin-infobox/InfoBox'; +import CommentBox from 'coral-plugin-commentbox/CommentBox'; +import InfoBox from 'coral-plugin-infobox/InfoBox'; // import Content from '../../coral-plugin-commentcontent/CommentContent'; // import PubDate from '../../coral-plugin-pubdate/PubDate'; -import Count from '../../coral-plugin-comment-count/CommentCount'; +import Count from 'coral-plugin-comment-count/CommentCount'; // import AuthorName from '../../coral-plugin-author-name/AuthorName'; // import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; @@ -97,7 +97,7 @@ class Embed extends Component { } render () { - + console.log(this.props) // const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0]; // const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; // const {actions, users, comments} = this.props.items; @@ -156,11 +156,11 @@ class Embed extends Component { showSignInDialog={showSignInDialog}/> } - {/* */} + - {/* */} + } ; } } -const mapStateToProps = state => (state); +const mapStateToProps = state => ({ + config: state.config.toJS(), + items: state.items.toJS(), + notification: state.notification.toJS(), + auth: state.auth.toJS(), + userData: state.user.toJS() +}); const mapDispatchToProps = dispatch => ({ // addItem: (item, item_id) => dispatch(addItem(item, item_id)), // updateItem: (id, property, value, itemType) => dispatch(updateItem(id, property, value, itemType)), - // postItem: (data, type, id) => { - // console.log('postItem', dispatch, ownProps) - // // dispatch(postItem(data, type, id, ownProps)) - // }, // getStream: (rootId) => dispatch(getStream(rootId)), addNotification: (type, text) => dispatch(addNotification(type, text)), clearNotification: () => dispatch(clearNotification()), diff --git a/client/coral-embed-stream/src/graphql/mutations/index.js b/client/coral-embed-stream/src/graphql/mutations/index.js index 1c74d2e69..2ed814f7a 100644 --- a/client/coral-embed-stream/src/graphql/mutations/index.js +++ b/client/coral-embed-stream/src/graphql/mutations/index.js @@ -4,15 +4,12 @@ import POST_COMMENT from './postComment.graphql'; export const postComment = graphql(POST_COMMENT, { props: ({dispatch, mutate}) => ({ postItem: ({asset_id, body}) => { - mutate({ + return mutate({ variables: { asset_id, body, parent_id: null } - }).then(({data}) => { - console.log('it workt'); - console.log(data); }); }}), }); diff --git a/client/coral-framework/modules/notification/Notification.js b/client/coral-framework/modules/notification/Notification.js index 2a5e1d693..2d4d0ce91 100644 --- a/client/coral-framework/modules/notification/Notification.js +++ b/client/coral-framework/modules/notification/Notification.js @@ -1,6 +1,7 @@ import React from 'react'; const Notification = (props) => { + console.log(props) if (props.notification.text) { setTimeout(() => { props.clearNotification(); diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index fe67b22e0..52a6377fe 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -58,11 +58,12 @@ class CommentBox extends Component { return; } postItem(comment, 'comments') - .then((postedComment) => { + .then(({data}) => { + const postedComment = data.createComment const commentId = postedComment.id; if (postedComment.status === 'rejected') { addNotification('error', lang.t('comment-post-banned-word')); - } else if (premod === 'pre') { + } else if (postedComment.status === 'PREMOD') { addNotification('success', lang.t('comment-post-notif-premod')); } else { appendItemArray(parent_id || id, related, commentId, !parent_id, parent_type); From 9c40ee1245512e14ce4920adc84c3cd09e14c3d9 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Fri, 20 Jan 2017 19:35:31 -0300 Subject: [PATCH 036/107] Login sign In back on! :tada: --- client/coral-embed-stream/src/Embed.js | 15 +++++--------- .../src/graphql/queries/streamQuery.graphql | 4 ---- client/coral-framework/actions/auth.js | 20 ++++++++----------- .../containers/SignInContainer.js | 7 +------ 4 files changed, 14 insertions(+), 32 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 87da4271c..0231ad1b8 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -97,17 +97,16 @@ class Embed extends Component { } render () { - console.log(this.props) // const rootItemId = this.props.items.assets && Object.keys(this.props.items.assets)[0]; // const rootItem = this.props.items.assets && this.props.items.assets[rootItemId]; // const {actions, users, comments} = this.props.items; - const {isAdmin, showSignInDialog, loggedIn} = this.props.auth; + const {loggedIn, isAdmin, user, showSignInDialog, signInOffset} = this.props.auth; const {activeTab} = this.state; // const banned = (this.props.userData.status === 'banned'); - const {loading, asset, currentUser, refetch} = this.props.data; + const {loading, asset, refetch} = this.props.data; // const {status, moderation, closedMessage, charCount, charCountEnable} = asset.settings; @@ -124,7 +123,7 @@ class Embed extends Component { Settings Configure Stream - {currentUser && } + {loggedIn && } { asset.closedAt === null @@ -144,17 +143,13 @@ class Embed extends Component { reply={false} currentUser={this.props.auth.user} banned={false} - author={currentUser} + author={user} charCount={asset.settings.charCountEnable && asset.settings.charCount}/> :

{asset.settings.closedMessage}

} - { - !currentUser && - } + {!loggedIn && } ({type: actions.CLEAN_STATE}); // Sign In Actions const signInRequest = () => ({type: actions.FETCH_SIGNIN_REQUEST}); - const signInSuccess = (user, isAdmin) => ({type: actions.FETCH_SIGNIN_SUCCESS, user, isAdmin}); const signInFailure = error => ({type: actions.FETCH_SIGNIN_FAILURE, error}); export const fetchSignIn = (formData) => (dispatch) => { dispatch(signInRequest()); - return coralApi('/auth/local', {method: 'POST', body: formData}) - .then((user) => { - + coralApi('/auth/local', {method: 'POST', body: formData}) + .then(({user}) => { const isAdmin = !!user.roles.filter(i => i === 'admin').length; dispatch(signInSuccess(user, isAdmin)); - - // dispatch(hideSignInDialog()); - - // dispatch(addItem(user, 'users')); + dispatch(hideSignInDialog()); + dispatch(addItem(user, 'users')); }) .catch(() => dispatch(signInFailure(lang.t('error.emailPasswordError')))); }; @@ -79,7 +75,7 @@ const signUpFailure = error => ({type: actions.FETCH_SIGNUP_FAILURE, error}); export const fetchSignUp = formData => (dispatch) => { dispatch(signUpRequest()); - return coralApi('/users', {method: 'POST', body: formData}) + coralApi('/users', {method: 'POST', body: formData}) .then(({user}) => { dispatch(signUpSuccess(user)); setTimeout(() =>{ @@ -99,7 +95,7 @@ const forgotPassowordFailure = () => ({type: actions.FETCH_FORGOT_PASSWORD_FAILU export const fetchForgotPassword = email => (dispatch) => { dispatch(forgotPassowordRequest(email)); - return coralApi('/account/password/reset', {method: 'POST', body: {email}}) + coralApi('/account/password/reset', {method: 'POST', body: {email}}) .then(() => dispatch(forgotPassowordSuccess())) .catch(error => dispatch(forgotPassowordFailure(error))); }; @@ -112,7 +108,7 @@ const logOutFailure = () => ({type: actions.LOGOUT_FAILURE}); export const logout = () => dispatch => { dispatch(logOutRequest()); - return coralApi('/auth', {method: 'DELETE'}) + coralApi('/auth', {method: 'DELETE'}) .then(() => dispatch(logOutSuccess())) .catch(error => dispatch(logOutFailure(error))); }; @@ -130,7 +126,7 @@ const checkLoginFailure = error => ({type: actions.CHECK_LOGIN_FAILURE, error}); export const checkLogin = () => dispatch => { dispatch(checkLoginRequest()); - return coralApi('/auth') + coralApi('/auth') .then((result) => { if (!result.user) { throw new Error('Not logged in'); diff --git a/client/coral-sign-in/containers/SignInContainer.js b/client/coral-sign-in/containers/SignInContainer.js index 9f97a14c7..07f82a849 100644 --- a/client/coral-sign-in/containers/SignInContainer.js +++ b/client/coral-sign-in/containers/SignInContainer.js @@ -121,12 +121,7 @@ class SignInContainer extends Component { handleSignIn(e) { e.preventDefault(); - this.props.fetchSignIn(this.state.formData) - - // Using refetch to get data after the user has logged in. - // This is the equivalent of a page reload, we may want to use a more speficic mustation here. - // Also, we may want to find a way for this logic to live elsewhere. - .then(() => this.props.refetch()); + this.props.fetchSignIn(this.state.formData); } handleClose() { From d724351fb66697df9e544c8d5541d844aa0ac0c4 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 20 Jan 2017 15:54:18 -0700 Subject: [PATCH 037/107] merge my changes --- client/coral-embed-stream/src/Comment.js | 48 ++++++++++++++++++++++-- client/coral-embed-stream/src/Embed.js | 29 ++++++-------- client/coral-embed-stream/src/Stream.js | 16 ++++++-- 3 files changed, 70 insertions(+), 23 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 71fe3723f..0d19d7b30 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -8,18 +8,60 @@ import React, {PropTypes} from 'react'; import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton'; +import AuthorName from '../../coral-plugin-author-name/AuthorName'; +import Content from '../../coral-plugin-commentcontent/CommentContent'; +import PubDate from '../../coral-plugin-pubdate/PubDate'; +import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; +import FlagComment from '../../coral-plugin-flags/FlagComment'; +import LikeButton from '../../coral-plugin-likes/LikeButton'; -const Comment = ({comment}) => { +const Comment = ({comment, currentUser, asset}) => { + console.log('A Comment', comment); + console.log('the asset', asset); return (

- {comment.body} - + {/**/} + + +
+ +
+
+ + +
+ { + comment.replies.map(reply => { + return ; + }) + } +
); }; Comment.propTypes = { + asset: PropTypes.shape({ + id: PropTypes.string, + title: PropTypes.string, + url: PropTypes.string + }).isRequired, + currentUser: PropTypes.object, comment: PropTypes.shape({ depth: PropTypes.number, actions: PropTypes.array.isRequired, diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 87da4271c..173c12164 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -23,12 +23,6 @@ import InfoBox from 'coral-plugin-infobox/InfoBox'; // import PubDate from '../../coral-plugin-pubdate/PubDate'; import Count from 'coral-plugin-comment-count/CommentCount'; -// import AuthorName from '../../coral-plugin-author-name/AuthorName'; -// import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; -// import FlagComment from '../../coral-plugin-flags/FlagComment'; -// import LikeButton from '../../coral-plugin-likes/LikeButton'; -// import PermalinkButton from '../../coral-plugin-permalinks/PermalinkButton'; - import SignInContainer from '../../coral-sign-in/containers/SignInContainer'; import UserBox from '../../coral-sign-in/components/UserBox'; @@ -94,6 +88,7 @@ class Embed extends Component { // } // }, 100); // }); + } render () { @@ -155,12 +150,17 @@ class Embed extends Component { refetch={refetch} showSignInDialog={showSignInDialog}/> } - - + + { + + }
({ dispatch: d => dispatch(d) }); -// Initialize GraphQL queries or mutations with the `gql` tag -const pym = new Pym.Child({polling: 100}); - -let url = pym.parentUrl; - export default compose( connect(mapStateToProps, mapDispatchToProps), postComment, diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index 61cfcd29d..999cfbd94 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -1,12 +1,17 @@ import React, {PropTypes} from 'react'; import Comment from './Comment'; -const Stream = ({comments}) => { +const Stream = ({comments, currentUser, asset}) => { + console.log('currentUser', currentUser); return (
{ comments.map(comment => { - return ; + return ; }) }
@@ -14,7 +19,12 @@ const Stream = ({comments}) => { }; Stream.propTypes = { - comments: PropTypes.array.isRequired + asset: PropTypes.object.isRequired, + comments: PropTypes.array.isRequired, + currentUser: PropTypes.shape({ + displayName: PropTypes.string, + id: PropTypes.string + }) }; export default Stream; From 0dcc912fd04d7dd99a50e7b47770bd91c7ad9468 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Fri, 20 Jan 2017 15:58:36 -0700 Subject: [PATCH 038/107] whoops, committed broken code --- client/coral-embed-stream/src/Embed.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 71d673075..ed61fbd57 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -144,11 +144,11 @@ class Embed extends Component { :

{asset.settings.closedMessage}

} + {!loggedIn && } - {!loggedIn && } Date: Fri, 20 Jan 2017 16:23:34 -0700 Subject: [PATCH 039/107] replies --- client/coral-embed-stream/src/Comment.css | 1 + client/coral-embed-stream/src/Comment.js | 12 ++++++++---- client/coral-embed-stream/src/Stream.js | 1 + 3 files changed, 10 insertions(+), 4 deletions(-) create mode 100644 client/coral-embed-stream/src/Comment.css diff --git a/client/coral-embed-stream/src/Comment.css b/client/coral-embed-stream/src/Comment.css new file mode 100644 index 000000000..8b1378917 --- /dev/null +++ b/client/coral-embed-stream/src/Comment.css @@ -0,0 +1 @@ + diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 0d19d7b30..0e5806e6b 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -15,11 +15,12 @@ import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; import FlagComment from '../../coral-plugin-flags/FlagComment'; import LikeButton from '../../coral-plugin-likes/LikeButton'; -const Comment = ({comment, currentUser, asset}) => { - console.log('A Comment', comment); - console.log('the asset', asset); +const Comment = ({comment, currentUser, asset, depth}) => { return ( -
+

{/* {
{ + comment.replies && comment.replies.map(reply => { return { }; Comment.propTypes = { + depth: PropTypes.number.isRequired, asset: PropTypes.shape({ id: PropTypes.string, title: PropTypes.string, diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index 999cfbd94..78a988553 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -8,6 +8,7 @@ const Stream = ({comments, currentUser, asset}) => { { comments.map(comment => { return Date: Fri, 20 Jan 2017 16:49:19 -0700 Subject: [PATCH 040/107] modularized mutators, loaders --- .eslintrc.json | 2 +- client/coral-embed-stream/src/Comment.js | 2 +- client/coral-embed-stream/src/Embed.js | 5 +- .../src/graphql/mutations/index.js | 2 +- .../src/graphql/queries/index.js | 2 +- .../modules/notification/Notification.js | 2 +- client/coral-plugin-commentbox/CommentBox.js | 2 +- graph/context.js | 21 +++ graph/index.js | 26 +-- graph/loaders.js | 171 ------------------ graph/loaders/actions.js | 27 +++ graph/loaders/assets.js | 63 +++++++ graph/loaders/comments.js | 91 ++++++++++ graph/loaders/index.js | 28 +++ graph/loaders/settings.js | 12 ++ graph/loaders/users.js | 16 ++ graph/loaders/util.js | 68 +++++++ graph/mutators/action.js | 54 ++++++ graph/{mutators.js => mutators/comment.js} | 69 +------ graph/mutators/index.js | 19 ++ graph/mutators/user.js | 31 ++++ graph/resolvers/action.js | 13 +- graph/resolvers/action_summary.js | 4 +- graph/resolvers/comment.js | 5 +- graph/resolvers/root_query.js | 37 +++- graph/resolvers/user.js | 10 + graph/typeDefs.js | 76 ++++++-- models/action.js | 3 +- models/user.js | 7 + 29 files changed, 580 insertions(+), 288 deletions(-) create mode 100644 graph/context.js delete mode 100644 graph/loaders.js create mode 100644 graph/loaders/actions.js create mode 100644 graph/loaders/assets.js create mode 100644 graph/loaders/comments.js create mode 100644 graph/loaders/index.js create mode 100644 graph/loaders/settings.js create mode 100644 graph/loaders/users.js create mode 100644 graph/loaders/util.js create mode 100644 graph/mutators/action.js rename graph/{mutators.js => mutators/comment.js} (73%) create mode 100644 graph/mutators/index.js create mode 100644 graph/mutators/user.js diff --git a/.eslintrc.json b/.eslintrc.json index 8b737cbd2..34b46b83b 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -17,7 +17,7 @@ "no-template-curly-in-string": [1], "no-unsafe-negation": [1], "array-callback-return": [1], - "eqeqeq": [2], + "eqeqeq": [2, "smart"], "no-eval": [2], "no-global-assign": [2], "no-implied-eval": [2], diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 0e5806e6b..4ed0296a2 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -22,7 +22,7 @@ const Comment = ({comment, currentUser, asset, depth}) => { id={`c_${comment.id}`} style={{marginLeft: depth * 30}}>
- {/* { - console.log(props) + console.log(props); if (props.notification.text) { setTimeout(() => { props.clearNotification(); diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index 52a6377fe..ebafab918 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -59,7 +59,7 @@ class CommentBox extends Component { } postItem(comment, 'comments') .then(({data}) => { - const postedComment = data.createComment + const postedComment = data.createComment; const commentId = postedComment.id; if (postedComment.status === 'rejected') { addNotification('error', lang.t('comment-post-banned-word')); diff --git a/graph/context.js b/graph/context.js new file mode 100644 index 000000000..cdc05f6cf --- /dev/null +++ b/graph/context.js @@ -0,0 +1,21 @@ +const loaders = require('./loaders'); +const mutators = require('./mutators'); + +/** + * Stores the request context. + */ +class Context { + constructor({user = null}) { + + // Load the current logged in user to `user`, otherwise this'll be null. + this.user = user; + + // Create the loaders. + this.loaders = loaders(this); + + // Create the mutators. + this.mutators = mutators(this); + } +} + +module.exports = Context; diff --git a/graph/index.js b/graph/index.js index 514df2d0a..7fddce2eb 100644 --- a/graph/index.js +++ b/graph/index.js @@ -1,24 +1,14 @@ -const loaders = require('./loaders'); -const mutators = require('./mutators'); const schema = require('./schema'); +const Context = require('./context'); module.exports = { - createGraphOptions: (req) => { + createGraphOptions: (req) => ({ - let context = {}; + // Schema is created already, so just include it. + schema, - // Load the current logged in user to `user`, otherwise this'll be null. - context.user = req.user; - - // Create the loaders. - context.loaders = loaders(context); - - // Create the mutators. - context.mutators = mutators(context); - - return { - schema, - context - }; - } + // Load in the new context here, this'll create the loaders + mutators for + // the lifespan of this request. + context: new Context(req) + }) }; diff --git a/graph/loaders.js b/graph/loaders.js deleted file mode 100644 index 4388af4a8..000000000 --- a/graph/loaders.js +++ /dev/null @@ -1,171 +0,0 @@ -const DataLoader = require('dataloader'); -const _ = require('lodash'); -const url = require('url'); -const errors = require('../errors'); -const scraper = require('../services/scraper'); - -const Comment = require('../models/comment'); -const User = require('../models/user'); -const Action = require('../models/action'); -const Asset = require('../models/asset'); -const Settings = require('../models/setting'); - -/** - * SingletonResolver is a cached loader for a single result. - */ -class SingletonResolver { - constructor(resolver) { - this._cache = null; - this._resolver = resolver; - } - - load() { - if (this._cache) { - return this._cache; - } - - let promise = this._resolver(arguments).then((result) => { - return result; - }); - - // Set the promise on the cache. - this._cache = promise; - - return promise; - } -} - -/** - * This joins a set of results with a specific keys and sets an empty array in - * place if it was not found. - * @param {Array} ids ids to locate - * @param {String} key key to group by - * @return {Array} array of results - */ -const arrayJoinBy = (ids, key) => (items) => { - const itemsByKey = _.groupBy(items, key); - return ids.map((id) => { - if (id in itemsByKey) { - return itemsByKey[id]; - } - - return []; - }); -}; - -/** - * This joins a set of results with a specific keys and sets null in place if it - * was not found. - * @param {Array} ids ids to locate - * @param {String} key key to group by - * @return {Array} array of results - */ -const singleJoinBy = (ids, key) => (items) => { - const itemsByKey = _.groupBy(items, key); - return ids.map((id) => { - if (id in itemsByKey) { - return itemsByKey[id][0]; - } - - return null; - }); -}; - -/** - * Retrieves assets by an array of ids. - * @param {Array} ids array of ids to lookup - */ -const genAssetsByID = (ids) => Asset.find({ - id: { - $in: ids - } -}).then(singleJoinBy(ids, 'id')); - -/** - * Retrieves actions by an array of ids. - * @param {Array} ids array of ids to lookup - */ -const genActionsByID = (ids, user = {}) => Action.getActionSummaries(ids, user.id).then(arrayJoinBy(ids, 'item_id')); - -/** - * Retrieves comments by an array of asset id's. - * @param {Array} ids array of ids to lookup - */ -const genCommentsByAssetID = (ids) => Comment.find({ - asset_id: { - $in: ids - }, - parent_id: null, - status: { - $in: [null, 'accepted'] - } -}).then(arrayJoinBy(ids, 'asset_id')); - -/** - * Retrieves comments by an array of parent ids. - * @param {Array} ids array of ids to lookup - */ -const genCommentsByParentID = (ids) => Comment.find({ - parent_id: { - $in: ids - }, - status: { - $in: [null, 'accepted'] - } -}).then(arrayJoinBy(ids, 'parent_id')); - -/** - * This endpoint find or creates an asset at the given url when it is loaded. - * @param {String} asset_url the url passed in from the query - * @returns {Promise} resolves to the asset - */ -const findOrCreateAssetByURL = (asset_url) => { - - // Verify that the asset_url is parsable. - let parsed_asset_url = url.parse(asset_url); - if (!parsed_asset_url.protocol) { - return Promise.reject(errors.ErrInvalidAssetURL); - } - - return Asset.findOrCreateByUrl(asset_url) - .then((asset) => { - - // If the asset wasn't scraped before, scrape it! Otherwise just return - // the asset. - if (!asset.scraped) { - return scraper.create(asset).then(() => asset); - } - - return asset; - }); -}; - -/** - * Creates a set of loaders based on a GraphQL context. - * @param {Object} context the context of the GraphQL request - * @return {Object} object of loaders - */ -const createLoaders = (context) => ({ - Comments: { - getByParentID: new DataLoader((ids) => genCommentsByParentID(ids)), - getByAssetID: new DataLoader((ids) => genCommentsByAssetID(ids)), - }, - Actions: { - getByID: new DataLoader((ids) => genActionsByID(ids, context.user)), - }, - Users: { - getByID: new DataLoader((ids) => User.findByIdArray(ids)) - }, - Assets: { - - // TODO: decide whether we want to move these to mutators or not, as in fact - // this operation create a new asset if one isn't found. - getByURL: (url) => findOrCreateAssetByURL(url), - - getByID: new DataLoader((ids) => genAssetsByID(ids)), - getAll: new SingletonResolver(() => Asset.find({})) - }, - Settings: new SingletonResolver(() => Settings.retrieve()) -}); - -module.exports = createLoaders; diff --git a/graph/loaders/actions.js b/graph/loaders/actions.js new file mode 100644 index 000000000..c4664631a --- /dev/null +++ b/graph/loaders/actions.js @@ -0,0 +1,27 @@ +const DataLoader = require('dataloader'); + +const util = require('./util'); + +const Action = require('../../models/action'); + +/** + * Looks up actions based on the requested id's all bounded by the user. + * @param {Object} context the context of the request + * @param {Array} ids array of id's to get + * @return {Promise} resolves to the promises of the requested actions + */ +const genActionSummariessByItemID = ({user = {}}, item_ids) => { + return Action.getActionSummaries(item_ids, user.id) + .then(util.arrayJoinBy(item_ids, 'item_id')); +}; + +/** + * Creates a set of loaders based on a GraphQL context. + * @param {Object} context the context of the GraphQL request + * @return {Object} object of loaders + */ +module.exports = (context) => ({ + Actions: { + getByItemID: new DataLoader((ids) => genActionSummariessByItemID(context, ids)), + } +}); diff --git a/graph/loaders/assets.js b/graph/loaders/assets.js new file mode 100644 index 000000000..cd2ef881d --- /dev/null +++ b/graph/loaders/assets.js @@ -0,0 +1,63 @@ +const DataLoader = require('dataloader'); +const url = require('url'); + +const errors = require('../../errors'); +const scraper = require('../../services/scraper'); +const util = require('./util'); + +const Asset = require('../../models/asset'); + +/** + * Retrieves assets by an array of ids. + * @param {Object} context the context of the request + * @param {Array} ids array of ids to lookup + */ +const genAssetsByID = (context, ids) => Asset.find({ + id: { + $in: ids + } +}).then(util.singleJoinBy(ids, 'id')); + +/** + * This endpoint find or creates an asset at the given url when it is loaded. + * @param {Object} context the context of the request + * @param {String} asset_url the url passed in from the query + * @returns {Promise} resolves to the asset + */ +const findOrCreateAssetByURL = (context, asset_url) => { + + // Verify that the asset_url is parsable. + let parsed_asset_url = url.parse(asset_url); + if (!parsed_asset_url.protocol) { + return Promise.reject(errors.ErrInvalidAssetURL); + } + + return Asset.findOrCreateByUrl(asset_url) + .then((asset) => { + + // If the asset wasn't scraped before, scrape it! Otherwise just return + // the asset. + if (!asset.scraped) { + return scraper.create(asset).then(() => asset); + } + + return asset; + }); +}; + +/** + * Creates a set of loaders based on a GraphQL context. + * @param {Object} context the context of the GraphQL request + * @return {Object} object of loaders + */ +module.exports = (context) => ({ + Assets: { + + // TODO: decide whether we want to move these to mutators or not, as in fact + // this operation create a new asset if one isn't found. + getByURL: (url) => findOrCreateAssetByURL(context, url), + + getByID: new DataLoader((ids) => genAssetsByID(context, ids)), + getAll: new util.SingletonResolver(() => Asset.find({})) + } +}); diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js new file mode 100644 index 000000000..24db48079 --- /dev/null +++ b/graph/loaders/comments.js @@ -0,0 +1,91 @@ +const DataLoader = require('dataloader'); + +const util = require('./util'); + +const Action = require('../../models/action'); +const Comment = require('../../models/comment'); + +/** + * Retrieves comments by an array of asset id's. + * @param {Array} ids array of ids to lookup + */ +const genCommentsByAssetID = (context, ids) => Comment.find({ + asset_id: { + $in: ids + }, + parent_id: null, + status: { + $in: [null, 'accepted'] + } +}).then(util.arrayJoinBy(ids, 'asset_id')); + +/** + * Retrieves comments by an array of parent ids. + * @param {Array} ids array of ids to lookup + */ +const genCommentsByParentID = (context, ids) => Comment.find({ + parent_id: { + $in: ids + }, + status: { + $in: [null, 'accepted'] + } +}).then(util.arrayJoinBy(ids, 'parent_id')); + +const getCommentsByStatusAndAssetID = (context, {status = null, asset_id = null}) => { + + // TODO: remove when we move the enum over to the uppercase. + if (status) { + status = status.toLowerCase(); + } + + return Comment.moderationQueue(status, asset_id); +}; + +const getCommentsByActionTypeAndAssetID = (context, {action_type, asset_id = null}) => { + + // TODO: remove when we move the enum over to the uppercase. + if (action_type) { + action_type = action_type.toLowerCase(); + } + + return Action.find({ + action_type, + + // TODO: remove when we move the enum over to the uppercase. + item_type: 'comments' + }).then((actions) => { + let comments = Comment.find({ + id: { + $in: actions.map((action) => action.item_id) + } + }); + + if (asset_id) { + comments = comments.where({asset_id}); + } + + return comments; + }); +}; + +const genCommentsByAuthorID = (context, authorIDs) => Comment.find({ + author_id: { + $in: authorIDs + } +}).then(util.arrayJoinBy(authorIDs, 'author_id')); + +/** + * Creates a set of loaders based on a GraphQL context. + * @param {Object} context the context of the GraphQL request + * @return {Object} object of loaders + */ +module.exports = (context) => ({ + Comments: { + getByParentID: new DataLoader((ids) => genCommentsByParentID(context, ids)), + getByAssetID: new DataLoader((ids) => genCommentsByAssetID(context, ids)), + getByStatusAndAssetID: (query) => getCommentsByStatusAndAssetID(context, query), + getByActionTypeAndAssetID: (query) => getCommentsByActionTypeAndAssetID(context, query), + getByAuthorID: new DataLoader((authorIDs) => genCommentsByAuthorID(context, authorIDs)) + } +}); diff --git a/graph/loaders/index.js b/graph/loaders/index.js new file mode 100644 index 000000000..536e40fa9 --- /dev/null +++ b/graph/loaders/index.js @@ -0,0 +1,28 @@ +const _ = require('lodash'); + +const Actions = require('./actions'); +const Assets = require('./assets'); +const Comments = require('./comments'); +const Settings = require('./settings'); +const Users = require('./users'); + +/** + * Creates a set of loaders based on a GraphQL context. + * @param {Object} context the context of the GraphQL request + * @return {Object} object of loaders + */ +module.exports = (context) => { + + // We need to return an object to be accessed. + return _.merge(...[ + Actions, + Assets, + Comments, + Settings, + Users + ].map((loaders) => { + + // Each loader is a function which takes the context. + return loaders(context); + })); +}; diff --git a/graph/loaders/settings.js b/graph/loaders/settings.js new file mode 100644 index 000000000..c7a8810dd --- /dev/null +++ b/graph/loaders/settings.js @@ -0,0 +1,12 @@ +const Settings = require('../../models/setting'); + +const util = require('./util'); + +/** + * Creates a set of loaders based on a GraphQL context. + * @param {Object} context the context of the GraphQL request + * @return {Object} object of loaders + */ +module.exports = () => ({ + Settings: new util.SingletonResolver(() => Settings.retrieve()) +}); diff --git a/graph/loaders/users.js b/graph/loaders/users.js new file mode 100644 index 000000000..d59e524b6 --- /dev/null +++ b/graph/loaders/users.js @@ -0,0 +1,16 @@ +const DataLoader = require('dataloader'); + +const User = require('../../models/user'); + +const genUserByIDs = (context, ids) => User.findByIdArray(ids); + +/** + * Creates a set of loaders based on a GraphQL context. + * @param {Object} context the context of the GraphQL request + * @return {Object} object of loaders + */ +module.exports = (context) => ({ + Users: { + getByID: new DataLoader((ids) => genUserByIDs(context, ids)) + } +}); diff --git a/graph/loaders/util.js b/graph/loaders/util.js new file mode 100644 index 000000000..e947f2fa6 --- /dev/null +++ b/graph/loaders/util.js @@ -0,0 +1,68 @@ +const _ = require('lodash'); + +/** + * SingletonResolver is a cached loader for a single result. + */ +class SingletonResolver { + constructor(resolver) { + this._cache = null; + this._resolver = resolver; + } + + load() { + if (this._cache) { + return this._cache; + } + + let promise = this._resolver(arguments).then((result) => { + return result; + }); + + // Set the promise on the cache. + this._cache = promise; + + return promise; + } +} + +/** + * This joins a set of results with a specific keys and sets an empty array in + * place if it was not found. + * @param {Array} ids ids to locate + * @param {String} key key to group by + * @return {Array} array of results + */ +const arrayJoinBy = (ids, key) => (items) => { + const itemsByKey = _.groupBy(items, key); + return ids.map((id) => { + if (id in itemsByKey) { + return itemsByKey[id]; + } + + return []; + }); +}; + +/** + * This joins a set of results with a specific keys and sets null in place if it + * was not found. + * @param {Array} ids ids to locate + * @param {String} key key to group by + * @return {Array} array of results + */ +const singleJoinBy = (ids, key) => (items) => { + const itemsByKey = _.groupBy(items, key); + return ids.map((id) => { + if (id in itemsByKey) { + return itemsByKey[id][0]; + } + + return null; + }); +}; + +module.exports = { + singleJoinBy, + arrayJoinBy, + SingletonResolver +}; diff --git a/graph/mutators/action.js b/graph/mutators/action.js new file mode 100644 index 000000000..7c37ddd86 --- /dev/null +++ b/graph/mutators/action.js @@ -0,0 +1,54 @@ +const Action = require('../../models/action'); + +/** + * Creates an action on a item. + * @param {Object} user the user performing the request + * @param {String} item_id id of the item to add the action to + * @param {String} item_type type of the item + * @param {String} action_type type of the action + * @return {Promise} resolves to the action created + */ +const createAction = ({user = {}}, {item_id, item_type, action_type, metadata = {}}) => { + return Action.insertUserAction({ + item_id, + item_type, + user_id: user.id, + action_type, + metadata + }); +}; + +/** + * Deletes an action based on the user id if the user owns that action. + * @param {Object} user the user performing the request + * @param {String} id the id of the action to delete + * @return {Promise} resolves when the action is deleted + */ +const deleteAction = ({user}, {id}) => { + return Action.remove({ + id, + user_id: user.id + }); +}; + +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) { + return { + Action: { + create: (action) => createAction(context, action), + delete: (action) => deleteAction(context, action) + } + }; + } + + return { + Action: { + create: () => {}, + delete: () => {} + } + }; +}; diff --git a/graph/mutators.js b/graph/mutators/comment.js similarity index 73% rename from graph/mutators.js rename to graph/mutators/comment.js index 69fdcd92e..26ee691b4 100644 --- a/graph/mutators.js +++ b/graph/mutators/comment.js @@ -1,12 +1,8 @@ -/* eslint eqeqeq: ["error", "smart"]*/ +const errors = require('../../errors'); +const Asset = require('../../models/asset'); +const Comment = require('../../models/comment'); -const errors = require('../errors'); -const Action = require('../models/action'); -const Asset = require('../models/asset'); -const Comment = require('../models/comment'); -const User = require('../models/user'); - -const Wordlist = require('../services/wordlist'); +const Wordlist = require('../../services/wordlist'); /** * Creates a new comment. @@ -126,7 +122,7 @@ const createPublicComment = (context, commentInput) => { // TODO: this is kind of fragile, we should refactor this to resolve // all these const's that we're using like 'comments', 'flag' to be // defined in a checkable schema. - return createAction(null, { + return context.mutators.Action.createAction(null, { item_id: comment.id, item_type: 'comments', action_type: 'flag', @@ -142,47 +138,6 @@ const createPublicComment = (context, commentInput) => { })); }; -/** - * Creates an action on a item. - * @param {Object} user the user performing the request - * @param {String} item_id id of the item to add the action to - * @param {String} item_type type of the item - * @param {String} action_type type of the action - * @return {Promise} resolves to the action created - */ -const createAction = ({user = {}}, {item_id, item_type, action_type, metadata = {}}) => { - return Action.insertUserAction({ - item_id, - item_type, - user_id: user.id, - action_type, - metadata - }); -}; - -/** - * Deletes an action based on the user id if the user owns that action. - * @param {Object} user the user performing the request - * @param {[type]} id [description] - * @return {[type]} [description] - */ -const deleteAction = ({user}, {id}) => { - return Action.remove({ - id, - user_id: user.id - }); -}; - -/** - * Updates a users settings. - * @param {Object} user the user performing the request - * @param {String} bio the new user bio - * @return {Promise} - */ -const updateUserSettings = ({user}, {bio}) => { - return User.updateSettings(user.id, {bio}); -}; - module.exports = (context) => { // TODO: refactor to something that'll return an error in the event an attempt @@ -192,13 +147,6 @@ module.exports = (context) => { return { Comment: { create: (comment) => createPublicComment(context, comment) - }, - Action: { - create: (action) => createAction(context, action), - delete: (action) => deleteAction(context, action) - }, - User: { - updateSettings: (settings) => updateUserSettings(context, settings) } }; } @@ -206,13 +154,6 @@ module.exports = (context) => { return { Comment: { create: () => {} - }, - Action: { - create: () => {}, - delete: () => {} - }, - User: { - updateSettings: () => {} } }; }; diff --git a/graph/mutators/index.js b/graph/mutators/index.js new file mode 100644 index 000000000..b799cf83d --- /dev/null +++ b/graph/mutators/index.js @@ -0,0 +1,19 @@ +const _ = require('lodash'); + +const Comment = require('./comment'); +const Action = require('./action'); +const User = require('./user'); + +module.exports = (context) => { + + // We need to return an object to be accessed. + return _.merge(...[ + Comment, + Action, + User, + ].map((mutators) => { + + // Each set of mutators is a function which takes the context. + return mutators(context); + })); +}; diff --git a/graph/mutators/user.js b/graph/mutators/user.js new file mode 100644 index 000000000..b386a5535 --- /dev/null +++ b/graph/mutators/user.js @@ -0,0 +1,31 @@ +const User = require('../../models/user'); + +/** + * Updates a users settings. + * @param {Object} user the user performing the request + * @param {String} bio the new user bio + * @return {Promise} + */ +const updateUserSettings = ({user}, {bio}) => { + return User.updateSettings(user.id, {bio}); +}; + +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) { + return { + User: { + updateSettings: (settings) => updateUserSettings(context, settings) + } + }; + } + + return { + User: { + updateSettings: () => {} + } + }; +}; diff --git a/graph/resolvers/action.js b/graph/resolvers/action.js index 6b42b84dd..f82cefdb1 100644 --- a/graph/resolvers/action.js +++ b/graph/resolvers/action.js @@ -1,18 +1,23 @@ const Action = { action_type({action_type}) { - // TODO: remove once we cast the data model to have uppercase action + // FIXME: remove once we cast the data model to have uppercase action // types. return action_type.toUpperCase(); }, item_type({item_type}) { - // TODO: remove once we cast the data model to have uppercase item + // FIXME: remove once we cast the data model to have uppercase item // types. return item_type.toUpperCase(); }, - user({user_id}, _, {loaders}) { - return loaders.Users.getByID.load(user_id); + + // This will load the user for the specific action. We'll limit this to the + // admin users only. + user({user_id}, _, {loaders, user}) { + if (user.hasRole('admin')) { + return loaders.Users.getByID.load(user_id); + } } }; diff --git a/graph/resolvers/action_summary.js b/graph/resolvers/action_summary.js index 662078500..5a2ef0994 100644 --- a/graph/resolvers/action_summary.js +++ b/graph/resolvers/action_summary.js @@ -1,13 +1,13 @@ const ActionSummary = { action_type({action_type}) { - // TODO: remove once we cast the data model to have uppercase action + // FIXME: remove once we cast the data model to have uppercase action // types. return action_type.toUpperCase(); }, item_type({item_type}) { - // TODO: remove once we cast the data model to have uppercase item + // FIXME: remove once we cast the data model to have uppercase item // types. return item_type.toUpperCase(); } diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index 1b1c10046..d7d90918b 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -6,7 +6,7 @@ const Comment = { return loaders.Comments.getByParentID.load(id); }, actions({id}, _, {loaders}) { - return loaders.Actions.getByID.load(id); + return loaders.Actions.getByItemID.load(id); }, status({status}) { @@ -14,6 +14,9 @@ const Comment = { if (status) { return status.toUpperCase(); } + }, + asset({asset_id}, _, {loaders}) { + return loaders.Assets.getByID.load(asset_id); } }; diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 4042ddb21..dd5033d85 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -1,23 +1,46 @@ const RootQuery = { - assets(_, args, {loaders}) { - return loaders.Assets.getAll.load(); + assets(_, args, {loaders, user}) { + if (user.hasRole('admin')) { + return loaders.Assets.getAll.load(); + } }, - asset(_, {id = null, url}, {loaders}) { - if (id) { + asset(_, query, {loaders}) { + if (query.id) { // TODO: we may not always have a comment stream here, therefore, when we // load it, we may also need to create with the url. This may also have to // move the logic over to the mutators function as an upsert operation // possibly. - return loaders.Assets.getByID.load(id); - } else { - return loaders.Assets.getByURL(url); + return loaders.Assets.getByID.load(query.id); } + + return loaders.Assets.getByURL(query.url); }, settings(_, args, {loaders}) { return loaders.Settings.load(); }, + + // This endpoint is used for loading moderation queues, so hide it in the + // event that we aren't an admin. + comments(_, {query}, {loaders, user}) { + if (user == null || !user.hasRole('admin')) { + return null; + } + + if (query.action_type) { + return loaders.Comments.getByActionTypeAndAssetID(query); + } else { + return loaders.Comments.getByStatusAndAssetID(query); + } + }, + + // This returns the current user, ensure that if we aren't logged in, we + // return null. me(_, args, {user}) { + if (user == null) { + return null; + } + return user; } }; diff --git a/graph/resolvers/user.js b/graph/resolvers/user.js index 98d25f524..3d65416fa 100644 --- a/graph/resolvers/user.js +++ b/graph/resolvers/user.js @@ -1,6 +1,16 @@ const User = { actions({id}, _, {loaders}) { return loaders.Actions.getByID.load(id); + }, + comments({id}, _, {loaders, user}) { + + // If the user is not an admin, only return comment list for the owner of + // the comments. + if (!user.hasRoles('admin') || user.id !== id) { + return null; + } + + return loaders.Comments.getByAuthorID.load(id); } }; diff --git a/graph/typeDefs.js b/graph/typeDefs.js index 9889815d5..75deee196 100644 --- a/graph/typeDefs.js +++ b/graph/typeDefs.js @@ -1,26 +1,66 @@ -// TODO: Adjust `RootQuery.asset(id: ID, url: URL)` to instead be -// `RootQuery.asset(id: ID, url: URL!)` because we'll always need the url, if +// TODO: Adjust `RootQuery.asset(id: ID, url: String)` to instead be +// `RootQuery.asset(id: ID, url: String!)` because we'll always need the url, if // this change is done now everything will likely break on the front end. const typeDefs = [` +interface ActionableItem { + id: ID! +} + type UserSettings { + # bio of the user. bio: String } +input CommentsInput { + # current status of a comment. + status: COMMENT_STATUS + + # asset that a comment is on. + asset_id: ID + + # action type to find comments that have an action with. + action_type: ACTION_TYPE +} + +# Any person who can author comments, create actions, and view comments on a +# stream. type User { id: ID! + + # display name of a user. displayName: String! + + # actions against a specific user. actions: [ActionSummary] + + # settings for a user. settings: UserSettings + + # returns all comments based on a query. + comments(query: CommentsInput): [Comment] } type Comment { id: ID! + + # the actual comment data. body: String! + + # the user who authored the comment. user: User + + # the replies that were made to the comment. replies(limit: Int = 3): [Comment] + + # the actions made against a comment. actions: [ActionSummary] - status: String + + # the asset that a comment was made on. + asset: Asset + + # the current status of a comment. + status: COMMENT_STATUS } enum ITEM_TYPE { @@ -34,22 +74,20 @@ enum ACTION_TYPE { FLAG } -interface ActionInterface { - action_type: ACTION_TYPE! - item_type: ITEM_TYPE! -} - -type Action implements ActionInterface { +type Action { id: ID! - item_id: ID! action_type: ACTION_TYPE! + + item_id: ID! item_type: ITEM_TYPE! + item: ActionableItem + user: User! updated_at: String created_at: String } -type ActionSummary implements ActionInterface { +type ActionSummary { action_type: ACTION_TYPE! item_type: ITEM_TYPE! count: Int @@ -76,10 +114,26 @@ type Asset { closedAt: String } +enum COMMENT_STATUS { + ACCEPTED + REJECTED + PREMOD +} + type RootQuery { + # retrieves site wide settings and defaults. settings: Settings + + # retrieves all assets. assets: [Asset] + + # retrieves a specific asset. asset(id: ID, url: String): Asset + + # retrieves comments based on the input query. + comments(query: CommentsInput): [Comment] + + # retrieves the current logged in user. me: User } diff --git a/models/action.js b/models/action.js index 8e6634072..b73ea77c5 100644 --- a/models/action.js +++ b/models/action.js @@ -166,8 +166,7 @@ ActionSchema.statics.getActionSummaries = function(item_ids, current_user_id = ' current_user: '$current_user' } } - ]) - .exec(); + ]); }; /* diff --git a/models/user.js b/models/user.js index a0ca34d7a..5fe84c9eb 100644 --- a/models/user.js +++ b/models/user.js @@ -169,6 +169,13 @@ UserSchema.method('filterForUser', function(user = false) { return this.toJSON(); }); +/** + * Returns true if the user has all the roles specified. + */ +UserSchema.method('hasRoles', function(...roles) { + return roles.every((role) => this.roles.indexOf(role) >= 0); +}); + // Create the User model. const UserModel = mongoose.model('User', UserSchema); From fbd0bf52fe2255895ebbe70d6820130aed0b9c2b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 20 Jan 2017 16:53:43 -0700 Subject: [PATCH 041/107] Fixed methods --- graph/resolvers/root_query.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index dd5033d85..3a2499fd8 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -1,8 +1,10 @@ const RootQuery = { assets(_, args, {loaders, user}) { - if (user.hasRole('admin')) { - return loaders.Assets.getAll.load(); + if (user == null || !user.hasRoles('admin')) { + return null; } + + return loaders.Assets.getAll.load(); }, asset(_, query, {loaders}) { if (query.id) { @@ -23,7 +25,7 @@ const RootQuery = { // This endpoint is used for loading moderation queues, so hide it in the // event that we aren't an admin. comments(_, {query}, {loaders, user}) { - if (user == null || !user.hasRole('admin')) { + if (user == null || !user.hasRoles('admin')) { return null; } From 6563f29ccf5f35bd685061a5c5d152f6ea260388 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 20 Jan 2017 17:28:08 -0700 Subject: [PATCH 042/107] Moved tests -> test because mocha likes that --- .dockerignore | 1 + .gitignore | 4 ++-- .nodemon.json | 2 +- package.json | 6 ++++-- {tests => test}/.eslintrc.json | 0 {tests => test}/client/.eslintrc.json | 0 {tests => test}/client/coral-admin/actions/assets.js | 0 {tests => test}/client/coral-admin/reducers/assets.js | 0 .../client/coral-framework/store/itemActions.js | 0 .../client/coral-framework/store/itemReducer.js | 0 .../coral-framework/store/notificationReducer.spec.js | 0 .../client/coral-plugin-history/Comment.spec.js | 0 .../client/coral-plugin-history/CommentHistory.spec.js | 0 {tests => test}/e2e/globals.js | 0 {tests => test}/e2e/mocks.js | 0 {tests => test}/e2e/pages/adminPage.js | 0 {tests => test}/e2e/pages/embedStreamPage.js | 0 {tests => test}/e2e/tests/00_AppTest.js | 0 {tests => test}/e2e/tests/01_EmbedStreamTest.js | 0 {tests => test}/e2e/tests/Admin/LoginTest.js | 0 {tests => test}/e2e/tests/Commenter/FlagCommentTest.js | 0 {tests => test}/e2e/tests/Commenter/FlagUsernameTest.js | 0 {tests => test}/e2e/tests/Commenter/LikeCommentTest.js | 0 {tests => test}/e2e/tests/Commenter/LoginTest.js | 0 {tests => test}/e2e/tests/Commenter/PermalinkTest.js | 0 {tests => test}/e2e/tests/Commenter/PostComment.js | 0 {tests => test}/e2e/tests/EmbedStreamTests.js | 0 {tests => test}/e2e/tests/Moderator/LoginTest.js | 0 {tests => test}/e2e/tests/Visitor/FlagCommentTest.js | 0 {tests => test}/e2e/tests/Visitor/LikeCommentTest.js | 0 {tests => test}/e2e/tests/Visitor/SignUpTest.js | 0 {tests => test}/helpers/browser.js | 0 {tests => test}/helpers/index.test.html | 0 {tests => test}/helpers/mongoose.js | 0 {tests => test}/kue.js | 0 test/mocha.opts | 7 +++++++ {tests => test}/models/action.js | 0 {tests => test}/models/asset.js | 0 {tests => test}/models/comment.js | 0 {tests => test}/models/setting.js | 0 {tests => test}/models/user.js | 0 {tests => test}/mongoose.js | 0 {tests => test}/passport.js | 0 {tests => test}/routes/api/assets/index.js | 0 {tests => test}/routes/api/auth/index.js | 0 {tests => test}/routes/api/comments/index.js | 0 {tests => test}/routes/api/queue/index.js | 0 {tests => test}/routes/api/settings/index.js | 0 {tests => test}/routes/api/stream/index.js | 0 {tests => test}/routes/api/user/index.js | 0 {tests => test}/services/scraper.js | 0 {tests => test}/services/wordlist.js | 0 52 files changed, 15 insertions(+), 5 deletions(-) rename {tests => test}/.eslintrc.json (100%) rename {tests => test}/client/.eslintrc.json (100%) rename {tests => test}/client/coral-admin/actions/assets.js (100%) rename {tests => test}/client/coral-admin/reducers/assets.js (100%) rename {tests => test}/client/coral-framework/store/itemActions.js (100%) rename {tests => test}/client/coral-framework/store/itemReducer.js (100%) rename {tests => test}/client/coral-framework/store/notificationReducer.spec.js (100%) rename {tests => test}/client/coral-plugin-history/Comment.spec.js (100%) rename {tests => test}/client/coral-plugin-history/CommentHistory.spec.js (100%) rename {tests => test}/e2e/globals.js (100%) rename {tests => test}/e2e/mocks.js (100%) rename {tests => test}/e2e/pages/adminPage.js (100%) rename {tests => test}/e2e/pages/embedStreamPage.js (100%) rename {tests => test}/e2e/tests/00_AppTest.js (100%) rename {tests => test}/e2e/tests/01_EmbedStreamTest.js (100%) rename {tests => test}/e2e/tests/Admin/LoginTest.js (100%) rename {tests => test}/e2e/tests/Commenter/FlagCommentTest.js (100%) rename {tests => test}/e2e/tests/Commenter/FlagUsernameTest.js (100%) rename {tests => test}/e2e/tests/Commenter/LikeCommentTest.js (100%) rename {tests => test}/e2e/tests/Commenter/LoginTest.js (100%) rename {tests => test}/e2e/tests/Commenter/PermalinkTest.js (100%) rename {tests => test}/e2e/tests/Commenter/PostComment.js (100%) rename {tests => test}/e2e/tests/EmbedStreamTests.js (100%) rename {tests => test}/e2e/tests/Moderator/LoginTest.js (100%) rename {tests => test}/e2e/tests/Visitor/FlagCommentTest.js (100%) rename {tests => test}/e2e/tests/Visitor/LikeCommentTest.js (100%) rename {tests => test}/e2e/tests/Visitor/SignUpTest.js (100%) rename {tests => test}/helpers/browser.js (100%) rename {tests => test}/helpers/index.test.html (100%) rename {tests => test}/helpers/mongoose.js (100%) rename {tests => test}/kue.js (100%) create mode 100644 test/mocha.opts rename {tests => test}/models/action.js (100%) rename {tests => test}/models/asset.js (100%) rename {tests => test}/models/comment.js (100%) rename {tests => test}/models/setting.js (100%) rename {tests => test}/models/user.js (100%) rename {tests => test}/mongoose.js (100%) rename {tests => test}/passport.js (100%) rename {tests => test}/routes/api/assets/index.js (100%) rename {tests => test}/routes/api/auth/index.js (100%) rename {tests => test}/routes/api/comments/index.js (100%) rename {tests => test}/routes/api/queue/index.js (100%) rename {tests => test}/routes/api/settings/index.js (100%) rename {tests => test}/routes/api/stream/index.js (100%) rename {tests => test}/routes/api/user/index.js (100%) rename {tests => test}/services/scraper.js (100%) rename {tests => test}/services/wordlist.js (100%) diff --git a/.dockerignore b/.dockerignore index 3c3629e64..f05b1f265 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1 +1,2 @@ node_modules +test diff --git a/.gitignore b/.gitignore index 2ec55accd..777dcc7f9 100644 --- a/.gitignore +++ b/.gitignore @@ -3,13 +3,13 @@ npm-debug.log* dist !dist/coral-admin dist/coral-admin/bundle.js -tests/e2e/reports +test/e2e/reports .DS_Store *.iml *.swp dump.rdb .env -gaba.cfg +*.cfg .idea/ coverage/ yarn.lock diff --git a/.nodemon.json b/.nodemon.json index 834e6c054..4289167f7 100644 --- a/.nodemon.json +++ b/.nodemon.json @@ -1,4 +1,4 @@ { "verbose": true, - "ignore": ["tests/*", "client/*", "dist/*"] + "ignore": ["test/*", "client/*", "dist/*"] } diff --git a/package.json b/package.json index 52c49c156..7d4933524 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,9 @@ "build-watch": "NODE_ENV=development webpack --config webpack.config.dev.js --watch", "lint": "eslint bin/* .", "lint-fix": "eslint bin/* . --fix", - "test": "TEST_MODE=unit NODE_ENV=test mocha --compilers js:babel-core/register tests/helpers/*.js --require ignore-styles --recursive tests", - "test-watch": "TEST_MODE=unit NODE_ENV=test mocha --compilers js:babel-core/register --recursive -w tests", + "test": "TEST_MODE=unit NODE_ENV=test mocha", + "test-watch": "TEST_MODE=unit NODE_ENV=test mocha -w", + "test-cover": "TEST_MODE=unit NODE_ENV=test istanbul cover _mocha --report text --check-coverage -- -R spec", "pree2e": "NODE_ENV=test scripts/pree2e.sh", "e2e": "NODE_ENV=test nightwatch", "embed-start": "NODE_ENV=development npm run build && ./bin/cli serve --jobs", @@ -122,6 +123,7 @@ "ignore-styles": "^5.0.1", "immutable": "^3.8.1", "imports-loader": "^0.6.5", + "istanbul": "^1.1.0-alpha.1", "jsdom": "^9.8.3", "json-loader": "^0.5.4", "keymaster": "^1.6.2", diff --git a/tests/.eslintrc.json b/test/.eslintrc.json similarity index 100% rename from tests/.eslintrc.json rename to test/.eslintrc.json diff --git a/tests/client/.eslintrc.json b/test/client/.eslintrc.json similarity index 100% rename from tests/client/.eslintrc.json rename to test/client/.eslintrc.json diff --git a/tests/client/coral-admin/actions/assets.js b/test/client/coral-admin/actions/assets.js similarity index 100% rename from tests/client/coral-admin/actions/assets.js rename to test/client/coral-admin/actions/assets.js diff --git a/tests/client/coral-admin/reducers/assets.js b/test/client/coral-admin/reducers/assets.js similarity index 100% rename from tests/client/coral-admin/reducers/assets.js rename to test/client/coral-admin/reducers/assets.js diff --git a/tests/client/coral-framework/store/itemActions.js b/test/client/coral-framework/store/itemActions.js similarity index 100% rename from tests/client/coral-framework/store/itemActions.js rename to test/client/coral-framework/store/itemActions.js diff --git a/tests/client/coral-framework/store/itemReducer.js b/test/client/coral-framework/store/itemReducer.js similarity index 100% rename from tests/client/coral-framework/store/itemReducer.js rename to test/client/coral-framework/store/itemReducer.js diff --git a/tests/client/coral-framework/store/notificationReducer.spec.js b/test/client/coral-framework/store/notificationReducer.spec.js similarity index 100% rename from tests/client/coral-framework/store/notificationReducer.spec.js rename to test/client/coral-framework/store/notificationReducer.spec.js diff --git a/tests/client/coral-plugin-history/Comment.spec.js b/test/client/coral-plugin-history/Comment.spec.js similarity index 100% rename from tests/client/coral-plugin-history/Comment.spec.js rename to test/client/coral-plugin-history/Comment.spec.js diff --git a/tests/client/coral-plugin-history/CommentHistory.spec.js b/test/client/coral-plugin-history/CommentHistory.spec.js similarity index 100% rename from tests/client/coral-plugin-history/CommentHistory.spec.js rename to test/client/coral-plugin-history/CommentHistory.spec.js diff --git a/tests/e2e/globals.js b/test/e2e/globals.js similarity index 100% rename from tests/e2e/globals.js rename to test/e2e/globals.js diff --git a/tests/e2e/mocks.js b/test/e2e/mocks.js similarity index 100% rename from tests/e2e/mocks.js rename to test/e2e/mocks.js diff --git a/tests/e2e/pages/adminPage.js b/test/e2e/pages/adminPage.js similarity index 100% rename from tests/e2e/pages/adminPage.js rename to test/e2e/pages/adminPage.js diff --git a/tests/e2e/pages/embedStreamPage.js b/test/e2e/pages/embedStreamPage.js similarity index 100% rename from tests/e2e/pages/embedStreamPage.js rename to test/e2e/pages/embedStreamPage.js diff --git a/tests/e2e/tests/00_AppTest.js b/test/e2e/tests/00_AppTest.js similarity index 100% rename from tests/e2e/tests/00_AppTest.js rename to test/e2e/tests/00_AppTest.js diff --git a/tests/e2e/tests/01_EmbedStreamTest.js b/test/e2e/tests/01_EmbedStreamTest.js similarity index 100% rename from tests/e2e/tests/01_EmbedStreamTest.js rename to test/e2e/tests/01_EmbedStreamTest.js diff --git a/tests/e2e/tests/Admin/LoginTest.js b/test/e2e/tests/Admin/LoginTest.js similarity index 100% rename from tests/e2e/tests/Admin/LoginTest.js rename to test/e2e/tests/Admin/LoginTest.js diff --git a/tests/e2e/tests/Commenter/FlagCommentTest.js b/test/e2e/tests/Commenter/FlagCommentTest.js similarity index 100% rename from tests/e2e/tests/Commenter/FlagCommentTest.js rename to test/e2e/tests/Commenter/FlagCommentTest.js diff --git a/tests/e2e/tests/Commenter/FlagUsernameTest.js b/test/e2e/tests/Commenter/FlagUsernameTest.js similarity index 100% rename from tests/e2e/tests/Commenter/FlagUsernameTest.js rename to test/e2e/tests/Commenter/FlagUsernameTest.js diff --git a/tests/e2e/tests/Commenter/LikeCommentTest.js b/test/e2e/tests/Commenter/LikeCommentTest.js similarity index 100% rename from tests/e2e/tests/Commenter/LikeCommentTest.js rename to test/e2e/tests/Commenter/LikeCommentTest.js diff --git a/tests/e2e/tests/Commenter/LoginTest.js b/test/e2e/tests/Commenter/LoginTest.js similarity index 100% rename from tests/e2e/tests/Commenter/LoginTest.js rename to test/e2e/tests/Commenter/LoginTest.js diff --git a/tests/e2e/tests/Commenter/PermalinkTest.js b/test/e2e/tests/Commenter/PermalinkTest.js similarity index 100% rename from tests/e2e/tests/Commenter/PermalinkTest.js rename to test/e2e/tests/Commenter/PermalinkTest.js diff --git a/tests/e2e/tests/Commenter/PostComment.js b/test/e2e/tests/Commenter/PostComment.js similarity index 100% rename from tests/e2e/tests/Commenter/PostComment.js rename to test/e2e/tests/Commenter/PostComment.js diff --git a/tests/e2e/tests/EmbedStreamTests.js b/test/e2e/tests/EmbedStreamTests.js similarity index 100% rename from tests/e2e/tests/EmbedStreamTests.js rename to test/e2e/tests/EmbedStreamTests.js diff --git a/tests/e2e/tests/Moderator/LoginTest.js b/test/e2e/tests/Moderator/LoginTest.js similarity index 100% rename from tests/e2e/tests/Moderator/LoginTest.js rename to test/e2e/tests/Moderator/LoginTest.js diff --git a/tests/e2e/tests/Visitor/FlagCommentTest.js b/test/e2e/tests/Visitor/FlagCommentTest.js similarity index 100% rename from tests/e2e/tests/Visitor/FlagCommentTest.js rename to test/e2e/tests/Visitor/FlagCommentTest.js diff --git a/tests/e2e/tests/Visitor/LikeCommentTest.js b/test/e2e/tests/Visitor/LikeCommentTest.js similarity index 100% rename from tests/e2e/tests/Visitor/LikeCommentTest.js rename to test/e2e/tests/Visitor/LikeCommentTest.js diff --git a/tests/e2e/tests/Visitor/SignUpTest.js b/test/e2e/tests/Visitor/SignUpTest.js similarity index 100% rename from tests/e2e/tests/Visitor/SignUpTest.js rename to test/e2e/tests/Visitor/SignUpTest.js diff --git a/tests/helpers/browser.js b/test/helpers/browser.js similarity index 100% rename from tests/helpers/browser.js rename to test/helpers/browser.js diff --git a/tests/helpers/index.test.html b/test/helpers/index.test.html similarity index 100% rename from tests/helpers/index.test.html rename to test/helpers/index.test.html diff --git a/tests/helpers/mongoose.js b/test/helpers/mongoose.js similarity index 100% rename from tests/helpers/mongoose.js rename to test/helpers/mongoose.js diff --git a/tests/kue.js b/test/kue.js similarity index 100% rename from tests/kue.js rename to test/kue.js diff --git a/test/mocha.opts b/test/mocha.opts new file mode 100644 index 000000000..a159ae7aa --- /dev/null +++ b/test/mocha.opts @@ -0,0 +1,7 @@ +test/helpers/*.js +test +--compilers js:babel-core/register +--require ignore-styles +--recursive +--colors +--sort diff --git a/tests/models/action.js b/test/models/action.js similarity index 100% rename from tests/models/action.js rename to test/models/action.js diff --git a/tests/models/asset.js b/test/models/asset.js similarity index 100% rename from tests/models/asset.js rename to test/models/asset.js diff --git a/tests/models/comment.js b/test/models/comment.js similarity index 100% rename from tests/models/comment.js rename to test/models/comment.js diff --git a/tests/models/setting.js b/test/models/setting.js similarity index 100% rename from tests/models/setting.js rename to test/models/setting.js diff --git a/tests/models/user.js b/test/models/user.js similarity index 100% rename from tests/models/user.js rename to test/models/user.js diff --git a/tests/mongoose.js b/test/mongoose.js similarity index 100% rename from tests/mongoose.js rename to test/mongoose.js diff --git a/tests/passport.js b/test/passport.js similarity index 100% rename from tests/passport.js rename to test/passport.js diff --git a/tests/routes/api/assets/index.js b/test/routes/api/assets/index.js similarity index 100% rename from tests/routes/api/assets/index.js rename to test/routes/api/assets/index.js diff --git a/tests/routes/api/auth/index.js b/test/routes/api/auth/index.js similarity index 100% rename from tests/routes/api/auth/index.js rename to test/routes/api/auth/index.js diff --git a/tests/routes/api/comments/index.js b/test/routes/api/comments/index.js similarity index 100% rename from tests/routes/api/comments/index.js rename to test/routes/api/comments/index.js diff --git a/tests/routes/api/queue/index.js b/test/routes/api/queue/index.js similarity index 100% rename from tests/routes/api/queue/index.js rename to test/routes/api/queue/index.js diff --git a/tests/routes/api/settings/index.js b/test/routes/api/settings/index.js similarity index 100% rename from tests/routes/api/settings/index.js rename to test/routes/api/settings/index.js diff --git a/tests/routes/api/stream/index.js b/test/routes/api/stream/index.js similarity index 100% rename from tests/routes/api/stream/index.js rename to test/routes/api/stream/index.js diff --git a/tests/routes/api/user/index.js b/test/routes/api/user/index.js similarity index 100% rename from tests/routes/api/user/index.js rename to test/routes/api/user/index.js diff --git a/tests/services/scraper.js b/test/services/scraper.js similarity index 100% rename from tests/services/scraper.js rename to test/services/scraper.js diff --git a/tests/services/wordlist.js b/test/services/wordlist.js similarity index 100% rename from tests/services/wordlist.js rename to test/services/wordlist.js From b2d9688c89ff4c0a0490ba2b4d90cf1eb5e54145 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 23 Jan 2017 13:25:24 -0500 Subject: [PATCH 043/107] Adding postAction mutation and fixing lint errors. --- client/coral-embed-stream/src/Comment.js | 8 ++++---- client/coral-embed-stream/src/Embed.js | 4 ++-- client/coral-embed-stream/src/graphql/mutations/index.js | 3 +-- .../src/graphql/mutations/postAction.graphql | 5 +++++ client/coral-plugin-commentbox/CommentBox.js | 1 - 5 files changed, 12 insertions(+), 9 deletions(-) create mode 100644 client/coral-embed-stream/src/graphql/mutations/postAction.graphql diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 4ed0296a2..c5158e31a 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -8,12 +8,12 @@ import React, {PropTypes} from 'react'; import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton'; -import AuthorName from '../../coral-plugin-author-name/AuthorName'; +// import AuthorName from '../../coral-plugin-author-name/AuthorName'; import Content from '../../coral-plugin-commentcontent/CommentContent'; import PubDate from '../../coral-plugin-pubdate/PubDate'; -import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; -import FlagComment from '../../coral-plugin-flags/FlagComment'; -import LikeButton from '../../coral-plugin-likes/LikeButton'; +// import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; +// import FlagComment from '../../coral-plugin-flags/FlagComment'; +// import LikeButton from '../../coral-plugin-likes/LikeButton'; const Comment = ({comment, currentUser, asset, depth}) => { return ( diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index ee2d1f16e..5a44f8532 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -1,5 +1,5 @@ import React, {Component, PropTypes} from 'react'; -import Pym from 'pym.js'; +// import Pym from 'pym.js'; import {compose} from 'react-apollo'; import {connect} from 'react-redux'; @@ -102,7 +102,7 @@ class Embed extends Component { // const banned = (this.props.userData.status === 'banned'); - const {loading, asset, refetch} = this.props.data; + const {loading, asset} = this.props.data; // const {status, moderation, closedMessage, charCount, charCountEnable} = asset.settings; diff --git a/client/coral-embed-stream/src/graphql/mutations/index.js b/client/coral-embed-stream/src/graphql/mutations/index.js index f637a325a..419891c06 100644 --- a/client/coral-embed-stream/src/graphql/mutations/index.js +++ b/client/coral-embed-stream/src/graphql/mutations/index.js @@ -2,7 +2,7 @@ import {graphql} from 'react-apollo'; import POST_COMMENT from './postComment.graphql'; export const postComment = graphql(POST_COMMENT, { - props: ({dispatch, mutate}) => ({ + props: ({mutate}) => ({ postItem: ({asset_id, body}) => { return mutate({ variables: { @@ -13,4 +13,3 @@ export const postComment = graphql(POST_COMMENT, { }); }}), }); - diff --git a/client/coral-embed-stream/src/graphql/mutations/postAction.graphql b/client/coral-embed-stream/src/graphql/mutations/postAction.graphql new file mode 100644 index 000000000..52a0e1735 --- /dev/null +++ b/client/coral-embed-stream/src/graphql/mutations/postAction.graphql @@ -0,0 +1,5 @@ +mutation CreateAction ($action: ActionInput) { + createAction(action:$action) { + ...action + } +} diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index ebafab918..ff9bf7e39 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -31,7 +31,6 @@ class CommentBox extends Component { child_id, addNotification, appendItemArray, - premod, author } = this.props; From d1621ad4245e15d971ac695c8aa56ab4e93cc5c5 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 23 Jan 2017 11:57:35 -0700 Subject: [PATCH 044/107] Fixed context --- graph/context.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/graph/context.js b/graph/context.js index cdc05f6cf..ce8f5eb83 100644 --- a/graph/context.js +++ b/graph/context.js @@ -8,7 +8,9 @@ class Context { constructor({user = null}) { // Load the current logged in user to `user`, otherwise this'll be null. - this.user = user; + if (user) { + this.user = user; + } // Create the loaders. this.loaders = loaders(this); From a8227bd4add14f8fa670a8b7abf9cc61272c45d5 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 23 Jan 2017 14:22:25 -0500 Subject: [PATCH 045/107] Activating post and delete actions on likebutton. --- client/coral-embed-stream/src/Comment.js | 25 ++++++++++++++--- client/coral-embed-stream/src/Embed.js | 7 ++++- client/coral-embed-stream/src/Stream.js | 6 +++-- .../graphql/mutations/deleteAction.graphql | 3 +++ .../src/graphql/mutations/index.js | 24 +++++++++++++++++ .../src/graphql/mutations/postAction.graphql | 4 +-- .../src/graphql/queries/index.js | 2 +- client/coral-plugin-likes/LikeButton.js | 27 +++++++------------ 8 files changed, 71 insertions(+), 27 deletions(-) create mode 100644 client/coral-embed-stream/src/graphql/mutations/deleteAction.graphql diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index c5158e31a..2817f4675 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -13,9 +13,10 @@ import Content from '../../coral-plugin-commentcontent/CommentContent'; import PubDate from '../../coral-plugin-pubdate/PubDate'; // import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; // import FlagComment from '../../coral-plugin-flags/FlagComment'; -// import LikeButton from '../../coral-plugin-likes/LikeButton'; +import LikeButton from '../../coral-plugin-likes/LikeButton'; -const Comment = ({comment, currentUser, asset, depth}) => { +const Comment = ({comment, currentUser, asset, depth, showSignInDialog, postAction, deleteAction}) => { + const like = comment.actions.filter((a) => a.type === 'LIKE')[0]; return (
{
- + {/* + + */} +
- + {/* + + */}
{ @@ -49,6 +62,10 @@ const Comment = ({comment, currentUser, asset, depth}) => { depth={depth + 1} asset={asset} currentUser={currentUser} + currentUser={currentUser} + postAction={postAction} + deleteAction={deleteAction} + showSignInDialog={showSignInDialog} key={reply.id} comment={reply} />; }) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 5a44f8532..4d44a10b6 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -3,7 +3,7 @@ import React, {Component, PropTypes} from 'react'; import {compose} from 'react-apollo'; import {connect} from 'react-redux'; -import {postComment} from './graphql/mutations'; +import {postComment, postAction, deleteAction} from './graphql/mutations'; import {queryStream} from './graphql/queries'; import { @@ -149,6 +149,9 @@ class Embed extends Component { ({ export default compose( connect(mapStateToProps, mapDispatchToProps), postComment, + postAction, + deleteAction, queryStream )(Embed); diff --git a/client/coral-embed-stream/src/Stream.js b/client/coral-embed-stream/src/Stream.js index 78a988553..74a1c7b65 100644 --- a/client/coral-embed-stream/src/Stream.js +++ b/client/coral-embed-stream/src/Stream.js @@ -1,8 +1,7 @@ import React, {PropTypes} from 'react'; import Comment from './Comment'; -const Stream = ({comments, currentUser, asset}) => { - console.log('currentUser', currentUser); +const Stream = ({comments, currentUser, asset, postAction, deleteAction, showSignInDialog}) => { return (
{ @@ -11,6 +10,9 @@ const Stream = ({comments, currentUser, asset}) => { depth={0} asset={asset} currentUser={currentUser} + postAction={postAction} + deleteAction={deleteAction} + showSignInDialog={showSignInDialog} key={comment.id} comment={comment} />; }) diff --git a/client/coral-embed-stream/src/graphql/mutations/deleteAction.graphql b/client/coral-embed-stream/src/graphql/mutations/deleteAction.graphql new file mode 100644 index 000000000..bfce8cf6a --- /dev/null +++ b/client/coral-embed-stream/src/graphql/mutations/deleteAction.graphql @@ -0,0 +1,3 @@ +mutation deleteAction ($id: ID!) { + deleteAction(id:$id) +} diff --git a/client/coral-embed-stream/src/graphql/mutations/index.js b/client/coral-embed-stream/src/graphql/mutations/index.js index 419891c06..d3c0a6080 100644 --- a/client/coral-embed-stream/src/graphql/mutations/index.js +++ b/client/coral-embed-stream/src/graphql/mutations/index.js @@ -1,5 +1,7 @@ import {graphql} from 'react-apollo'; import POST_COMMENT from './postComment.graphql'; +import POST_ACTION from './postAction.graphql'; +import DELETE_ACTION from './deleteAction.graphql'; export const postComment = graphql(POST_COMMENT, { props: ({mutate}) => ({ @@ -13,3 +15,25 @@ export const postComment = graphql(POST_COMMENT, { }); }}), }); + +export const postAction = graphql(POST_ACTION, { + props: ({mutate}) => ({ + postAction: (action) => { + return mutate({ + variables: { + action + } + }); + }}), +}); + +export const deleteAction = graphql(DELETE_ACTION, { + props: ({mutate}) => ({ + deleteAction: (id) => { + return mutate({ + variables: { + id + } + }); + }}), +}); diff --git a/client/coral-embed-stream/src/graphql/mutations/postAction.graphql b/client/coral-embed-stream/src/graphql/mutations/postAction.graphql index 52a0e1735..fff737fa8 100644 --- a/client/coral-embed-stream/src/graphql/mutations/postAction.graphql +++ b/client/coral-embed-stream/src/graphql/mutations/postAction.graphql @@ -1,5 +1,5 @@ -mutation CreateAction ($action: ActionInput) { +mutation CreateAction ($action: CreateActionInput!) { createAction(action:$action) { - ...action + id } } diff --git a/client/coral-embed-stream/src/graphql/queries/index.js b/client/coral-embed-stream/src/graphql/queries/index.js index 47f585449..ecf875aaa 100644 --- a/client/coral-embed-stream/src/graphql/queries/index.js +++ b/client/coral-embed-stream/src/graphql/queries/index.js @@ -3,7 +3,7 @@ import STREAM_QUERY from './streamQuery.graphql'; import Pym from 'pym.js'; const pym = new Pym.Child({polling: 100}); -let url = pym.parentUrl; +let url = pym.parentUrl || 'http://localhost:3000/'; export const queryStream = graphql(STREAM_QUERY, { options: {variables: {asset_url: url}} diff --git a/client/coral-plugin-likes/LikeButton.js b/client/coral-plugin-likes/LikeButton.js index fae30c8c7..796ad159c 100644 --- a/client/coral-plugin-likes/LikeButton.js +++ b/client/coral-plugin-likes/LikeButton.js @@ -4,33 +4,26 @@ import translations from './translations.json'; const name = 'coral-plugin-likes'; -const LikeButton = ({like, id, postAction, deleteAction, addItem, showSignInDialog, updateItem, currentUser, banned}) => { - const liked = like && like.current_user; +const LikeButton = ({like, id, postAction, deleteAction, showSignInDialog, currentUser}) => { + const liked = like && like.current; const onLikeClick = () => { if (!currentUser) { const offset = document.getElementById(`c_${id}`).getBoundingClientRect().top - 75; showSignInDialog(offset); return; } - if (banned) { + if (currentUser.banned) { return; } if (!liked) { - const action = { - action_type: 'like' - }; - postAction(id, 'comments', action) - .then((action) => { - let id = `${action.action_type}_${action.item_id}`; - addItem({id, current_user: action, count: like ? like.count + 1 : 1}, 'actions'); - updateItem(action.item_id, action.action_type, id, 'comments'); - }); + postAction({ + item_id: id, + item_type: 'COMMENTS', + action_type: 'LIKE' + }); + // TODO: frontend update from mutation } else { - deleteAction(liked.id) - .then(() => { - updateItem(like.id, 'count', like.count - 1, 'actions'); - updateItem(like.id, 'current_user', false, 'actions'); - }); + deleteAction(liked.id); } }; From da761468b66720478c3bd7f9b8385dd9d590ad22 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 23 Jan 2017 16:18:51 -0500 Subject: [PATCH 046/107] Enabling like buttons. --- client/coral-embed-stream/src/Embed.js | 2 +- client/coral-plugin-likes/LikeButton.js | 101 ++++++++++++++++-------- 2 files changed, 67 insertions(+), 36 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 4d44a10b6..cd55c13cd 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -151,7 +151,7 @@ class Embed extends Component { currentUser={user} postAction={this.props.postAction} deleteAction={this.props.deleteAction} - showSignInDialog={showSignInDialog} + showSignInDialog={this.props.showSignInDialog} comments={asset.comments} /> { - const liked = like && like.current; - const onLikeClick = () => { - if (!currentUser) { - const offset = document.getElementById(`c_${id}`).getBoundingClientRect().top - 75; - showSignInDialog(offset); - return; - } - if (currentUser.banned) { - return; - } - if (!liked) { - postAction({ - item_id: id, - item_type: 'COMMENTS', - action_type: 'LIKE' - }); - // TODO: frontend update from mutation - } else { - deleteAction(liked.id); - } - }; +class LikeButton extends Component { - return
- -
; -}; + if (currentUser.banned) { + return; + } + if (!liked) { + this.setState({localPost: 'temp', localDelete: false}); + postAction({ + item_id: id, + item_type: 'COMMENTS', + action_type: 'LIKE' + }).then(({data}) => { + this.setState({localPost: data.createAction.id}); + }); + } else { + this.setState((prev) => prev.localPost ? {...prev, localPost: null} : {...prev, localDelete: true}); + deleteAction(localPost || like.current.id); + } + }; + + return
+ +
; + } +} export default LikeButton; From 44058b1718d73769e15f06808c70165f2841a229 Mon Sep 17 00:00:00 2001 From: David Jay Date: Mon, 23 Jan 2017 17:34:24 -0500 Subject: [PATCH 047/107] Adding flag action buttons. --- client/coral-embed-stream/src/Comment.js | 19 +++++--- client/coral-plugin-flags/FlagBio.js | 2 +- client/coral-plugin-flags/FlagButton.js | 55 ++++++++++++++---------- client/coral-plugin-flags/FlagComment.js | 6 +-- 4 files changed, 50 insertions(+), 32 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 2817f4675..6e3744aab 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -12,11 +12,14 @@ import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton'; import Content from '../../coral-plugin-commentcontent/CommentContent'; import PubDate from '../../coral-plugin-pubdate/PubDate'; // import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; -// import FlagComment from '../../coral-plugin-flags/FlagComment'; +import FlagComment from '../../coral-plugin-flags/FlagComment'; import LikeButton from '../../coral-plugin-likes/LikeButton'; +const getAction = (type, comment) => comment.actions.filter((a) => a.type === type)[0]; + const Comment = ({comment, currentUser, asset, depth, showSignInDialog, postAction, deleteAction}) => { - const like = comment.actions.filter((a) => a.type === 'LIKE')[0]; + const like = getAction('LIKE', comment); + const flag = getAction('FLAG', comment); return (
- {/* - - */} +
{ diff --git a/client/coral-plugin-flags/FlagBio.js b/client/coral-plugin-flags/FlagBio.js index edc0a5286..2639ab8cf 100644 --- a/client/coral-plugin-flags/FlagBio.js +++ b/client/coral-plugin-flags/FlagBio.js @@ -9,7 +9,7 @@ const getPopupMenu = [ () => { return { header: lang.t('step-2-header'), - itemType: 'user', + itemType: 'USERS', field: 'bio', options: [ {val: 'This bio is offensive', text: lang.t('bio-offensive')}, diff --git a/client/coral-plugin-flags/FlagButton.js b/client/coral-plugin-flags/FlagButton.js index 8fc747ce1..b0d1cea4d 100644 --- a/client/coral-plugin-flags/FlagButton.js +++ b/client/coral-plugin-flags/FlagButton.js @@ -14,22 +14,31 @@ class FlagButton extends Component { reason: '', note: '', step: 0, - posted: false + localPost: null, + localDelete: false } // When the "report" button is clicked expand the menu onReportClick = () => { - if (!this.props.currentUser) { + const {currentUser, flag, deleteAction} = this.props; + const {localPost, localDelete} = this.state; + const flagged = (flag && flag.current && !localDelete) || localPost; + if (!currentUser) { const offset = document.getElementById(`c_${this.props.id}`).getBoundingClientRect().top - 75; this.props.showSignInDialog(offset); return; } - this.setState({showMenu: !this.state.showMenu}); + if (flagged) { + this.setState((prev) => prev.localPost ? {...prev, localPost: null, step: 0} : {...prev, localDelete: true}); + deleteAction(localPost || flag.current.id); + } else { + this.setState({showMenu: !this.state.showMenu}); + } } onPopupContinue = () => { - const {postAction, addItem, updateItem, flag, id, author_id} = this.props; - const {itemType, field, reason, step, note, posted} = this.state; + const {postAction, id, author_id} = this.props; + const {itemType, reason, step, localPost} = this.state; // Proceed to the next step or close the menu if we've reached the end if (step + 1 >= this.props.getPopupMenu.length) { @@ -39,33 +48,32 @@ class FlagButton extends Component { } // If itemType and reason are both set, post the action - if (itemType && reason && !posted) { + if (itemType && reason && !localPost) { // Set the text from the "other" field if it exists. let item_id; switch(itemType) { - case 'comments': + case 'COMMENTS': item_id = id; break; - case 'users': + case 'USERS': item_id = author_id; break; } - const action = { - action_type: 'flag', - metadata: { - field, - reason, - note + + // Note: Action metadata has been temporarily removed. + if (itemType === 'COMMENTS') { + this.setState({localPost: 'temp'}); + } + postAction({ + item_id, + item_type: itemType, + action_type: 'FLAG' + }).then(({data}) => { + if (itemType === 'COMMENTS') { + this.setState({localPost: data.createAction.id}); } - }; - postAction(item_id, itemType, action) - .then((action) => { - let id = `${action.action_type}_${action.item_id}`; - addItem({id, current_user: action, count: flag ? flag.count + 1 : 1}, 'actions'); - updateItem(action.item_id, action.action_type, id, action.item_type); - this.setState({posted: true}); - }); + }); } } @@ -98,7 +106,8 @@ class FlagButton extends Component { render () { const {flag, getPopupMenu} = this.props; - const flagged = flag && flag.current_user; + const {localPost, localDelete} = this.state; + const flagged = (flag && flag.current && !localDelete) || localPost; const popupMenu = getPopupMenu[this.state.step](this.state.itemType); return
diff --git a/client/coral-plugin-flags/FlagComment.js b/client/coral-plugin-flags/FlagComment.js index 54dc954d9..5ea2e9fa1 100644 --- a/client/coral-plugin-flags/FlagComment.js +++ b/client/coral-plugin-flags/FlagComment.js @@ -10,15 +10,15 @@ const getPopupMenu = [ return { header: lang.t('step-1-header'), options: [ - {val: 'users', text: lang.t('flag-username')}, - {val: 'comments', text: lang.t('flag-comment')} + {val: 'USERS', text: lang.t('flag-username')}, + {val: 'COMMENTS', text: lang.t('flag-comment')} ], button: lang.t('continue'), sets: 'itemType' }; }, (itemType) => { - const options = itemType === 'comments' ? + const options = itemType === 'COMMENTS' ? [ {val: 'I don\'t agree with this comment', text: lang.t('no-agree-comment')}, {val: 'This comment is offensive', text: lang.t('comment-offensive')}, From adf4729d89c1cd7beb7196aefb91ba99faae0858 Mon Sep 17 00:00:00 2001 From: Belen Curcio Date: Mon, 23 Jan 2017 19:45:34 -0300 Subject: [PATCH 048/107] Resolver settings fix, info box and func working! --- graph/resolvers/asset.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index f6c055f42..7bb08182f 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -7,9 +7,9 @@ const Asset = { .then((globalSettings) => { if (settings) { - settings = Object.assign({}, settings, globalSettings); + settings = Object.assign({}, settings, globalSettings.toObject()); } else { - settings = globalSettings; + settings = globalSettings.toObject(); } return settings; From 9e22c100f04a15d728a8373a64b03ae9edc09044 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 23 Jan 2017 16:31:37 -0700 Subject: [PATCH 049/107] Added admin login form (naive approach) --- app.js | 2 +- graph/resolvers/index.js | 2 + graph/resolvers/settings.js | 3 + package.json | 1 - routes/admin/index.js | 6 +- views/admin/login.ejs | 132 +++++++++++++++++++++++++++ views/{ => admin}/password-reset.ejs | 4 +- 7 files changed, 146 insertions(+), 4 deletions(-) create mode 100644 graph/resolvers/settings.js create mode 100644 views/admin/login.ejs rename views/{ => admin}/password-reset.ejs (97%) diff --git a/app.js b/app.js index 9aa420e0e..c04e0cb4a 100644 --- a/app.js +++ b/app.js @@ -51,7 +51,7 @@ const session_opts = { name: 'talk.sid', cookie: { secure: false, - maxAge: 18000000, // 30 minutes for expiry. + maxAge: 36000000, // 1 hour for expiry. }, store: new RedisStore({ ttl: 1800, diff --git a/graph/resolvers/index.js b/graph/resolvers/index.js index 6cb85a85f..064b1e664 100644 --- a/graph/resolvers/index.js +++ b/graph/resolvers/index.js @@ -4,6 +4,7 @@ const Asset = require('./asset'); const Comment = require('./comment'); const RootMutation = require('./root_mutation'); const RootQuery = require('./root_query'); +const Settings = require('./settings'); const User = require('./user'); module.exports = { @@ -13,5 +14,6 @@ module.exports = { Comment, RootMutation, RootQuery, + Settings, User }; diff --git a/graph/resolvers/settings.js b/graph/resolvers/settings.js new file mode 100644 index 000000000..72608a9c4 --- /dev/null +++ b/graph/resolvers/settings.js @@ -0,0 +1,3 @@ +const Settings = {}; + +module.exports = Settings; diff --git a/package.json b/package.json index 7d4933524..5c375a5b4 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,6 @@ "lint": "eslint bin/* .", "lint-fix": "eslint bin/* . --fix", "test": "TEST_MODE=unit NODE_ENV=test mocha", - "test-watch": "TEST_MODE=unit NODE_ENV=test mocha -w", "test-cover": "TEST_MODE=unit NODE_ENV=test istanbul cover _mocha --report text --check-coverage -- -R spec", "pree2e": "NODE_ENV=test scripts/pree2e.sh", "e2e": "NODE_ENV=test nightwatch", diff --git a/routes/admin/index.js b/routes/admin/index.js index 6241dcedb..283295af1 100644 --- a/routes/admin/index.js +++ b/routes/admin/index.js @@ -3,13 +3,17 @@ const router = express.Router(); // Get /password-reset expects a signed token (JWT) in the hash. // Links to this endpoint are generated by /views/password-reset-email.ejs. -router.get('/password-reset', (req, res, next) => { +router.get('/password-reset', (req, res) => { // 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('/login', (req, res, next) => { + res.render('login'); +}); + router.get('*', (req, res) => { res.render('admin', {basePath: '/client/coral-admin'}); }); diff --git a/views/admin/login.ejs b/views/admin/login.ejs new file mode 100644 index 000000000..e88dd86fd --- /dev/null +++ b/views/admin/login.ejs @@ -0,0 +1,132 @@ + + + + + + Admin Login + + + + + + +
+
+ Admin Login + + + + +
+
+
+ + + + diff --git a/views/password-reset.ejs b/views/admin/password-reset.ejs similarity index 97% rename from views/password-reset.ejs rename to views/admin/password-reset.ejs index ec905c041..06b9d2a86 100644 --- a/views/password-reset.ejs +++ b/views/admin/password-reset.ejs @@ -3,7 +3,6 @@ - Password Reset @@ -122,6 +121,9 @@ url: '/api/v1/account/password/reset', contentType: 'application/json', method: 'PUT', + headers: { + 'X-CSRF-Token': '<%= csrfToken %>' + }, data: JSON.stringify({password: password, token: location.hash.replace('#', '')}) }).then(function (success) { location.href = '<%= redirectUri %>'; From 6282c39f3ae9c1adc7c986e27975350d7e062df3 Mon Sep 17 00:00:00 2001 From: Riley Davis Date: Mon, 23 Jan 2017 16:52:16 -0700 Subject: [PATCH 050/107] merge remote. can reply to a comment --- client/coral-embed-stream/src/Comment.js | 226 +++++++++++------- client/coral-embed-stream/src/Embed.js | 33 ++- client/coral-embed-stream/src/Stream.js | 6 +- .../src/graphql/mutations/index.js | 4 +- client/coral-plugin-commentbox/CommentBox.js | 71 +++--- client/coral-plugin-flags/FlagButton.js | 2 +- client/coral-plugin-replies/ReplyBox.js | 31 ++- client/coral-plugin-replies/ReplyButton.js | 32 ++- 8 files changed, 245 insertions(+), 160 deletions(-) diff --git a/client/coral-embed-stream/src/Comment.js b/client/coral-embed-stream/src/Comment.js index 6e3744aab..21e98ae64 100644 --- a/client/coral-embed-stream/src/Comment.js +++ b/client/coral-embed-stream/src/Comment.js @@ -11,103 +11,157 @@ import PermalinkButton from 'coral-plugin-permalinks/PermalinkButton'; // import AuthorName from '../../coral-plugin-author-name/AuthorName'; import Content from '../../coral-plugin-commentcontent/CommentContent'; import PubDate from '../../coral-plugin-pubdate/PubDate'; -// import {ReplyBox, ReplyButton} from '../../coral-plugin-replies'; +import {ReplyBox, ReplyButton} from 'coral-plugin-replies'; import FlagComment from '../../coral-plugin-flags/FlagComment'; import LikeButton from '../../coral-plugin-likes/LikeButton'; const getAction = (type, comment) => comment.actions.filter((a) => a.type === type)[0]; -const Comment = ({comment, currentUser, asset, depth, showSignInDialog, postAction, deleteAction}) => { - const like = getAction('LIKE', comment); - const flag = getAction('FLAG', comment); - return ( -
-
- {/* */} - - -
- {/* - - */} - -
-
- +
+ {/* - -
- { - comment.replies && - comment.replies.map(reply => { - return */} + + + + { + currentUser + ?
+ { + console.log('reply button click'); + this.setState({replyBoxVisible: !this.state.replyBoxVisible}); + }} + parentCommentId={comment.id} + currentUserId={currentUser.id} + banned={false} /> + +
+ : null + } +
+ { + currentUser + ? ; - }) - } + currentUser={currentUser} /> + : null + } -
- ); -}; + +
+ { + this.state.replyBoxVisible + ? + : null + } + { + comment.replies && + comment.replies.map(reply => { + return ; + }) + } -Comment.propTypes = { - depth: PropTypes.number.isRequired, - asset: PropTypes.shape({ - id: PropTypes.string, - title: PropTypes.string, - url: PropTypes.string - }).isRequired, - currentUser: PropTypes.object, - comment: PropTypes.shape({ - depth: PropTypes.number, - actions: PropTypes.array.isRequired, - body: PropTypes.string.isRequired, - id: PropTypes.string.isRequired, - replies: PropTypes.arrayOf( - PropTypes.shape({ - body: PropTypes.string.isRequired, - id: PropTypes.string.isRequired - }) - ), - user: PropTypes.shape({ - id: PropTypes.string.isRequired, - name: PropTypes.string.isRequired - }).isRequired - }).isRequired -}; +
+ ); + } +} export default Comment; diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index cd55c13cd..8ddc215cb 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -102,7 +102,7 @@ class Embed extends Component { // const banned = (this.props.userData.status === 'banned'); - const {loading, asset} = this.props.data; + const {loading, asset, refetch} = this.props.data; // const {status, moderation, closedMessage, charCount, charCountEnable} = asset.settings; @@ -129,24 +129,31 @@ class Embed extends Component { enable={asset.settings.infoBoxEnable} /> }> - + { + user + ? + : null + }
:

{asset.settings.closedMessage}

} {!loggedIn && } { +const Stream = ({comments, currentUser, asset, postItem, addNotification, postAction, deleteAction, showSignInDialog}) => { return (
{ comments.map(comment => { return ({ - postItem: ({asset_id, body}) => { + postItem: ({asset_id, body, parent_id} /*, type */) => { return mutate({ variables: { asset_id, body, - parent_id: null + parent_id } }); }}), diff --git a/client/coral-plugin-commentbox/CommentBox.js b/client/coral-plugin-commentbox/CommentBox.js index ff9bf7e39..90113180c 100644 --- a/client/coral-plugin-commentbox/CommentBox.js +++ b/client/coral-plugin-commentbox/CommentBox.js @@ -8,11 +8,13 @@ const name = 'coral-plugin-commentbox'; class CommentBox extends Component { static propTypes = { - postItem: PropTypes.func, - updateItem: PropTypes.func, - id: PropTypes.string, - comments: PropTypes.array, - reply: PropTypes.bool, + postItem: PropTypes.func.isRequired, + // updateItem: PropTypes.func, + assetId: PropTypes.string.isRequired, + parentId: PropTypes.string, + authorId: PropTypes.string.isRequired, + // comments: PropTypes.array, + isReply: PropTypes.bool.isRequired, canPost: PropTypes.bool, currentUser: PropTypes.object } @@ -25,33 +27,36 @@ class CommentBox extends Component { postComment = () => { const { postItem, - updateItem, - id, - parent_id, - child_id, + // updateItem, + assetId, + parentId, + // child_id, addNotification, - appendItemArray, - author + // appendItemArray, + authorId } = this.props; let comment = { body: this.state.body, - asset_id: id, - author_id: author.id + asset_id: assetId, + author_id: authorId, + parent_id: parentId }; - let related; - let parent_type; - if (parent_id) { - comment.parent_id = parent_id; - related = 'children'; - parent_type = 'comments'; - } else { - related = 'comments'; - parent_type = 'assets'; - } - if (child_id || parent_id) { - updateItem(child_id || parent_id, 'showReply', false, 'comments'); - } + + console.log('CommentBox.parentId', parentId); + // let related; + // let parent_type; + // if (parent_id) { + // comment.parent_id = parent_id; + // related = 'children'; + // parent_type = 'comments'; + // } else { + // related = 'comments'; + // parent_type = 'assets'; + // } + // if (child_id || parent_id) { + // updateItem(child_id || parent_id, 'showReply', false, 'comments'); + // } if (this.props.charCount && this.state.body.length > this.props.charCount) { return; @@ -59,13 +64,13 @@ class CommentBox extends Component { postItem(comment, 'comments') .then(({data}) => { const postedComment = data.createComment; - const commentId = postedComment.id; + // const commentId = postedComment.id; if (postedComment.status === 'rejected') { addNotification('error', lang.t('comment-post-banned-word')); } else if (postedComment.status === 'PREMOD') { addNotification('success', lang.t('comment-post-notif-premod')); } else { - appendItemArray(parent_id || id, related, commentId, !parent_id, parent_type); + // appendItemArray(parent_id || id, related, commentId, !parent_id, parent_type); addNotification('success', 'Your comment has been posted.'); } }) @@ -74,23 +79,23 @@ class CommentBox extends Component { } render () { - const {styles, reply, author, charCount} = this.props; + const {styles, isReply, authorId, charCount} = this.props; const length = this.state.body.length; return