From 735aa6fbd35cdd8528c89db85f4495411da4a569 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 7 Apr 2017 16:40:30 -0600 Subject: [PATCH 01/10] Added new Metadata Service and models --- models/asset.js | 6 ++++ models/comment.js | 8 ++++- services/metadata.js | 80 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 services/metadata.js diff --git a/models/asset.js b/models/asset.js index f0046aef0..51ff42705 100644 --- a/models/asset.js +++ b/models/asset.js @@ -46,6 +46,12 @@ const AssetSchema = new Schema({ type: Schema.Types.Mixed, default: null }, + + // Additional metadata stored on the field. + metadata: { + default: {}, + type: Object + } }, { versionKey: false, timestamps: { diff --git a/models/comment.js b/models/comment.js index e6523e45f..0984a0832 100644 --- a/models/comment.js +++ b/models/comment.js @@ -75,7 +75,13 @@ const CommentSchema = new Schema({ default: 'NONE' }, tags: [TagSchema], - parent_id: String + parent_id: String, + + // Additional metadata stored on the field. + metadata: { + default: {}, + type: Object + } }, { timestamps: { createdAt: 'created_at', diff --git a/services/metadata.js b/services/metadata.js new file mode 100644 index 000000000..a0c433f8b --- /dev/null +++ b/services/metadata.js @@ -0,0 +1,80 @@ +/** + * The key must be composed of alpha characters with periods seperating them. + */ +const KEY_REGEX = /^(?:[A-Za-z][A-Za-z\.]*[A-Za-z])?(?:[A-Za-z]*)$/; + +/** + * Allows metadata properties to be set/unset from specific models. It is the + * expecatation of this API that the metadata field is either accessed later + * directly, or accessed as a result of another database load rather than + * this service providing an interface to do so. + * + * @class MetadataService + */ +class MetadataService { + + /** + * Parses a key by ensuring that if it is either a string, or an array with + * only characters defined in the `KEY_REGEX` + * + * @static + * @param {String|Array} key + * @returns {String} string form of the key + * + * @memberOf Metadata + */ + static parseKey(key) { + if (Array.isArray(key)) { + key = key.join('.'); + } + + if ((typeof key !== 'string') || !KEY_REGEX.test(key) || key.length === 0) { + throw new Error(`${key} is not valid, only a-zA-Z. allowed`); + } + + return ['metadata', key].join('.'); + } + + /** + * Sets an object on the metadata field of an object. + * + * @static + * @param {mongoose.Model} model the mongoose model for the object + * @param {String} id the value for the field `id` of the model + * @param {String|Array} key key for the metadata field + * @param {any} value javascript object to set the value of the metadata to + * @returns {Promise} resolves when the update is complete + * + * @memberOf Metadata + */ + static async set(model, id, key, value) { + key = MetadataService.parseKey(key); + + return model.update({id}, { + $set: { + [key]: value + } + }); + } + + /** + * Removes the value for the metadata field as the specific key. + * + * @static + * @param {mongoose.Model} model the mongoose model for the object + * @param {String} id the value for the field `id` of the model + * @param {String|Array} key key for the metadata field + * @returns + * + * @memberOf Metadata + */ + static async unset(model, id, key) { + key = MetadataService.parseKey(key); + + return model.update({id}, { + $unset: key + }); + } +} + +module.exports = MetadataService; From 8e33d25a2801c9c0816ccaaa398f9ff4a1261f86 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Mon, 10 Apr 2017 12:12:07 -0600 Subject: [PATCH 02/10] Some bug fixes + validation updates --- graph/hooks.js | 8 ++++++++ services/metadata.js | 2 +- 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/graph/hooks.js b/graph/hooks.js index 499eb60b1..c5b7541b3 100644 --- a/graph/hooks.js +++ b/graph/hooks.js @@ -3,6 +3,7 @@ const { GraphQLInterfaceType } = require('graphql'); const debug = require('debug')('talk:graph:schema'); +const Joi = require('joi'); /** * XXX taken from graphql-js: src/execution/execute.js, because that function @@ -82,6 +83,8 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa Object.keys(hooks).forEach((hook) => { switch (hook) { case 'pre': + Joi.assert(hooks.pre, Joi.func().maxArity(4)); + debug(`adding pre hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); if (typeof hooks.pre !== 'function') { @@ -91,6 +94,8 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa acc.pre.push(hooks.pre); break; case 'post': + Joi.assert(hooks.pre, Joi.func().maxArity(5)); + debug(`adding post hook to resolver ${typeName}.${fieldName} from plugin '${plugin.name}'`); if (typeof hooks.post !== 'function') { @@ -129,6 +134,9 @@ const decorateWithHooks = (schema, hooks) => forEachField(schema, (field, typeNa return; } + // Ensure it matches the format we expect. + Joi.assert(post, Joi.array().items(Joi.func().maxArity(3)), `invalid post hooks were found for ${typeName}.${fieldName}`); + // Cache the original resolverType function. let resolveType = field.resolveType; diff --git a/services/metadata.js b/services/metadata.js index a0c433f8b..2cabed8b8 100644 --- a/services/metadata.js +++ b/services/metadata.js @@ -72,7 +72,7 @@ class MetadataService { key = MetadataService.parseKey(key); return model.update({id}, { - $unset: key + $unset: {[key]: ''} }); } } From 56dedb551f4c8bf88c04a0b0aec7a01db259f082 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 11 Apr 2017 15:02:04 -0700 Subject: [PATCH 03/10] Adds click to showAll and refresh query to the '# comments' tab. --- client/coral-embed-stream/src/Embed.js | 8 +++++++- client/coral-plugin-comment-count/CommentCount.js | 4 ++-- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index d9b5c8310..4df0290ac 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -144,7 +144,13 @@ class Embed extends Component {
- + { + this.props.viewAllComments(); + this.props.data.refetch(); + }}/> + {lang.t('MY_COMMENTS')} Configure Stream diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index 764984634..e53944025 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -3,8 +3,8 @@ import {I18n} from '../coral-framework'; import translations from './translations.json'; const name = 'coral-plugin-comment-count'; -const CommentCount = ({count}) => { - return
+const CommentCount = ({count, onClick}) => { + return
onClick()}> {`${count} ${count === 1 ? lang.t('comment') : lang.t('comment-plural')}`}
; }; From f900727455521a03271f08172343ea53e975fd48 Mon Sep 17 00:00:00 2001 From: gaba Date: Tue, 11 Apr 2017 17:57:56 -0700 Subject: [PATCH 04/10] Move the method to the class, changes name and removes wrapper. --- client/coral-embed-stream/src/Embed.js | 25 +++++++++++++------ .../CommentCount.js | 4 +-- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index 4df0290ac..aa4223757 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -1,4 +1,4 @@ -import React, {Component} from 'react'; +import React from 'react'; import {compose} from 'react-apollo'; import {connect} from 'react-redux'; import isEqual from 'lodash/isEqual'; @@ -36,9 +36,18 @@ import HighlightedComment from './Comment'; import LoadMore from './LoadMore'; import NewCount from './NewCount'; -class Embed extends Component { +class Embed extends React.Component { - state = {activeTab: 0, showSignInDialog: false, activeReplyBox: ''}; + constructor(props) { + super(props); + this.state = { + activeTab: 0, + showSignInDialog: + false, activeReplyBox: '' + }; + + this.handleClick = this.handleClick.bind(this); + } changeTab = (tab) => { const {isAdmin} = this.props.auth; @@ -114,6 +123,11 @@ class Embed extends Component { } } + handleClick() { + this.props.viewAllComments(); + this.props.data.refetch(); + } + render () { const {activeTab} = this.state; const {closedAt, countCache = {}} = this.props.asset; @@ -146,10 +160,7 @@ class Embed extends Component { { - this.props.viewAllComments(); - this.props.data.refetch(); - }}/> + handleClick={this.handleClick}/> {lang.t('MY_COMMENTS')} Configure Stream diff --git a/client/coral-plugin-comment-count/CommentCount.js b/client/coral-plugin-comment-count/CommentCount.js index e53944025..be38d1425 100644 --- a/client/coral-plugin-comment-count/CommentCount.js +++ b/client/coral-plugin-comment-count/CommentCount.js @@ -3,8 +3,8 @@ import {I18n} from '../coral-framework'; import translations from './translations.json'; const name = 'coral-plugin-comment-count'; -const CommentCount = ({count, onClick}) => { - return
onClick()}> +const CommentCount = ({count, handleClick}) => { + return
{`${count} ${count === 1 ? lang.t('comment') : lang.t('comment-plural')}`}
; }; From f4f8e5053f1a0d59d040631c687fe7cb536a0e5c Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 12 Apr 2017 10:11:21 -0700 Subject: [PATCH 05/10] Remove the binding in the constructor. --- client/coral-embed-stream/src/Embed.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index aa4223757..453d645c2 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -42,11 +42,9 @@ class Embed extends React.Component { super(props); this.state = { activeTab: 0, - showSignInDialog: - false, activeReplyBox: '' + showSignInDialog: false, + activeReplyBox: '' }; - - this.handleClick = this.handleClick.bind(this); } changeTab = (tab) => { @@ -123,7 +121,7 @@ class Embed extends React.Component { } } - handleClick() { + handleClick = () => { this.props.viewAllComments(); this.props.data.refetch(); } From 48795b29bf600810a7d3cca01fe982c16c877862 Mon Sep 17 00:00:00 2001 From: gaba Date: Wed, 12 Apr 2017 10:40:15 -0700 Subject: [PATCH 06/10] Removes isAdmin for refetching when changing tabs. --- 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 453d645c2..342e490ea 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -48,10 +48,10 @@ class Embed extends React.Component { } changeTab = (tab) => { - const {isAdmin} = this.props.auth; // Everytime the comes from another tab, the Stream needs to be updated. - if (tab === 0 && isAdmin) { + if (tab === 0) { + this.props.viewAllComments(); this.props.data.refetch(); } From 92205a2c6f85c29519053cd07d398d9fec9fb5e9 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 13 Apr 2017 01:17:46 +0700 Subject: [PATCH 07/10] Fix load more buttons --- client/coral-embed-stream/src/Embed.js | 12 +++++++++--- client/coral-embed-stream/src/NewCount.js | 4 ++-- .../graphql/queries/streamQuery.graphql | 3 +++ graph/resolvers/asset.js | 7 +++++++ graph/typeDefs.graphql | 3 +++ 5 files changed, 24 insertions(+), 5 deletions(-) diff --git a/client/coral-embed-stream/src/Embed.js b/client/coral-embed-stream/src/Embed.js index d9b5c8310..a16852c00 100644 --- a/client/coral-embed-stream/src/Embed.js +++ b/client/coral-embed-stream/src/Embed.js @@ -79,10 +79,12 @@ class Embed extends Component { if(!isEqual(nextProps.data.asset, this.props.data.asset)) { loadAsset(nextProps.data.asset); - const {getCounts, updateCountCache} = this.props; + const {getCounts, updateCountCache, asset: {countCache}} = this.props; const {asset} = nextProps.data; - updateCountCache(asset.id, asset.commentCount); + if (!countCache) { + updateCountCache(asset.id, asset.commentCount); + } this.setState({ countPoll: setInterval(() => { @@ -127,6 +129,10 @@ class Embed extends Component { const banned = user && user.status === 'BANNED'; + const hasOlderComments = + asset && asset.lastComment && + asset.lastComment.id !== asset.comments[asset.comments.length - 1].id; + const expandForLogin = showSignInDialog ? { minHeight: document.body.scrollHeight + 200 } : {}; @@ -259,7 +265,7 @@ class Embed extends Component { topLevel={true} assetId={asset.id} comments={asset.comments} - moreComments={countCache[asset.id] > asset.comments.length} + moreComments={hasOlderComments} loadMore={this.props.loadMore} />
} diff --git a/client/coral-embed-stream/src/NewCount.js b/client/coral-embed-stream/src/NewCount.js index 2368c1ab8..8d7333aab 100644 --- a/client/coral-embed-stream/src/NewCount.js +++ b/client/coral-embed-stream/src/NewCount.js @@ -17,10 +17,10 @@ const onLoadMoreClick = ({loadMore, commentCount, firstCommentDate, assetId, upd const NewCount = (props) => { const newComments = props.commentCount - props.countCache; - return
+ return
{ props.countCache && newComments > 0 ? -