From 74a2ed4a4141cb322321d01371049169d3dedb6b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 7 Feb 2018 13:29:59 -0700 Subject: [PATCH 01/30] introduce no limit --- config.js | 4 + docs/_docs/02-02-advanced-configuration.md | 8 +- graph/loaders/comments.js | 87 +++++++++++----------- 3 files changed, 55 insertions(+), 44 deletions(-) diff --git a/config.js b/config.js index dd9aed13c..9b7c7adcc 100644 --- a/config.js +++ b/config.js @@ -44,6 +44,10 @@ const CONFIG = { // fetching again. SETTINGS_CACHE_TIME: ms(process.env.TALK_SETTINGS_CACHE_TIME || '1hr'), + // ALLOW_NO_LIMIT_QUERIES enables some queries to specify a limit of -1 to + // request all of the records. Otherwise, minimum limits of 0 are enforced. + ALLOW_NO_LIMIT_QUERIES: process.env.TALK_ALLOW_NO_LIMIT_QUERIES === 'TRUE', + //------------------------------------------------------------------------------ // JWT based configuration //------------------------------------------------------------------------------ diff --git a/docs/_docs/02-02-advanced-configuration.md b/docs/_docs/02-02-advanced-configuration.md index e985f8f20..fb083f63c 100644 --- a/docs/_docs/02-02-advanced-configuration.md +++ b/docs/_docs/02-02-advanced-configuration.md @@ -486,4 +486,10 @@ Used to set the key for use with [Apollo Engine](https://www.apollographql.com/engine/){:target="_blank"} for tracing of GraphQL requests. -**Note: Apollo Engine is a premium service, charges may apply.** \ No newline at end of file +**Note: Apollo Engine is a premium service, charges may apply.** + +## ALLOW_NO_LIMIT_QUERIES + +Setting this to `TRUE` will allow queries to execute without a limit (returns +all documents). This introduces a significant performance regression, and should +be used with caution. (Default `FALSE`) \ No newline at end of file diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index 7f0dbea00..37d40a219 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -4,7 +4,10 @@ const { SEARCH_NON_NULL_OR_ACCEPTED_COMMENTS, SEARCH_OTHERS_COMMENTS, } = require('../../perms/constants'); -const { CACHE_EXPIRY_COMMENT_COUNT } = require('../../config'); +const { + CACHE_EXPIRY_COMMENT_COUNT, + ALLOW_NO_LIMIT_QUERIES, +} = require('../../config'); const ms = require('ms'); const sc = require('snake-case'); @@ -148,12 +151,11 @@ const getCommentCountByQuery = (ctx, options) => { * @param {Object} params the params from the client describing the query */ const getStartCursor = (ctx, nodes, { cursor, sortBy }) => { - switch (sortBy) { - case 'CREATED_AT': - return nodes.length ? nodes[0].created_at : null; - case 'REPLIES': - // The cursor is the start! This is using numeric pagination. - return cursor != null ? cursor : 0; + if (sortBy === 'CREATED_AT') { + return nodes.length ? nodes[0].created_at : null; + } else if (sortBy === 'REPLIES') { + // The cursor is the start! This is using numeric pagination. + return cursor != null ? cursor : 0; } const SORT_KEY = sortBy.toLowerCase(); @@ -181,11 +183,10 @@ const getStartCursor = (ctx, nodes, { cursor, sortBy }) => { * @param {Object} params the params from the client describing the query */ const getEndCursor = (ctx, nodes, { cursor, sortBy }) => { - switch (sortBy) { - case 'CREATED_AT': - return nodes.length ? nodes[nodes.length - 1].created_at : null; - case 'REPLIES': - return nodes.length ? (cursor != null ? cursor : 0) + nodes.length : null; + if (sortBy === 'CREATED_AT') { + return nodes.length ? nodes[nodes.length - 1].created_at : null; + } else if (sortBy === 'REPLIES') { + return nodes.length ? (cursor != null ? cursor : 0) + nodes.length : null; } const SORT_KEY = sortBy.toLowerCase(); @@ -212,36 +213,33 @@ const getEndCursor = (ctx, nodes, { cursor, sortBy }) => { * @param {Object} params the params from the client describing the query */ const applySort = (ctx, query, { cursor, sortOrder, sortBy }) => { - switch (sortBy) { - case 'CREATED_AT': { - if (cursor) { - if (sortOrder === 'DESC') { - query = query.where({ - created_at: { - $lt: cursor, - }, - }); - } else { - query = query.where({ - created_at: { - $gt: cursor, - }, - }); - } + if (sortBy === 'CREATED_AT') { + if (cursor) { + if (sortOrder === 'DESC') { + query = query.where({ + created_at: { + $lt: cursor, + }, + }); + } else { + query = query.where({ + created_at: { + $gt: cursor, + }, + }); } - - return query.sort({ created_at: sortOrder === 'DESC' ? -1 : 1 }); } - case 'REPLIES': { - if (cursor) { - query = query.skip(cursor); - } - return query.sort({ - reply_count: sortOrder === 'DESC' ? -1 : 1, - created_at: sortOrder === 'DESC' ? -1 : 1, - }); + return query.sort({ created_at: sortOrder === 'DESC' ? -1 : 1 }); + } else if (sortBy === 'REPLIES') { + if (cursor) { + query = query.skip(cursor); } + + return query.sort({ + reply_count: sortOrder === 'DESC' ? -1 : 1, + created_at: sortOrder === 'DESC' ? -1 : 1, + }); } const SORT_KEY = sortBy.toLowerCase(); @@ -280,7 +278,7 @@ const executeWithSort = async ( query = applySort(ctx, query, { cursor, sortOrder, sortBy }); // Apply the limit (if it exists, as it's applied universally). - if (limit) { + if (limit >= 0) { query = query.limit(limit + 1); } @@ -290,7 +288,7 @@ const executeWithSort = async ( // The hasNextPage is always handled the same (ask for one more than we need, // if there is one more, than there is more). let hasNextPage = false; - if (limit && nodes.length > limit) { + if (limit >= 0 && nodes.length > limit) { // There was one more than we expected! Set hasNextPage = true and remove // the last item from the array that we requested. hasNextPage = true; @@ -302,11 +300,9 @@ const executeWithSort = async ( return { startCursor: getStartCursor(ctx, nodes, { cursor, - sortOrder, sortBy, - limit, }), - endCursor: getEndCursor(ctx, nodes, { cursor, sortOrder, sortBy, limit }), + endCursor: getEndCursor(ctx, nodes, { cursor, sortBy }), hasNextPage, nodes, }; @@ -338,6 +334,11 @@ const getCommentsByQuery = async ( ) => { let comments = CommentModel.find(); + // Enforce that the limit must be gte 0 if this option is not true. + if (!ALLOW_NO_LIMIT_QUERIES && limit < 0) { + throw new Error('cannot query for limit < 0'); + } + // If user queries for statuses other than NONE and/or ACCEPTED statuses, it needs // special privileges. if ( From 73034a20f0e7b564206b679aed0b3f60271f4928 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 13 Feb 2018 17:24:57 -0700 Subject: [PATCH 02/30] added support for configuring limiting, action optims --- Dockerfile.onbuild | 3 + .../src/constants/stream.js | 29 ++-- .../src/tabs/stream/containers/Comment.js | 7 +- .../src/tabs/stream/containers/Stream.js | 3 +- docs/_docs/02-02-advanced-configuration.md | 27 +++- graph/loaders/actions.js | 130 ++++++++++++++---- graph/resolvers/comment.js | 8 +- graph/resolvers/user.js | 4 +- services/actions.js | 109 ++------------- test/server/services/actions.js | 63 --------- webpack.config.js | 3 + 11 files changed, 180 insertions(+), 206 deletions(-) diff --git a/Dockerfile.onbuild b/Dockerfile.onbuild index dab06f336..5739d6f95 100644 --- a/Dockerfile.onbuild +++ b/Dockerfile.onbuild @@ -1,6 +1,9 @@ FROM coralproject/talk:latest # Setup the build arguments +ONBUILD ARG TALK_ADDTL_COMMENTS_ON_LOAD_MORE=10 +ONBUILD ARG TALK_ASSET_COMMENTS_LOAD_DEPTH=10 +ONBUILD ARG TALK_REPLY_COMMENTS_LOAD_DEPTH=3 ONBUILD ARG TALK_THREADING_LEVEL=3 ONBUILD ARG TALK_DEFAULT_STREAM_TAB=all ONBUILD ARG TALK_DEFAULT_LANG=en diff --git a/client/coral-embed-stream/src/constants/stream.js b/client/coral-embed-stream/src/constants/stream.js index 09d069851..64d4e64d3 100644 --- a/client/coral-embed-stream/src/constants/stream.js +++ b/client/coral-embed-stream/src/constants/stream.js @@ -1,14 +1,27 @@ +import defaultTo from 'lodash/defaultTo'; + const prefix = 'TALK_EMBED_STREAM'; -export const SET_ACTIVE_REPLY_BOX = 'SET_ACTIVE_REPLY_BOX'; -export const ADDTL_COMMENTS_ON_LOAD_MORE = 10; -export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS'; -export const VIEW_COMMENT = 'VIEW_COMMENT'; +export const ADDTL_COMMENTS_ON_LOAD_MORE = parseInt( + defaultTo(process.env.TALK_ADDTL_COMMENTS_ON_LOAD_MORE, '10') +); +export const ASSET_COMMENTS_LOAD_DEPTH = parseInt( + defaultTo(process.env.TALK_ASSET_COMMENTS_LOAD_DEPTH, '10') +); +export const REPLY_COMMENTS_LOAD_DEPTH = parseInt( + defaultTo(process.env.TALK_REPLY_COMMENTS_LOAD_DEPTH, '3') +); +export const THREADING_LEVEL = parseInt( + defaultTo(process.env.TALK_THREADING_LEVEL, '3') +); + +export const ADD_COMMENT_BOX_TAG = `${prefix}_COMMENT_BOX_ADD_TAG`; export const ADD_COMMENT_CLASSNAME = 'ADD_COMMENT_CLASSNAME'; +export const CLEAR_COMMENT_BOX_TAGS = `${prefix}_COMMENT_BOX_CLEAR_TAGS`; +export const REMOVE_COMMENT_BOX_TAG = `${prefix}_COMMENT_BOX_REMOVE_TAG`; export const REMOVE_COMMENT_CLASSNAME = 'REMOVE_COMMENT_CLASSNAME'; -export const THREADING_LEVEL = process.env.TALK_THREADING_LEVEL; +export const SET_ACTIVE_REPLY_BOX = 'SET_ACTIVE_REPLY_BOX'; export const SET_ACTIVE_TAB = 'CORAL_STREAM_SET_ACTIVE_TAB'; export const SET_SORT = 'CORAL_STREAM_SET_SORT'; -export const ADD_COMMENT_BOX_TAG = `${prefix}_COMMENT_BOX_ADD_TAG`; -export const REMOVE_COMMENT_BOX_TAG = `${prefix}_COMMENT_BOX_REMOVE_TAG`; -export const CLEAR_COMMENT_BOX_TAGS = `${prefix}_COMMENT_BOX_CLEAR_TAGS`; +export const VIEW_ALL_COMMENTS = 'VIEW_ALL_COMMENTS'; +export const VIEW_COMMENT = 'VIEW_COMMENT'; diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Comment.js b/client/coral-embed-stream/src/tabs/stream/containers/Comment.js index 9ed421273..5a6c332e0 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Comment.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Comment.js @@ -4,7 +4,10 @@ import Comment from '../components/Comment'; import { withFragments } from 'coral-framework/hocs'; import { getSlotFragmentSpreads } from 'coral-framework/utils'; import { withSetCommentStatus } from 'coral-framework/graphql/mutations'; -import { THREADING_LEVEL } from '../../../constants/stream'; +import { + THREADING_LEVEL, + REPLY_COMMENTS_LOAD_DEPTH, +} from '../../../constants/stream'; import hoistStatics from 'recompose/hoistStatics'; import { nest } from '../../../graphql/utils'; @@ -118,7 +121,7 @@ const withCommentFragments = withFragments({ ...CoralEmbedStream_Comment_SingleComment ${nest( ` - replies(query: {limit: 3, excludeIgnored: $excludeIgnored}) { + replies(query: {limit: ${REPLY_COMMENTS_LOAD_DEPTH}, excludeIgnored: $excludeIgnored}) { nodes { ...CoralEmbedStream_Comment_SingleComment ...nest diff --git a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js index 51b4d9d9e..d69966dc3 100644 --- a/client/coral-embed-stream/src/tabs/stream/containers/Stream.js +++ b/client/coral-embed-stream/src/tabs/stream/containers/Stream.js @@ -4,6 +4,7 @@ import { connect } from 'react-redux'; import { bindActionCreators } from 'redux'; import { ADDTL_COMMENTS_ON_LOAD_MORE, + ASSET_COMMENTS_LOAD_DEPTH, THREADING_LEVEL, } from '../../../constants/stream'; import { @@ -379,7 +380,7 @@ const fragments = { requireEmailConfirmation } totalCommentCount @skip(if: $hasComment) - comments(query: {limit: 10, excludeIgnored: $excludeIgnored, sortOrder: $sortOrder, sortBy: $sortBy}) @skip(if: $hasComment) { + comments(query: {limit: ${ASSET_COMMENTS_LOAD_DEPTH}, excludeIgnored: $excludeIgnored, sortOrder: $sortOrder, sortBy: $sortBy}) @skip(if: $hasComment) { nodes { ...CoralEmbedStream_Stream_comment } diff --git a/docs/_docs/02-02-advanced-configuration.md b/docs/_docs/02-02-advanced-configuration.md index fb083f63c..7d2ea81df 100644 --- a/docs/_docs/02-02-advanced-configuration.md +++ b/docs/_docs/02-02-advanced-configuration.md @@ -492,4 +492,29 @@ tracing of GraphQL requests. Setting this to `TRUE` will allow queries to execute without a limit (returns all documents). This introduces a significant performance regression, and should -be used with caution. (Default `FALSE`) \ No newline at end of file +be used with caution. (Default `FALSE`) + + +## TALK_ADDTL_COMMENTS_ON_LOAD_MORE + +This is a **Build Variable** and must be consumed during build. If using the +[Docker-onbuild]({{ "/installation-from-docker/#onbuild" | relative_url }}) +image you can specify it with `--build-arg TALK_ADDTL_COMMENTS_ON_LOAD_MORE=10`. + +Specifies the number of additional comments to load when a user clicks `Load More`. (Default `10`) + +## TALK_ASSET_COMMENTS_LOAD_DEPTH + +This is a **Build Variable** and must be consumed during build. If using the +[Docker-onbuild]({{ "/installation-from-docker/#onbuild" | relative_url }}) +image you can specify it with `--build-arg TALK_ASSET_COMMENTS_LOAD_DEPTH=10`. + +Specifies the initial number of comments to load for an asset. (Default `10`) + +## TALK_REPLY_COMMENTS_LOAD_DEPTH + +This is a **Build Variable** and must be consumed during build. If using the +[Docker-onbuild]({{ "/installation-from-docker/#onbuild" | relative_url }}) +image you can specify it with `--build-arg TALK_REPLY_COMMENTS_LOAD_DEPTH=3`. + +Specifies the initial replies to load for a comment. (Default `3`) \ No newline at end of file diff --git a/graph/loaders/actions.js b/graph/loaders/actions.js index 96e45b0e3..2a501b6c6 100644 --- a/graph/loaders/actions.js +++ b/graph/loaders/actions.js @@ -1,9 +1,7 @@ const DataLoader = require('dataloader'); - const util = require('./util'); - const ActionsService = require('../../services/actions'); -const ActionModel = require('../../models/action'); +const { first, get, remove, groupBy, reduce, defaultTo } = require('lodash'); /** * Gets actions based on their item id's. @@ -15,40 +13,122 @@ const genActionsByItemID = (_, item_ids) => { }; /** - * 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 + * Looks up the actions for each of the items. + * + * @param {Object} ctx the graph context of the request + * @param {Array} itemIDs the items that we need to get the actions for */ -const genActionSummariessByItemID = ({ user = {} }, item_ids) => { - return ActionsService.getActionSummaries(item_ids, user.id).then( - util.arrayJoinBy(item_ids, 'item_id') +const genActionsAuthoredWithID = ({ user = {} }, itemIDs) => + ActionsService.getUserActions(user.id, itemIDs).then( + util.arrayJoinBy(itemIDs, 'item_id') ); -}; /** - * Search for actions based on their action_type and item_type and ensures that - * the actions returned have unique item id's. - * @param {String} action_type the action to search by - * @param {String} item_type the item id to search by - * @return {Promise} resolves to distinct items actions + * Looks up the action summaries for a set of items. + * + * @param {Object} ctx the graph context of the request + * @param {Array} items the items that should have their items looked up for */ -const getItemIdsByActionTypeAndItemType = (_, action_type, item_type) => { - return ActionModel.distinct('item_id', { action_type, item_type }); +const genActionSummariesByItem = async (ctx, items) => { + const { loaders: { Actions } } = ctx; + + // This is designed to match the action_counts value that is embedded on + // documents which cache action counts. For users that are not logged in, we + // don't need to hit the actions collection at all! + + // This will match any action count that is specific for a group id. + const nonGroupIDTest = /^([A-Z]+)_([A-Z_]+)$/; + + // We will literate over all the items that we're comparing. + return items.map(async ({ id, action_counts = {} }) => { + // Cache all those entries for which we got the group id of, because we + // don't want to include them twice. + const groupIDCache = {}; + + // Possibly get the list of user actions completed by the user. This will be + // used later to join together with the action summaries to provide context. + const userActions = + ctx.user && reduce(action_counts, (total, count) => total + count, 0) > 0 + ? await Actions.getAuthoredByID.load(id) + : []; + + // Group the user actions in the same way that the action counts are + // grouped. This will let us extract it easy. + const groupedUserActions = groupBy( + userActions, + ({ action_type, group_id }) => + (group_id ? `${action_type}_${group_id}` : action_type).toUpperCase() + ); + + // Generate the action summaries for the item. + return Object.keys(action_counts) + .map(action_type => ({ + count: action_counts[action_type], + action_type: action_type.toUpperCase(), + })) + .reduce((actionTypeList, { count, action_type }) => { + // Get the current user's actions (if they have any). + const current_user = defaultTo( + first(get(groupedUserActions, action_type, [])), + null + ); + + // Check to see if this is a action without a corresponding group id. + if (nonGroupIDTest.test(action_type)) { + // This action type does have a group id associated with it. + const results = nonGroupIDTest.exec(action_type); + const groupActionType = results[1]; + const groupID = results[2]; + + // Purge out the summary if it already exists, and mark that this + // group id has been found so we don't include it in the future. + remove( + actionTypeList, + ({ action_type }) => action_type === groupActionType + ); + groupIDCache[groupActionType] = true; + + // Push the new entry in. + actionTypeList.push({ + action_type: groupActionType, + group_id: groupID, + count, + current_user, + }); + } else { + // This does not have a group id. Check to see if this group id + // already has an specific (group id) entry. + if (groupIDCache[action_type]) { + // It does. Don't add anything. + return actionTypeList; + } + + // It does not, add the entry. + actionTypeList.push({ + action_type, + group_id: null, + count, + current_user, + }); + } + + return actionTypeList; + }, []); + }); }; /** * Creates a set of loaders based on a GraphQL context. - * @param {Object} context the context of the GraphQL request + * @param {Object} ctx the context of the GraphQL request * @return {Object} object of loaders */ -module.exports = context => ({ +module.exports = ctx => ({ Actions: { - getByID: new DataLoader(ids => genActionsByItemID(context, ids)), - getSummariesByItemID: new DataLoader(ids => - genActionSummariessByItemID(context, ids) + getByID: new DataLoader(ids => genActionsByItemID(ctx, ids)), + getSummariesByItem: new DataLoader( + items => genActionSummariesByItem(ctx, items), + { cacheKeyFn: ({ id }) => id } ), - getByTypes: ({ action_type, item_type }) => - getItemIdsByActionTypeAndItemType(context, action_type, item_type), + getAuthoredByID: new DataLoader(ids => genActionsAuthoredWithID(ctx, ids)), }, }); diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index 9949a5e2e..37594ce49 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -40,12 +40,12 @@ const Comment = { return Actions.getByID.load(id); }, - action_summaries({ id, action_summaries }, _, { loaders: { Actions } }) { - if (action_summaries) { - return action_summaries; + action_summaries(comment, _, { loaders: { Actions } }) { + if (comment.action_summaries) { + return comment.action_summaries; } - return Actions.getSummariesByItemID.load(id); + return Actions.getSummariesByItem.load(comment); }, asset({ asset_id }, _, { loaders: { Assets } }) { return Assets.getByID.load(asset_id); diff --git a/graph/resolvers/user.js b/graph/resolvers/user.js index 11db4c366..c72a5923c 100644 --- a/graph/resolvers/user.js +++ b/graph/resolvers/user.js @@ -10,8 +10,8 @@ const { } = require('../../perms/constants'); const User = { - action_summaries({ id }, _, { loaders: { Actions } }) { - return Actions.getSummariesByItemID.load(id); + action_summaries(user, _, { loaders: { Actions } }) { + return Actions.getSummariesByItem.load(user); }, actions({ id }, _, { user, loaders: { Actions } }) { // Only return the actions if the user is not an admin. diff --git a/services/actions.js b/services/actions.js index a1cef9ff6..055a812f8 100644 --- a/services/actions.js +++ b/services/actions.js @@ -104,110 +104,19 @@ module.exports = class ActionsService { } /** - * Fetches the action summaries for the given asset, and comments around the - * given user id. + * Get the actions for a specific user on the specific items. * - * @param {[type]} asset_id [description] - * @param {[type]} comments [description] - * @param {String} [current_user_id=''] [description] - * @return {[type]} [description] + * @param {String} userID the id of the user to find their actions for + * @param {Array} itemIDs the ids of the items to find their actions + * for */ - static getActionSummariesFromComments( - asset_id = '', - comments, - current_user_id = '' - ) { - // Get the user id's from the author id's as a unique array that gets - // sorted. - let userIDs = _.uniq(comments.map(comment => comment.author_id)).sort(); - - // Fetch the actions for pretty much everything at this point. - return ActionsService.getActionSummaries( - _.uniq( - [ - // Actions can be on assets... - asset_id, - - // Comments... - ...comments.map(comment => comment.id), - - // Or Authors... - ...userIDs, - ].filter(e => e) - ), - current_user_id - ); - } - - /** - * Returns summaries of actions for an array of ids. - * - * @param {String} ids array of user identifiers (uuid) - */ - static getActionSummaries(item_ids, current_user_id = '') { - // only grab items that match the specified item id's - let $match = { + static getUserActions(userID, itemIDs) { + return ActionModel.find({ + user_id: userID, item_id: { - $in: item_ids, + $in: itemIDs, }, - }; - - let $group = { - // group unique documents by these properties, we are leveraging the - // fact that each uuid is completely unique. - _id: { - item_id: '$item_id', - action_type: '$action_type', - group_id: '$group_id', - }, - - // and sum up all actions matching the above grouping criteria - count: { - $sum: 1, - }, - - // we are leveraging the fact that each uuid is completely unique and - // just grabbing the last instance of the item type here. - item_type: { - $first: '$item_type', - }, - - current_user: { - $max: { - $cond: { - if: { - $eq: ['$user_id', current_user_id], - }, - then: '$$CURRENT', - else: null, - }, - }, - }, - }; - - let $project = { - // suppress the _id field - _id: false, - - // map the fields from the _id grouping down a level - item_id: '$_id.item_id', - action_type: '$_id.action_type', - group_id: '$_id.group_id', - - // map the field directly - count: '$count', - item_type: '$item_type', - - // set the current user to false here - current_user: '$current_user', - }; - - return ActionModel.aggregate([ - { $match }, - { $group }, - { $project }, - { $sort: { action_type: 1, group_id: 1 } }, - ]); + }); } /** diff --git a/test/server/services/actions.js b/test/server/services/actions.js index d1cab11bd..96170338e 100644 --- a/test/server/services/actions.js +++ b/test/server/services/actions.js @@ -151,67 +151,4 @@ describe('services.ActionsService', () => { ); }); }); - - describe('#getActionSummaries()', () => { - it('should return properly formatted summaries from an array of item_ids', () => { - return ActionsService.getActionSummaries([comment.id, '789']).then( - summaries => { - expect(summaries).to.have.length(2); - - expect(summaries).to.deep.include({ - action_type: 'LIKE', - count: 1, - item_id: comment.id, - item_type: 'COMMENTS', - current_user: null, - }); - - expect(summaries).to.deep.include({ - action_type: 'FLAG', - count: 2, - item_id: comment.id, - item_type: 'COMMENTS', - current_user: null, - }); - } - ); - }); - - it('should include a current user when one is passed', () => { - return ActionsService.getActionSummaries( - [comment.id], - 'flagginguserid' - ).then(summaries => { - expect(summaries).to.have.length(2); - - let summary = summaries.find( - s => s.item_id === comment.id && s.action_type === 'FLAG' - ); - - expect(summary).to.not.be.undefined; - expect(summary.current_user).to.not.be.null; - expect(summary.current_user).to.have.property('item_id', comment.id); - expect(summary.current_user).to.have.property('item_type', 'COMMENTS'); - expect(summary.current_user).to.have.property( - 'user_id', - 'flagginguserid' - ); - expect(summary.current_user).to.have.property('action_type', 'FLAG'); - }); - }); - - it("should not include a current user when one is passed for a user that doesn't have an action", () => { - return ActionsService.getActionSummaries( - [comment.id], - 'flagginguserid2' - ).then(summaries => { - expect(summaries).to.have.length(2); - - summaries.forEach(summary => { - expect(summary).to.not.be.undefined; - expect(summary).to.have.property('current_user', null); - }); - }); - }); - }); }); diff --git a/webpack.config.js b/webpack.config.js index 6a018caba..124766686 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -128,6 +128,9 @@ const config = { new webpack.EnvironmentPlugin({ TALK_PLUGINS_JSON: '{}', TALK_THREADING_LEVEL: '3', + TALK_ADDTL_COMMENTS_ON_LOAD_MORE: '10', + TALK_ASSET_COMMENTS_LOAD_DEPTH: '10', + TALK_REPLY_COMMENTS_LOAD_DEPTH: '3', TALK_DEFAULT_STREAM_TAB: 'all', TALK_DEFAULT_LANG: 'en', }), From 1e329c8967ffb0ecfb9dca00e20c1f68adc08d94 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 13 Feb 2018 17:45:26 -0700 Subject: [PATCH 03/30] code cleanup --- graph/loaders/actions.js | 205 +++++++++++++++++++++++---------------- 1 file changed, 124 insertions(+), 81 deletions(-) diff --git a/graph/loaders/actions.js b/graph/loaders/actions.js index 2a501b6c6..6e8c42355 100644 --- a/graph/loaders/actions.js +++ b/graph/loaders/actions.js @@ -1,7 +1,7 @@ const DataLoader = require('dataloader'); const util = require('./util'); const ActionsService = require('../../services/actions'); -const { first, get, remove, groupBy, reduce, defaultTo } = require('lodash'); +const { first, get, merge, remove, groupBy, reduce } = require('lodash'); /** * Gets actions based on their item id's. @@ -23,6 +23,128 @@ const genActionsAuthoredWithID = ({ user = {} }, itemIDs) => util.arrayJoinBy(itemIDs, 'item_id') ); +/** + * iterateActionCounts will create an iterable object that can be used to + * compute action summaries. + * + * @param {Object} action_counts the action count object + */ +const iterateActionCounts = action_counts => + Object.keys(action_counts).map(action_type => ({ + count: action_counts[action_type], + action_type: action_type.toUpperCase(), + })); + +/** + * getUserActions will get the actions made by the user for this specific + * item. + * + * @param {Object} ctx the graph context of the request + * @param {Object} item the item that we're getting the actions for + */ +async function getUserActions(ctx, { action_counts, id }) { + const { loaders: { Actions } } = ctx; + + // Get the total count for all action types. + const totalActionCount = reduce( + action_counts, + (total, count) => total + count, + 0 + ); + + // Check to see if there are any user actions to get. + const hasUserActions = ctx.user && totalActionCount > 0; + if (!hasUserActions) { + return {}; + } + + // Possibly get the list of user actions completed by the user. This will be + // used later to join together with the action summaries to provide context. + const userActions = await Actions.getAuthoredByID.load(id); + if (userActions.length === 0) { + return {}; + } + + // Group the user actions in the same way that the action counts are + // grouped. This will let us extract it easy. + return reduce( + groupBy(userActions, ({ action_type, group_id }) => + (group_id ? `${action_type}_${group_id}` : action_type).toUpperCase() + ), + (allUserActions, userActions, actionType) => + merge(allUserActions, { [actionType]: first(userActions) }), + {} + ); +} + +// This will match any action count that is specific for a group id. +const nonGroupIDTest = /^([A-Z]+)_([A-Z_]+)$/; + +/** + * resolveActionSummariesForItem will resolve the action summaries for an item. + * + * @param {Object} ctx the graph context of the request + * @param {Object} item the item that we are resolving an action summary for + */ +async function resolveActionSummariesForItem(ctx, { id, action_counts }) { + // Cache all those entries for which we got the group id of, because we + // don't want to include them twice. + const groupIDCache = {}; + + // Get the user actions for this specific item. + const groupedUserActions = await getUserActions(ctx, { id, action_counts }); + + // Generate the action summaries for the item. + return iterateActionCounts(action_counts).reduce( + (actionTypeList, { count, action_type }) => { + // Get the current user's actions (if they have any). + const current_user = get(groupedUserActions, action_type, null); + + // Check to see if this is a action without a corresponding group id. + if (nonGroupIDTest.test(action_type)) { + // This action type does have a group id associated with it. + const results = nonGroupIDTest.exec(action_type); + const groupActionType = results[1]; + const groupID = results[2]; + + // Purge out the summary if it already exists, and mark that this + // group id has been found so we don't include it in the future. + remove( + actionTypeList, + ({ action_type }) => action_type === groupActionType + ); + groupIDCache[groupActionType] = true; + + // Push the new entry in. + actionTypeList.push({ + action_type: groupActionType, + group_id: groupID, + count, + current_user, + }); + } else { + // This does not have a group id. Check to see if this group id + // already has an specific (group id) entry. + if (groupIDCache[action_type]) { + // It does. Don't add anything. + return actionTypeList; + } + + // It does not, add the entry. + actionTypeList.push({ + action_type, + group_id: null, + count, + current_user, + }); + } + + return actionTypeList; + }, + [] + ); +} + /** * Looks up the action summaries for a set of items. * @@ -30,91 +152,12 @@ const genActionsAuthoredWithID = ({ user = {} }, itemIDs) => * @param {Array} items the items that should have their items looked up for */ const genActionSummariesByItem = async (ctx, items) => { - const { loaders: { Actions } } = ctx; - // This is designed to match the action_counts value that is embedded on // documents which cache action counts. For users that are not logged in, we // don't need to hit the actions collection at all! - // This will match any action count that is specific for a group id. - const nonGroupIDTest = /^([A-Z]+)_([A-Z_]+)$/; - // We will literate over all the items that we're comparing. - return items.map(async ({ id, action_counts = {} }) => { - // Cache all those entries for which we got the group id of, because we - // don't want to include them twice. - const groupIDCache = {}; - - // Possibly get the list of user actions completed by the user. This will be - // used later to join together with the action summaries to provide context. - const userActions = - ctx.user && reduce(action_counts, (total, count) => total + count, 0) > 0 - ? await Actions.getAuthoredByID.load(id) - : []; - - // Group the user actions in the same way that the action counts are - // grouped. This will let us extract it easy. - const groupedUserActions = groupBy( - userActions, - ({ action_type, group_id }) => - (group_id ? `${action_type}_${group_id}` : action_type).toUpperCase() - ); - - // Generate the action summaries for the item. - return Object.keys(action_counts) - .map(action_type => ({ - count: action_counts[action_type], - action_type: action_type.toUpperCase(), - })) - .reduce((actionTypeList, { count, action_type }) => { - // Get the current user's actions (if they have any). - const current_user = defaultTo( - first(get(groupedUserActions, action_type, [])), - null - ); - - // Check to see if this is a action without a corresponding group id. - if (nonGroupIDTest.test(action_type)) { - // This action type does have a group id associated with it. - const results = nonGroupIDTest.exec(action_type); - const groupActionType = results[1]; - const groupID = results[2]; - - // Purge out the summary if it already exists, and mark that this - // group id has been found so we don't include it in the future. - remove( - actionTypeList, - ({ action_type }) => action_type === groupActionType - ); - groupIDCache[groupActionType] = true; - - // Push the new entry in. - actionTypeList.push({ - action_type: groupActionType, - group_id: groupID, - count, - current_user, - }); - } else { - // This does not have a group id. Check to see if this group id - // already has an specific (group id) entry. - if (groupIDCache[action_type]) { - // It does. Don't add anything. - return actionTypeList; - } - - // It does not, add the entry. - actionTypeList.push({ - action_type, - group_id: null, - count, - current_user, - }); - } - - return actionTypeList; - }, []); - }); + return items.map(resolveActionSummariesForItem); }; /** From 9820e575b271299da4a918b253baebdcc6959d9b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 13 Feb 2018 18:29:07 -0700 Subject: [PATCH 04/30] patch --- graph/loaders/actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph/loaders/actions.js b/graph/loaders/actions.js index 6e8c42355..99aaa6015 100644 --- a/graph/loaders/actions.js +++ b/graph/loaders/actions.js @@ -157,7 +157,7 @@ const genActionSummariesByItem = async (ctx, items) => { // don't need to hit the actions collection at all! // We will literate over all the items that we're comparing. - return items.map(resolveActionSummariesForItem); + return items.map(item => resolveActionSummariesForItem(ctx, item)); }; /** From 18710e2345c5cfbb9cfe7c0b2d981a265d8dd6f5 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Wed, 14 Feb 2018 15:56:04 -0700 Subject: [PATCH 05/30] added tests --- graph/loaders/actions.js | 15 ++-- test/server/graph/loaders/actions.js | 122 +++++++++++++++++++++++++++ 2 files changed, 132 insertions(+), 5 deletions(-) create mode 100644 test/server/graph/loaders/actions.js diff --git a/graph/loaders/actions.js b/graph/loaders/actions.js index 99aaa6015..b5ea31a3f 100644 --- a/graph/loaders/actions.js +++ b/graph/loaders/actions.js @@ -1,13 +1,15 @@ const DataLoader = require('dataloader'); const util = require('./util'); -const ActionsService = require('../../services/actions'); const { first, get, merge, remove, groupBy, reduce } = require('lodash'); /** * Gets actions based on their item id's. */ -const genActionsByItemID = (_, item_ids) => { - return ActionsService.findByItemIdArray(item_ids).then( +const genActionsByItemID = ( + { connectors: { services: { Actions } } }, + item_ids +) => { + return Actions.findByItemIdArray(item_ids).then( util.arrayJoinBy(item_ids, 'item_id') ); }; @@ -18,8 +20,11 @@ const genActionsByItemID = (_, item_ids) => { * @param {Object} ctx the graph context of the request * @param {Array} itemIDs the items that we need to get the actions for */ -const genActionsAuthoredWithID = ({ user = {} }, itemIDs) => - ActionsService.getUserActions(user.id, itemIDs).then( +const genActionsAuthoredWithID = ( + { user = {}, connectors: { services: { Actions } } }, + itemIDs +) => + Actions.getUserActions(user.id, itemIDs).then( util.arrayJoinBy(itemIDs, 'item_id') ); diff --git a/test/server/graph/loaders/actions.js b/test/server/graph/loaders/actions.js new file mode 100644 index 000000000..f8e96175b --- /dev/null +++ b/test/server/graph/loaders/actions.js @@ -0,0 +1,122 @@ +const chai = require('chai'); +chai.use(require('chai-as-promised')); +const { expect } = chai; +const sinon = require('sinon'); +const { find } = require('lodash'); +const loaders = require('../../../../graph/loaders/actions'); + +describe('graph.loaders.Actions', () => { + describe('#getAuthoredByID', () => { + it('loads the correct entries', async () => { + const spy = sinon.spy(async () => [ + { item_id: 'comment_1' }, + { item_id: 'comment_2' }, + ]); + const { Actions: { getAuthoredByID } } = loaders({ + user: { id: 'user_1' }, + connectors: { services: { Actions: { getUserActions: spy } } }, + }); + + const actions = await getAuthoredByID.loadMany([ + 'comment_2', + 'comment_1', + ]); + + expect(spy.calledWith('user_1', ['comment_2', 'comment_1'])); + expect(actions).to.have.length(2); + expect(actions[0]).to.have.length(1); + expect(actions[0][0]).to.have.property('item_id', 'comment_2'); + expect(actions[1]).to.have.length(1); + expect(actions[1][0]).to.have.property('item_id', 'comment_1'); + }); + }); + + describe('#getSummariesByItem', () => { + describe('logged out user', () => { + it('does not include any user data', async () => { + const { Actions: { getSummariesByItem } } = loaders({ + loaders: { + Actions: { + getAuthoredByID: { + load: () => Promise.reject(new Error('should not be called')), + }, + }, + }, + user: null, + }); + + const summaries = await getSummariesByItem.load({ + id: '1', + action_counts: { flag: 1, flag_comment_offensive: 1, respect: 2 }, + }); + + expect(summaries).to.have.length(2); + + const flag = find(summaries, { action_type: 'FLAG' }); + expect(flag).to.be.defined; + + expect(flag).to.have.property('current_user', null); + expect(flag).to.have.property('action_type', 'FLAG'); + expect(flag).to.have.property('group_id', 'COMMENT_OFFENSIVE'); + expect(flag).to.have.property('count', 1); + + const respect = find(summaries, { action_type: 'RESPECT' }); + expect(respect).to.be.defined; + + expect(respect).to.have.property('current_user', null); + expect(respect).to.have.property('action_type', 'RESPECT'); + expect(respect).to.have.property('group_id', null); + expect(respect).to.have.property('count', 2); + }); + }); + + describe('logged in user', () => { + it('does include user', async () => { + const { Actions: { getSummariesByItem } } = loaders({ + loaders: { + Actions: { + getAuthoredByID: { + load: commentID => { + expect(commentID).to.equal('comment_1'); + return [ + { + id: 'action_1', + action_type: 'FLAG', + group_id: 'COMMENT_OFFENSIVE', + }, + ]; + }, + }, + }, + }, + user: { id: 'user_1' }, + }); + + const summaries = await getSummariesByItem.load({ + id: 'comment_1', + action_counts: { flag: 1, flag_comment_offensive: 1, respect: 2 }, + }); + + expect(summaries).to.have.length(2); + + const flag = find(summaries, { action_type: 'FLAG' }); + expect(flag).to.be.defined; + + expect(flag).to.have.property('action_type', 'FLAG'); + expect(flag).to.have.property('group_id', 'COMMENT_OFFENSIVE'); + expect(flag).to.have.property('count', 1); + + expect(flag).to.have.property('current_user').not.null; + expect(flag.current_user).to.have.property('id', 'action_1'); + + const respect = find(summaries, { action_type: 'RESPECT' }); + expect(respect).to.be.defined; + + expect(respect).to.have.property('current_user', null); + expect(respect).to.have.property('action_type', 'RESPECT'); + expect(respect).to.have.property('group_id', null); + expect(respect).to.have.property('count', 2); + }); + }); + }); +}); From c3bccaf4a8a768187b203139df409fd2cd10fda9 Mon Sep 17 00:00:00 2001 From: snyk-bot Date: Thu, 15 Feb 2018 01:19:30 +0000 Subject: [PATCH 06/30] fix: package.json to reduce vulnerabilities The following vulnerabilities are fixed with an upgrade: - https://snyk.io/vuln/npm:hoek:20180212 - https://snyk.io/vuln/npm:hoek:20180212 Latest report for coralproject/talk: https://snyk.io/test/github/coralproject/talk --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index c5f7667a4..435815220 100644 --- a/package.json +++ b/package.json @@ -117,9 +117,9 @@ "inquirer": "^3.2.2", "inquirer-autocomplete-prompt": "^0.12.1", "ioredis": "3.1.4", - "joi": "^10.6.0", + "joi": "^13.0.0", "json-loader": "^0.5.7", - "jsonwebtoken": "^7.4.3", + "jsonwebtoken": "^8.0.0", "jwt-decode": "^2.2.0", "keymaster": "^1.6.2", "kue": "0.11.6", From 0519b55707e74e17e0a1897aa30f335bd0f87139 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Thu, 15 Feb 2018 15:55:51 -0500 Subject: [PATCH 07/30] Translation typo is breaking Akismet --- plugins/talk-plugin-akismet/client/translations.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/talk-plugin-akismet/client/translations.yml b/plugins/talk-plugin-akismet/client/translations.yml index a21f4b3c5..6cc19a492 100644 --- a/plugins/talk-plugin-akismet/client/translations.yml +++ b/plugins/talk-plugin-akismet/client/translations.yml @@ -68,7 +68,7 @@ nl_NL: spam_comment: "Spam" detected: "Gedetecteerd door Akismet" still_spam: | - Dank je wel. Ons moderatieteam zal je reactie beoordelen. + Dank je wel. Ons moderatieteam zal je reactie beoordelen. flags: reasons: comment: From ba11eb233a5b473a311350b9ce8290b4c18b5271 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Fri, 16 Feb 2018 12:31:37 +0100 Subject: [PATCH 08/30] Fix proptypes errors in auth --- client/coral-admin/src/components/ForgotPassword.js | 2 +- client/coral-admin/src/components/SignIn.js | 2 +- client/coral-admin/src/containers/ForgotPassword.js | 2 +- client/coral-admin/src/containers/SignIn.js | 2 +- .../client/login/components/ForgotPassword.js | 2 +- .../client/login/components/ResendEmailConfirmation.js | 2 +- .../talk-plugin-auth/client/login/components/SignIn.js | 2 +- .../talk-plugin-auth/client/login/components/SignUp.js | 10 +++++----- .../client/login/containers/ForgotPassword.js | 2 +- .../talk-plugin-auth/client/login/containers/SignIn.js | 2 +- .../talk-plugin-auth/client/login/containers/SignUp.js | 2 +- .../client/stream/components/SetUsernameDialog.js | 4 ++-- .../client/stream/containers/SetUsernameDialog.js | 2 +- 13 files changed, 18 insertions(+), 18 deletions(-) diff --git a/client/coral-admin/src/components/ForgotPassword.js b/client/coral-admin/src/components/ForgotPassword.js index ad90c5d6a..fe08436ae 100644 --- a/client/coral-admin/src/components/ForgotPassword.js +++ b/client/coral-admin/src/components/ForgotPassword.js @@ -75,7 +75,7 @@ ForgotPassword.propTypes = { email: PropTypes.string.isRequired, onEmailChange: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, onSignInLink: PropTypes.func.isRequired, }; diff --git a/client/coral-admin/src/components/SignIn.js b/client/coral-admin/src/components/SignIn.js index 1525145a7..5e1de2033 100644 --- a/client/coral-admin/src/components/SignIn.js +++ b/client/coral-admin/src/components/SignIn.js @@ -87,7 +87,7 @@ SignIn.propTypes = { onForgotPasswordLink: PropTypes.func.isRequired, onRecaptchaVerify: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, requireRecaptcha: PropTypes.bool.isRequired, }; diff --git a/client/coral-admin/src/containers/ForgotPassword.js b/client/coral-admin/src/containers/ForgotPassword.js index 558747ab9..62740e87c 100644 --- a/client/coral-admin/src/containers/ForgotPassword.js +++ b/client/coral-admin/src/containers/ForgotPassword.js @@ -34,7 +34,7 @@ class ForgotPasswordContainer extends Component { ForgotPasswordContainer.propTypes = { success: PropTypes.bool.isRequired, forgotPassword: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, onSignInLink: PropTypes.func.isRequired, }; diff --git a/client/coral-admin/src/containers/SignIn.js b/client/coral-admin/src/containers/SignIn.js index a48b15ce7..523d81091 100644 --- a/client/coral-admin/src/containers/SignIn.js +++ b/client/coral-admin/src/containers/SignIn.js @@ -50,7 +50,7 @@ class SignInContainer extends Component { SignInContainer.propTypes = { signIn: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, onForgotPasswordLink: PropTypes.func.isRequired, requireRecaptcha: PropTypes.bool.isRequired, }; diff --git a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js index 8fbd48c64..02d62a85d 100644 --- a/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js +++ b/plugins/talk-plugin-auth/client/login/components/ForgotPassword.js @@ -73,7 +73,7 @@ ForgotPassword.propTypes = { email: PropTypes.string.isRequired, onEmailChange: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, onSignInLink: PropTypes.func.isRequired, onSignUpLink: PropTypes.func.isRequired, }; diff --git a/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js index 6b316838d..a3a746034 100644 --- a/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js +++ b/plugins/talk-plugin-auth/client/login/components/ResendEmailConfirmation.js @@ -52,7 +52,7 @@ ResendVerification.propTypes = { loading: PropTypes.bool.isRequired, email: PropTypes.string.isRequired, onSubmit: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, }; export default ResendVerification; diff --git a/plugins/talk-plugin-auth/client/login/components/SignIn.js b/plugins/talk-plugin-auth/client/login/components/SignIn.js index 333ca0b0b..f082f7f4d 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/components/SignIn.js @@ -129,7 +129,7 @@ SignIn.propTypes = { onSignUpLink: PropTypes.func.isRequired, onRecaptchaVerify: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, requireRecaptcha: PropTypes.bool.isRequired, }; diff --git a/plugins/talk-plugin-auth/client/login/components/SignUp.js b/plugins/talk-plugin-auth/client/login/components/SignUp.js index 601a7dba0..61c4d0b1d 100644 --- a/plugins/talk-plugin-auth/client/login/components/SignUp.js +++ b/plugins/talk-plugin-auth/client/login/components/SignUp.js @@ -145,20 +145,20 @@ class SignUp extends React.Component { SignUp.propTypes = { loading: PropTypes.bool.isRequired, username: PropTypes.string.isRequired, - usernameError: PropTypes.string.isRequired, + usernameError: PropTypes.string, email: PropTypes.string.isRequired, - emailError: PropTypes.string.isRequired, + emailError: PropTypes.string, password: PropTypes.string.isRequired, - passwordError: PropTypes.string.isRequired, + passwordError: PropTypes.string, passwordRepeat: PropTypes.string.isRequired, - passwordRepeatError: PropTypes.string.isRequired, + passwordRepeatError: PropTypes.string, onUsernameChange: PropTypes.func.isRequired, onEmailChange: PropTypes.func.isRequired, onPasswordChange: PropTypes.func.isRequired, onPasswordRepeatChange: PropTypes.func.isRequired, onSignInLink: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, requireEmailConfirmation: PropTypes.bool.isRequired, success: PropTypes.bool.isRequired, }; diff --git a/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js b/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js index 9b1f6cb48..c5e2f42da 100644 --- a/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js +++ b/plugins/talk-plugin-auth/client/login/containers/ForgotPassword.js @@ -38,7 +38,7 @@ class ForgotPasswordContainer extends Component { ForgotPasswordContainer.propTypes = { success: PropTypes.bool.isRequired, forgotPassword: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, setView: PropTypes.func.isRequired, email: PropTypes.string.isRequired, setEmail: PropTypes.func.isRequired, diff --git a/plugins/talk-plugin-auth/client/login/containers/SignIn.js b/plugins/talk-plugin-auth/client/login/containers/SignIn.js index ed797aed3..77c6a8614 100644 --- a/plugins/talk-plugin-auth/client/login/containers/SignIn.js +++ b/plugins/talk-plugin-auth/client/login/containers/SignIn.js @@ -61,7 +61,7 @@ class SignInContainer extends Component { SignInContainer.propTypes = { signIn: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, requireRecaptcha: PropTypes.bool.isRequired, requireEmailConfirmation: PropTypes.bool.isRequired, loading: PropTypes.bool.isRequired, diff --git a/plugins/talk-plugin-auth/client/login/containers/SignUp.js b/plugins/talk-plugin-auth/client/login/containers/SignUp.js index cadca6f86..609102ebc 100644 --- a/plugins/talk-plugin-auth/client/login/containers/SignUp.js +++ b/plugins/talk-plugin-auth/client/login/containers/SignUp.js @@ -109,7 +109,7 @@ SignUpContainer.propTypes = { setPassword: PropTypes.func.isRequired, signUp: PropTypes.func.isRequired, loading: PropTypes.bool.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, requireEmailConfirmation: PropTypes.bool.isRequired, success: PropTypes.bool.isRequired, validate: PropTypes.func.isRequired, diff --git a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js index d9517a06f..0fd742879 100644 --- a/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/stream/components/SetUsernameDialog.js @@ -75,10 +75,10 @@ class SetUsernameDialog extends React.Component { SetUsernameDialog.propTypes = { loading: PropTypes.bool.isRequired, username: PropTypes.string.isRequired, - usernameError: PropTypes.string.isRequired, + usernameError: PropTypes.string, onUsernameChange: PropTypes.func.isRequired, onSubmit: PropTypes.func.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, }; export default SetUsernameDialog; diff --git a/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js b/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js index ae89b9fac..93c7ea74b 100644 --- a/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js +++ b/plugins/talk-plugin-auth/client/stream/containers/SetUsernameDialog.js @@ -47,7 +47,7 @@ SetUsernameDialogContainer.propTypes = { username: PropTypes.string, setUsername: PropTypes.func.isRequired, loading: PropTypes.bool.isRequired, - errorMessage: PropTypes.string.isRequired, + errorMessage: PropTypes.string, success: PropTypes.bool.isRequired, validateUsername: PropTypes.func.isRequired, }; From 4bb2b3d731dc8bf4c41d9cfa850f0fc473525a77 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 16 Feb 2018 14:34:04 -0700 Subject: [PATCH 09/30] fixes to spam plugin --- .../src/routes/Moderation/components/Comment.css | 1 - client/coral-ui/components/Label.css | 1 + .../client/components/SpamDetail.js | 2 +- .../client/components/SpamLabel.js | 2 +- .../client/containers/SpamCommentDetail.js | 2 +- .../client/containers/SpamCommentFlagDetail.js | 12 ------------ plugins/talk-plugin-akismet/client/index.js | 2 -- .../client/components/ToxicDetail.js | 2 +- .../client/components/ToxicLabel.js | 2 +- 9 files changed, 6 insertions(+), 20 deletions(-) delete mode 100644 plugins/talk-plugin-akismet/client/containers/SpamCommentFlagDetail.js diff --git a/client/coral-admin/src/routes/Moderation/components/Comment.css b/client/coral-admin/src/routes/Moderation/components/Comment.css index 119688b73..b0d989c54 100644 --- a/client/coral-admin/src/routes/Moderation/components/Comment.css +++ b/client/coral-admin/src/routes/Moderation/components/Comment.css @@ -143,7 +143,6 @@ i { font-size: 12px; - top: 2px; position: relative; } } diff --git a/client/coral-ui/components/Label.css b/client/coral-ui/components/Label.css index 5e7f656ee..18723fa2f 100644 --- a/client/coral-ui/components/Label.css +++ b/client/coral-ui/components/Label.css @@ -10,6 +10,7 @@ line-height: 22px; min-width: 80px; text-align: center; + vertical-align: text-top; } .icon { diff --git a/plugins/talk-plugin-akismet/client/components/SpamDetail.js b/plugins/talk-plugin-akismet/client/components/SpamDetail.js index dafdb5a86..951622e34 100644 --- a/plugins/talk-plugin-akismet/client/components/SpamDetail.js +++ b/plugins/talk-plugin-akismet/client/components/SpamDetail.js @@ -5,7 +5,7 @@ import { t } from 'plugin-api/beta/client/services'; const SpamLabel = () => ( diff --git a/plugins/talk-plugin-akismet/client/components/SpamLabel.js b/plugins/talk-plugin-akismet/client/components/SpamLabel.js index c2033ff39..17c9b53ef 100644 --- a/plugins/talk-plugin-akismet/client/components/SpamLabel.js +++ b/plugins/talk-plugin-akismet/client/components/SpamLabel.js @@ -3,7 +3,7 @@ import { FlagLabel } from 'plugin-api/beta/client/components/ui'; import { t } from 'plugin-api/beta/client/services'; const SpamLabel = () => ( - {t('talk-plugin-akismet.spam')} + {t('talk-plugin-akismet.spam')} ); export default SpamLabel; diff --git a/plugins/talk-plugin-akismet/client/containers/SpamCommentDetail.js b/plugins/talk-plugin-akismet/client/containers/SpamCommentDetail.js index a133be062..a49fd89ca 100644 --- a/plugins/talk-plugin-akismet/client/containers/SpamCommentDetail.js +++ b/plugins/talk-plugin-akismet/client/containers/SpamCommentDetail.js @@ -5,7 +5,7 @@ import { isSpam } from '../utils'; const enhance = compose( excludeIf( - ({ comment: { spam, actions } }) => spam === null || isSpam(actions) + ({ comment: { spam, actions } }) => spam === null || !isSpam(actions) ) ); diff --git a/plugins/talk-plugin-akismet/client/containers/SpamCommentFlagDetail.js b/plugins/talk-plugin-akismet/client/containers/SpamCommentFlagDetail.js deleted file mode 100644 index a49fd89ca..000000000 --- a/plugins/talk-plugin-akismet/client/containers/SpamCommentFlagDetail.js +++ /dev/null @@ -1,12 +0,0 @@ -import { compose } from 'react-apollo'; -import { excludeIf } from 'plugin-api/beta/client/hocs'; -import SpamDetail from './SpamDetail'; -import { isSpam } from '../utils'; - -const enhance = compose( - excludeIf( - ({ comment: { spam, actions } }) => spam === null || !isSpam(actions) - ) -); - -export default enhance(SpamDetail); diff --git a/plugins/talk-plugin-akismet/client/index.js b/plugins/talk-plugin-akismet/client/index.js index 0eca7ab85..7170e14fb 100644 --- a/plugins/talk-plugin-akismet/client/index.js +++ b/plugins/talk-plugin-akismet/client/index.js @@ -2,7 +2,6 @@ import translations from './translations.yml'; import CheckSpamHook from './containers/CheckSpamHook'; import SpamLabel from './containers/SpamLabel'; import SpamCommentDetail from './containers/SpamCommentDetail'; -import SpamCommentFlagDetail from './containers/SpamCommentFlagDetail'; export default { translations, @@ -10,6 +9,5 @@ export default { commentInputDetailArea: [CheckSpamHook], adminCommentLabels: [SpamLabel], adminCommentMoreDetails: [SpamCommentDetail], - adminCommentMoreFlagDetails: [SpamCommentFlagDetail], }, }; diff --git a/plugins/talk-plugin-toxic-comments/client/components/ToxicDetail.js b/plugins/talk-plugin-toxic-comments/client/components/ToxicDetail.js index 6a278b63b..9530188f2 100644 --- a/plugins/talk-plugin-toxic-comments/client/components/ToxicDetail.js +++ b/plugins/talk-plugin-toxic-comments/client/components/ToxicDetail.js @@ -29,7 +29,7 @@ const getInfo = (toxicity, actions) => { const ToxicLabel = ({ comment: { actions, toxicity } }) => ( diff --git a/plugins/talk-plugin-toxic-comments/client/components/ToxicLabel.js b/plugins/talk-plugin-toxic-comments/client/components/ToxicLabel.js index cb9bca87e..d0f5bbe29 100644 --- a/plugins/talk-plugin-toxic-comments/client/components/ToxicLabel.js +++ b/plugins/talk-plugin-toxic-comments/client/components/ToxicLabel.js @@ -1,6 +1,6 @@ import React from 'react'; import { FlagLabel } from 'plugin-api/beta/client/components/ui'; -const ToxicLabel = () => Toxic; +const ToxicLabel = () => Toxic; export default ToxicLabel; From dc82db6eda7e7441f8c5078c55b638f788f6b65b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 16 Feb 2018 14:38:57 -0700 Subject: [PATCH 10/30] fixed template bugs --- views/admin/confirm-email.ejs | 2 +- views/admin/docs.ejs | 2 +- views/admin/password-reset.ejs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/views/admin/confirm-email.ejs b/views/admin/confirm-email.ejs index e63910734..4e59de1d8 100644 --- a/views/admin/confirm-email.ejs +++ b/views/admin/confirm-email.ejs @@ -5,7 +5,7 @@ Email Verification - <%- include partials/head %> + <%- include ../partials/head %>
diff --git a/views/admin/docs.ejs b/views/admin/docs.ejs index bfc476ee3..7086404ae 100644 --- a/views/admin/docs.ejs +++ b/views/admin/docs.ejs @@ -24,7 +24,7 @@ font-weight: bold; } - <%- include partials/head %> + <%- include ../partials/head %>
diff --git a/views/admin/password-reset.ejs b/views/admin/password-reset.ejs index f66db491a..75770a15e 100644 --- a/views/admin/password-reset.ejs +++ b/views/admin/password-reset.ejs @@ -5,7 +5,7 @@ Password Reset - <%- include partials/head %> + <%- include ../partials/head %>
From e71bcbe58d8ca015cce73f410ee368d1d086348e Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Fri, 16 Feb 2018 17:14:54 -0500 Subject: [PATCH 11/30] Yarn install --- yarn.lock | 78 ++++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 21 deletions(-) diff --git a/yarn.lock b/yarn.lock index 427876288..bc59a3be3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4013,6 +4013,10 @@ hoek@4.x.x: version "4.2.0" resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d" +hoek@5.x.x: + version "5.0.3" + resolved "https://registry.yarnpkg.com/hoek/-/hoek-5.0.3.tgz#b71d40d943d0a95da01956b547f83c4a5b4a34ac" + hoist-non-react-statics@^1.0.0, hoist-non-react-statics@^1.0.3, hoist-non-react-statics@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-1.2.0.tgz#aa448cf0986d55cc40773b17174b7dd066cb7cfb" @@ -4608,9 +4612,11 @@ isemail@1.x.x: version "1.2.0" resolved "https://registry.yarnpkg.com/isemail/-/isemail-1.2.0.tgz#be03df8cc3e29de4d2c5df6501263f1fa4595e9a" -isemail@2.x.x: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isemail/-/isemail-2.2.1.tgz#0353d3d9a62951080c262c2aa0a42b8ea8e9e2a6" +isemail@3.x.x: + version "3.1.1" + resolved "https://registry.yarnpkg.com/isemail/-/isemail-3.1.1.tgz#e8450fe78ff1b48347db599122adcd0668bd92b5" + dependencies: + punycode "2.x.x" isexe@^2.0.0: version "2.0.0" @@ -4696,10 +4702,6 @@ istanbul-reports@^1.1.2: dependencies: handlebars "^4.0.3" -items@2.x.x: - version "2.1.1" - resolved "https://registry.yarnpkg.com/items/-/items-2.1.1.tgz#8bd16d9c83b19529de5aea321acaada78364a198" - iterall@1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/iterall/-/iterall-1.0.2.tgz#41a2e96ce9eda5e61c767ee5dc312373bb046e91" @@ -4933,14 +4935,13 @@ jest@^21.2.1: dependencies: jest-cli "^21.2.1" -joi@^10.6.0: - version "10.6.0" - resolved "https://registry.yarnpkg.com/joi/-/joi-10.6.0.tgz#52587f02d52b8b75cdb0c74f0b164a191a0e1fc2" +joi@^13.0.0: + version "13.1.2" + resolved "https://registry.yarnpkg.com/joi/-/joi-13.1.2.tgz#b2db260323cc7f919fafa51e09e2275bd089a97e" dependencies: - hoek "4.x.x" - isemail "2.x.x" - items "2.x.x" - topo "2.x.x" + hoek "5.x.x" + isemail "3.x.x" + topo "3.x.x" joi@^6.10.1: version "6.10.1" @@ -5109,7 +5110,7 @@ jsonpointer@^4.0.0: version "4.0.1" resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-4.0.1.tgz#4fd92cb34e0e9db3c89c8622ecf51f9b978c6cb9" -jsonwebtoken@^7.0.0, jsonwebtoken@^7.4.3: +jsonwebtoken@^7.0.0: version "7.4.3" resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-7.4.3.tgz#77f5021de058b605a1783fa1283e99812e645638" dependencies: @@ -5119,6 +5120,21 @@ jsonwebtoken@^7.0.0, jsonwebtoken@^7.4.3: ms "^2.0.0" xtend "^4.0.1" +jsonwebtoken@^8.0.0: + version "8.1.1" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-8.1.1.tgz#b04d8bb2ad847bc93238c3c92170ffdbdd1cb2ea" + dependencies: + jws "^3.1.4" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + xtend "^4.0.1" + jsprim@^1.2.2: version "1.4.1" resolved "https://registry.yarnpkg.com/jsprim/-/jsprim-1.4.1.tgz#313e66bc1e5cc06e438bc1b7499c2e5c56acb6a2" @@ -5463,6 +5479,10 @@ lodash.get@4.4.2, lodash.get@^4.4.2: version "4.4.2" resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + lodash.isarguments@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/lodash.isarguments/-/lodash.isarguments-3.1.0.tgz#2f573d85c6a24289ff00663b491c1d338ff3458a" @@ -5471,6 +5491,10 @@ lodash.isarray@^3.0.0: version "3.0.4" resolved "https://registry.yarnpkg.com/lodash.isarray/-/lodash.isarray-3.0.4.tgz#79e4eb88c36a8122af86f844aa9bcd851b5fbb55" +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + lodash.isempty@^4.4.0: version "4.4.0" resolved "https://registry.yarnpkg.com/lodash.isempty/-/lodash.isempty-4.4.0.tgz#6f86cbedd8be4ec987be9aaf33c9684db1b31e7e" @@ -5479,11 +5503,19 @@ lodash.isequal@^4.4.0: version "4.5.0" resolved "https://registry.yarnpkg.com/lodash.isequal/-/lodash.isequal-4.5.0.tgz#415c4478f2bcc30120c22ce10ed3226f7d3e18e0" +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + lodash.isobject@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/lodash.isobject/-/lodash.isobject-3.0.2.tgz#3c8fb8d5b5bf4bf90ae06e14f2a530a4ed935e1d" -lodash.isplainobject@^4.0.0: +lodash.isplainobject@^4.0.0, lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" @@ -5977,7 +6009,7 @@ ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" -ms@^2.0.0: +ms@^2.0.0, ms@^2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.1.tgz#30a5864eb3ebb0a66f2ebe6d727af06a09d86e0a" @@ -7507,6 +7539,10 @@ punycode@1.4.1, punycode@^1.2.4, punycode@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e" +punycode@2.x.x: + version "2.1.0" + resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d" + pym.js@^1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/pym.js/-/pym.js-1.3.2.tgz#0ebd083c5a7ef7650214db872b4b29a10743305d" @@ -9100,11 +9136,11 @@ topo@1.x.x: dependencies: hoek "2.x.x" -topo@2.x.x: - version "2.0.2" - resolved "https://registry.yarnpkg.com/topo/-/topo-2.0.2.tgz#cd5615752539057c0dc0491a621c3bc6fbe1d182" +topo@3.x.x: + version "3.0.0" + resolved "https://registry.yarnpkg.com/topo/-/topo-3.0.0.tgz#37e48c330efeac784538e0acd3e62ca5e231fe7a" dependencies: - hoek "4.x.x" + hoek "5.x.x" touch@^3.1.0: version "3.1.0" From d86ff8418d7b46e70d9fe02cc7fc5c1ab5689ba8 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 16 Feb 2018 15:26:18 -0700 Subject: [PATCH 12/30] fixed changes to api --- services/jwt.js | 18 ++++++++++++------ services/tokens.js | 4 +++- 2 files changed, 15 insertions(+), 7 deletions(-) diff --git a/services/jwt.js b/services/jwt.js index 67e45a3ab..356adbb62 100644 --- a/services/jwt.js +++ b/services/jwt.js @@ -1,5 +1,5 @@ const jwt = require('jsonwebtoken'); -const uniq = require('lodash/uniq'); +const { merge, uniq, omitBy, isUndefined } = require('lodash'); /** * MultiSecret will take many secrets and provide a unified interface for @@ -22,7 +22,10 @@ class MultiSecret { * Sign will sign with the first secret. */ sign(payload, options) { - return this.secrets[0].sign(payload, options); + return this.secrets[0].sign( + omitBy(payload, isUndefined), + omitBy(options, isUndefined) + ); } /** @@ -78,10 +81,13 @@ class Secret { return jwt.sign( payload, this.signingKey, - Object.assign({}, options, { - keyid: this.kid, - algorithm: this.algorithm, - }) + omitBy( + merge({}, options, { + keyid: this.kid, + algorithm: this.algorithm, + }), + isUndefined + ) ); } diff --git a/services/tokens.js b/services/tokens.js index 636465262..2f62af61f 100644 --- a/services/tokens.js +++ b/services/tokens.js @@ -27,7 +27,9 @@ module.exports = class TokenService { pat: true, }; - set(payload, JWT_USER_ID_CLAIM, userID); + if (userID) { + set(payload, JWT_USER_ID_CLAIM, userID); + } // Sign the payload. const jwt = JWT_SECRET.sign(payload, {}); From c2aa2077f470b396339eaef5b0f01b843f552e1a Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Fri, 16 Feb 2018 16:14:49 -0700 Subject: [PATCH 13/30] Show suspension status on button --- client/coral-framework/utils/user.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/client/coral-framework/utils/user.js b/client/coral-framework/utils/user.js index 93dcd4db9..f50fca335 100644 --- a/client/coral-framework/utils/user.js +++ b/client/coral-framework/utils/user.js @@ -21,7 +21,7 @@ export const getReliability = reliabilityValue => { */ export const isSuspended = user => { - const suspensionUntil = get(user, 'status.suspension.until'); + const suspensionUntil = get(user, 'state.status.suspension.until'); return user && suspensionUntil && new Date(suspensionUntil) > new Date(); }; From 499a7fe686bb75f5a388c1d29e44ac15c77e85da Mon Sep 17 00:00:00 2001 From: Kit Westneat Date: Fri, 16 Feb 2018 14:40:28 -0500 Subject: [PATCH 14/30] add Google auth plugin --- .gitignore | 1 + docs/_docs/02-02-advanced-configuration.md | 26 +++++++++++- .../talk-plugin-google-auth/.eslintrc.json | 3 ++ .../client/.eslintrc.json | 3 ++ .../talk-plugin-google-auth/client/actions.js | 7 ++++ .../client/components/GoogleButton.css | 13 ++++++ .../client/components/GoogleButton.js | 11 +++++ .../client/components/SignIn.js | 9 ++++ .../client/components/SignUp.js | 9 ++++ .../client/containers/GoogleButton.js | 9 ++++ .../talk-plugin-google-auth/client/index.js | 11 +++++ .../client/translations.yml | 20 +++++++++ plugins/talk-plugin-google-auth/index.js | 7 ++++ plugins/talk-plugin-google-auth/package.json | 9 ++++ .../server/passport.js | 41 +++++++++++++++++++ .../talk-plugin-google-auth/server/router.js | 29 +++++++++++++ plugins/talk-plugin-google-auth/yarn.lock | 34 +++++++++++++++ 17 files changed, 241 insertions(+), 1 deletion(-) create mode 100644 plugins/talk-plugin-google-auth/.eslintrc.json create mode 100644 plugins/talk-plugin-google-auth/client/.eslintrc.json create mode 100644 plugins/talk-plugin-google-auth/client/actions.js create mode 100644 plugins/talk-plugin-google-auth/client/components/GoogleButton.css create mode 100644 plugins/talk-plugin-google-auth/client/components/GoogleButton.js create mode 100644 plugins/talk-plugin-google-auth/client/components/SignIn.js create mode 100644 plugins/talk-plugin-google-auth/client/components/SignUp.js create mode 100644 plugins/talk-plugin-google-auth/client/containers/GoogleButton.js create mode 100644 plugins/talk-plugin-google-auth/client/index.js create mode 100644 plugins/talk-plugin-google-auth/client/translations.yml create mode 100644 plugins/talk-plugin-google-auth/index.js create mode 100644 plugins/talk-plugin-google-auth/package.json create mode 100644 plugins/talk-plugin-google-auth/server/passport.js create mode 100644 plugins/talk-plugin-google-auth/server/router.js create mode 100644 plugins/talk-plugin-google-auth/yarn.lock diff --git a/.gitignore b/.gitignore index a27a08f15..182f854dc 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,7 @@ plugins.json plugins/* !plugins/talk-plugin-akismet !plugins/talk-plugin-facebook-auth +!plugins/talk-plugin-google-auth !plugins/talk-plugin-auth !plugins/talk-plugin-respect !plugins/talk-plugin-offtopic diff --git a/docs/_docs/02-02-advanced-configuration.md b/docs/_docs/02-02-advanced-configuration.md index 50c430252..4fc60a2cc 100644 --- a/docs/_docs/02-02-advanced-configuration.md +++ b/docs/_docs/02-02-advanced-configuration.md @@ -77,6 +77,30 @@ or by visiting the guide. This is only required while the `talk-plugin-facebook-auth` plugin is enabled. +## TALK_GOOGLE_CLIENT_ID + +The Google OAuth2 client ID for your Google login web app. You can learn more +about getting a Google Client ID at the +[Google API Console](https://console.developers.google.com/apis/){:target="_blank"}. + +You will need to enable the Google+ API in the dashboard and create credentials +for a new OAuth client ID web application. The authorized JavaScript origin +should be set to the Talk domain, and the authorized redirect URI should be set +to http:///api/v1/auth/google/callback. This is only required while +the `talk-plugin-google-auth` plugin is enabled. + +## TALK_GOOGLE_CLIENT_SECRET + +The Google OAuth2 client ID for your Google login web app. You can learn more +about getting a Google Client ID at the +[Google API Console](https://console.developers.google.com/apis/){:target="_blank"}. + +You will need to enable the Google+ API in the dashboard and create credentials +for a new OAuth client ID web application. The authorized JavaScript origin +should be set to the Talk domain, and the authorized redirect URI should be set +to http:///api/v1/auth/google/callback. This is only required while +the `talk-plugin-google-auth` plugin is enabled. + ## TALK_HELMET_CONFIGURATION A JSON string representing the configuration passed to the @@ -538,4 +562,4 @@ This is a **Build Variable** and must be consumed during build. If using the [Docker-onbuild]({{ "/installation-from-docker/#onbuild" | relative_url }}) image you can specify it with `--build-arg TALK_REPLY_COMMENTS_LOAD_DEPTH=3`. -Specifies the initial replies to load for a comment. (Default `3`) \ No newline at end of file +Specifies the initial replies to load for a comment. (Default `3`) diff --git a/plugins/talk-plugin-google-auth/.eslintrc.json b/plugins/talk-plugin-google-auth/.eslintrc.json new file mode 100644 index 000000000..78f7c2397 --- /dev/null +++ b/plugins/talk-plugin-google-auth/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk" +} diff --git a/plugins/talk-plugin-google-auth/client/.eslintrc.json b/plugins/talk-plugin-google-auth/client/.eslintrc.json new file mode 100644 index 000000000..c8a6db18a --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk/client" +} diff --git a/plugins/talk-plugin-google-auth/client/actions.js b/plugins/talk-plugin-google-auth/client/actions.js new file mode 100644 index 000000000..6519d9f2b --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/actions.js @@ -0,0 +1,7 @@ +export const loginWithGoogle = () => (dispatch, _, { rest }) => { + window.open( + `${rest.uri}/auth/google`, + 'Continue with Google', + 'menubar=0,resizable=0,width=500,height=500,top=200,left=500' + ); +}; diff --git a/plugins/talk-plugin-google-auth/client/components/GoogleButton.css b/plugins/talk-plugin-google-auth/client/components/GoogleButton.css new file mode 100644 index 000000000..fb5e8abf3 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/components/GoogleButton.css @@ -0,0 +1,13 @@ +.button { + background-color: #db3236; + border-color: #db3236; + color: rgb(255, 255, 255); + width: 100%; + box-sizing: border-box; + padding: 10px 20px; +} + +.button:hover { + background-color: #c71e22; + border-color: #c71e22; +} diff --git a/plugins/talk-plugin-google-auth/client/components/GoogleButton.js b/plugins/talk-plugin-google-auth/client/components/GoogleButton.js new file mode 100644 index 000000000..8451595f7 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/components/GoogleButton.js @@ -0,0 +1,11 @@ +import React from 'react'; +import { BareButton } from 'plugin-api/beta/client/components/ui'; +import styles from './GoogleButton.css'; + +export default ({ onClick, children }) => { + return ( + + {children} + + ); +}; diff --git a/plugins/talk-plugin-google-auth/client/components/SignIn.js b/plugins/talk-plugin-google-auth/client/components/SignIn.js new file mode 100644 index 000000000..527d41238 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/components/SignIn.js @@ -0,0 +1,9 @@ +import React from 'react'; +import GoogleButton from '../containers/GoogleButton'; +import { t } from 'plugin-api/beta/client/services'; + +export default () => { + return ( + {t('talk-plugin-google-auth.sign_in')} + ); +}; diff --git a/plugins/talk-plugin-google-auth/client/components/SignUp.js b/plugins/talk-plugin-google-auth/client/components/SignUp.js new file mode 100644 index 000000000..704cc3963 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/components/SignUp.js @@ -0,0 +1,9 @@ +import React from 'react'; +import GoogleButton from '../containers/GoogleButton'; +import { t } from 'plugin-api/beta/client/services'; + +export default () => { + return ( + {t('talk-plugin-google-auth.sign_up')} + ); +}; diff --git a/plugins/talk-plugin-google-auth/client/containers/GoogleButton.js b/plugins/talk-plugin-google-auth/client/containers/GoogleButton.js new file mode 100644 index 000000000..d1982e351 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/containers/GoogleButton.js @@ -0,0 +1,9 @@ +import { connect } from 'plugin-api/beta/client/hocs'; +import { bindActionCreators } from 'redux'; +import { loginWithGoogle } from '../actions'; +import GoogleButton from '../components/GoogleButton'; + +const mapDispatchToProps = dispatch => + bindActionCreators({ onClick: loginWithGoogle }, dispatch); + +export default connect(null, mapDispatchToProps)(GoogleButton); diff --git a/plugins/talk-plugin-google-auth/client/index.js b/plugins/talk-plugin-google-auth/client/index.js new file mode 100644 index 000000000..cb8a8f059 --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/index.js @@ -0,0 +1,11 @@ +import SignIn from './components/SignIn'; +import SignUp from './components/SignUp'; +import translations from './translations.yml'; + +export default { + translations, + slots: { + authExternalSignIn: [SignIn], + authExternalSignUp: [SignUp], + }, +}; diff --git a/plugins/talk-plugin-google-auth/client/translations.yml b/plugins/talk-plugin-google-auth/client/translations.yml new file mode 100644 index 000000000..fd926f3ee --- /dev/null +++ b/plugins/talk-plugin-google-auth/client/translations.yml @@ -0,0 +1,20 @@ +en: + talk-plugin-google-auth: + sign_in: "Sign in with Google" + sign_up: "Sign up with Google" +es: + talk-plugin-google-auth: + google_sign_in: "Entrar con Google" + google_sign_up: "Registrarse con Google" +fr: + talk-plugin-google-auth: + google_sign_in: "Connectez-vous avec Google" + google_sign_up: "Inscrivez-vous avec Google" +zh_CN: + talk-plugin-google-auth: + google_sign_in: "使用 Google 帐号" + google_sign_up: "使用 Google 帐号" +zh_TW: + talk-plugin-google-auth: + google_sign_in: "使用 Google 帳號" + google_sign_up: "使用 Google 帳號" diff --git a/plugins/talk-plugin-google-auth/index.js b/plugins/talk-plugin-google-auth/index.js new file mode 100644 index 000000000..b9694613e --- /dev/null +++ b/plugins/talk-plugin-google-auth/index.js @@ -0,0 +1,7 @@ +const passport = require('./server/passport'); +const router = require('./server/router'); + +module.exports = { + passport, + router, +}; diff --git a/plugins/talk-plugin-google-auth/package.json b/plugins/talk-plugin-google-auth/package.json new file mode 100644 index 000000000..385556fce --- /dev/null +++ b/plugins/talk-plugin-google-auth/package.json @@ -0,0 +1,9 @@ +{ + "name": "talk-plugin-google-auth", + "version": "1.0.0", + "main": "index.js", + "license": "MIT", + "dependencies": { + "passport-google-oauth2": "^0.1.6" + } +} diff --git a/plugins/talk-plugin-google-auth/server/passport.js b/plugins/talk-plugin-google-auth/server/passport.js new file mode 100644 index 000000000..c3463557b --- /dev/null +++ b/plugins/talk-plugin-google-auth/server/passport.js @@ -0,0 +1,41 @@ +const GoogleStrategy = require('passport-google-oauth2').Strategy; +const UsersService = require('services/users'); +const { ValidateUserLogin } = require('services/passport'); +let { ROOT_URL } = require('config'); + +if (ROOT_URL[ROOT_URL.length - 1] !== '/') { + ROOT_URL += '/'; +} + +module.exports = passport => { + if ( + process.env.TALK_GOOGLE_CLIENT_ID && + process.env.TALK_GOOGLE_CLIENT_SECRET && + process.env.TALK_ROOT_URL + ) { + passport.use( + new GoogleStrategy( + { + clientID: process.env.TALK_GOOGLE_CLIENT_ID, + clientSecret: process.env.TALK_GOOGLE_CLIENT_SECRET, + callbackURL: `${ROOT_URL}api/v1/auth/google/callback`, + passReqToCallback: true, + }, + async (req, accessToken, refreshToken, profile, done) => { + let user; + try { + user = await UsersService.findOrCreateExternalUser(profile); + } catch (err) { + return done(err.toString()); + } + + return ValidateUserLogin(profile, user, done); + } + ) + ); + } else if (process.env.NODE_ENV !== 'test') { + throw new Error( + 'Google cannot be enabled, missing one of TALK_GOOGLE_CLIENT_ID, TALK_GOOGLE_CLIENT_SECRET, TALK_ROOT_URL' + ); + } +}; diff --git a/plugins/talk-plugin-google-auth/server/router.js b/plugins/talk-plugin-google-auth/server/router.js new file mode 100644 index 000000000..89977f35b --- /dev/null +++ b/plugins/talk-plugin-google-auth/server/router.js @@ -0,0 +1,29 @@ +module.exports = router => { + const { passport, HandleAuthPopupCallback } = require('services/passport'); + + /** + * Google auth endpoint, this will redirect the user immediatly to google + * for authorization. + */ + router.get( + '/api/v1/auth/google', + passport.authenticate('google', { + display: 'popup', + authType: 'rerequest', + scope: ['profile'], + }) + ); + + /** + * Google callback endpoint, this will send the user a html page designed to + * send back the user credentials upon sucesfull login. + */ + router.get('/api/v1/auth/google/callback', (req, res, next) => { + // Perform the google login flow and pass the data back through the opener. + passport.authenticate( + 'google', + { session: false }, + HandleAuthPopupCallback(req, res, next) + )(req, res, next); + }); +}; diff --git a/plugins/talk-plugin-google-auth/yarn.lock b/plugins/talk-plugin-google-auth/yarn.lock new file mode 100644 index 000000000..e1f5c4202 --- /dev/null +++ b/plugins/talk-plugin-google-auth/yarn.lock @@ -0,0 +1,34 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +oauth@0.9.x: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + +passport-google-oauth2@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/passport-google-oauth2/-/passport-google-oauth2-0.1.6.tgz#dfd7016ac7449fe27cfeb252ae974afc23257a0d" + dependencies: + passport-oauth2 "^1.1.2" + +passport-oauth2@^1.1.2: + version "1.4.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad" + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + +passport-strategy@1.x.x: + version "1.0.0" + resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" + +uid2@0.0.x: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + +utils-merge@1.x.x: + version "1.0.1" + resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" From 87ff631eedcf23256184c482920960cc3c8b90e1 Mon Sep 17 00:00:00 2001 From: Leandro Date: Mon, 19 Feb 2018 09:50:07 +0100 Subject: [PATCH 15/30] fix translations keys and add germna translations to facebook plugin --- .../client/translations.yml | 20 +++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/plugins/talk-plugin-facebook-auth/client/translations.yml b/plugins/talk-plugin-facebook-auth/client/translations.yml index 35a5c255c..ab72134f4 100644 --- a/plugins/talk-plugin-facebook-auth/client/translations.yml +++ b/plugins/talk-plugin-facebook-auth/client/translations.yml @@ -4,17 +4,21 @@ en: sign_up: "Sign up with Facebook" es: talk-plugin-facebook-auth: - facebook_sign_in: "Entrar con Facebook" - facebook_sign_up: "Registrarse con Facebook" + sign_in: "Entrar con Facebook" + sign_up: "Registrarse con Facebook" fr: talk-plugin-facebook-auth: - facebook_sign_in: "Connectez-vous avec Facebook" - facebook_sign_up: "Inscrivez-vous avec Facebook" + sign_in: "Connectez-vous avec Facebook" + sign_up: "Inscrivez-vous avec Facebook" zh_CN: talk-plugin-facebook-auth: - facebook_sign_in: "使用 Facebook 帐号" - facebook_sign_up: "使用 Facebook 帐号" + sign_in: "使用 Facebook 帐号" + sign_up: "使用 Facebook 帐号" zh_TW: talk-plugin-facebook-auth: - facebook_sign_in: "使用 Facebook 帳號" - facebook_sign_up: "使用 Facebook 帳號" + sign_in: "使用 Facebook 帳號" + sign_up: "使用 Facebook 帳號" +de: + talk-plugin-facebook-auth: + sign_in: "Mit Facebook anmelden" + sign_up: "Mit Facebook registrieren" \ No newline at end of file From 606d5734f9587677a78c580e188de88f5fe0008d Mon Sep 17 00:00:00 2001 From: Leandro Date: Mon, 19 Feb 2018 11:18:56 +0100 Subject: [PATCH 16/30] set a right last release 4.2.1 to package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 435815220..4b1fd68cf 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "4.2.0", + "version": "4.2.1", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "private": true, From 8c2070e345265a14bd39aa6be98678b5622a811c Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Mon, 19 Feb 2018 16:04:43 +0100 Subject: [PATCH 17/30] Refactor Profile --- .../src/components/Embed.js | 4 +- .../src/tabs/profile/components/Comment.js | 27 ++- .../tabs/profile/components/CommentHistory.js | 8 +- .../src/tabs/profile/components/Profile.js | 26 +++ .../src/tabs/profile/containers/Comment.js | 32 ++++ .../tabs/profile/containers/CommentHistory.js | 103 ++++++++++ .../src/tabs/profile/containers/Profile.js | 93 +++++++++ .../profile/containers/ProfileContainer.js | 178 ------------------ client/coral-framework/services/pym.js | 6 +- 9 files changed, 278 insertions(+), 199 deletions(-) create mode 100644 client/coral-embed-stream/src/tabs/profile/components/Profile.js create mode 100644 client/coral-embed-stream/src/tabs/profile/containers/Comment.js create mode 100644 client/coral-embed-stream/src/tabs/profile/containers/CommentHistory.js create mode 100644 client/coral-embed-stream/src/tabs/profile/containers/Profile.js delete mode 100644 client/coral-embed-stream/src/tabs/profile/containers/ProfileContainer.js diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index b7eb6d580..71ff39240 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -9,7 +9,7 @@ import AutomaticAssetClosure from '../containers/AutomaticAssetClosure'; import ExtendableTabPanel from '../containers/ExtendableTabPanel'; import { Tab, TabPane } from 'coral-ui'; -import ProfileContainer from '../tabs/profile/containers/ProfileContainer'; +import Profile from '../tabs/profile/containers/Profile'; import Popup from 'coral-framework/components/Popup'; import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty'; import cn from 'classnames'; @@ -112,7 +112,7 @@ export default class Embed extends React.Component { tabId="profile" className="talk-embed-stream-profile-tab-pane" > - + , { + this.props.navigate(this.props.comment.asset.url); + }; + + goToConversation = () => { + this.props.navigate( + `${this.props.comment.asset.url}?commentId=${this.props.comment.id}` + ); + }; + render() { - const { comment, link, data, root } = this.props; + const { comment, data, root } = this.props; const reactionCount = getTotalReactionsCount(comment.action_summaries); const queryData = { root, comment, asset: comment.asset }; @@ -67,7 +77,7 @@ class Comment extends React.Component { {t('common.story')}:{' '} {comment.asset.title ? comment.asset.title : comment.asset.url} @@ -77,10 +87,7 @@ class Comment extends React.Component {
  • - + {t('view_conversation')} @@ -105,10 +112,10 @@ class Comment extends React.Component { } Comment.propTypes = { - comment: PropTypes.shape({ - id: PropTypes.string, - body: PropTypes.string, - }).isRequired, + comment: PropTypes.object.isRequired, + navigate: PropTypes.func.isRequired, + data: PropTypes.object.isRequired, + root: PropTypes.object.isRequired, }; export default Comment; diff --git a/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js b/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js index a17a347c5..f16e8a119 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js +++ b/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js @@ -1,6 +1,6 @@ import React from 'react'; import PropTypes from 'prop-types'; -import Comment from './Comment'; +import Comment from '../containers/Comment'; import LoadMore from './LoadMore'; class CommentHistory extends React.Component { @@ -21,7 +21,7 @@ class CommentHistory extends React.Component { }; render() { - const { link, comments, data, root } = this.props; + const { navigate, comments, data, root } = this.props; return (
    @@ -32,7 +32,7 @@ class CommentHistory extends React.Component { data={data} root={root} comment={comment} - link={link} + navigate={navigate} /> ); })} @@ -51,7 +51,7 @@ class CommentHistory extends React.Component { CommentHistory.propTypes = { comments: PropTypes.object.isRequired, loadMore: PropTypes.func, - link: PropTypes.func, + navigate: PropTypes.func, data: PropTypes.object, root: PropTypes.object, }; diff --git a/client/coral-embed-stream/src/tabs/profile/components/Profile.js b/client/coral-embed-stream/src/tabs/profile/components/Profile.js new file mode 100644 index 000000000..b457e7a1d --- /dev/null +++ b/client/coral-embed-stream/src/tabs/profile/components/Profile.js @@ -0,0 +1,26 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import Slot from 'coral-framework/components/Slot'; +import CommentHistory from '../containers/CommentHistory'; + +import t from 'coral-framework/services/i18n'; + +const Profile = ({ username, emailAddress, data, root }) => ( +
    +

    {username}

    + {emailAddress ?

    {emailAddress}

    : null} + +
    +

    {t('framework.my_comments')}

    + +
    +); + +Profile.propTypes = { + username: PropTypes.string, + emailAddress: PropTypes.string, + data: PropTypes.object, + root: PropTypes.object, +}; + +export default Profile; diff --git a/client/coral-embed-stream/src/tabs/profile/containers/Comment.js b/client/coral-embed-stream/src/tabs/profile/containers/Comment.js new file mode 100644 index 000000000..7406d4c5e --- /dev/null +++ b/client/coral-embed-stream/src/tabs/profile/containers/Comment.js @@ -0,0 +1,32 @@ +import { gql, compose } from 'react-apollo'; +import Comment from '../components/Comment'; +import { withFragments } from 'coral-framework/hocs'; +import { getSlotFragmentSpreads } from 'coral-framework/utils'; + +const slots = ['commentContent', 'historyCommentTimestamp']; + +const withCommentFragments = withFragments({ + comment: gql` + fragment TalkEmbedStream_Comment_Fragment on Comment { + id + body + replyCount + action_summaries { + count + __typename + } + asset { + id + title + url + ${getSlotFragmentSpreads(slots, 'asset')} + } + created_at + ${getSlotFragmentSpreads(slots, 'comment')} + } + `, +}); + +const enhance = compose(withCommentFragments); + +export default enhance(Comment); diff --git a/client/coral-embed-stream/src/tabs/profile/containers/CommentHistory.js b/client/coral-embed-stream/src/tabs/profile/containers/CommentHistory.js new file mode 100644 index 000000000..a02225939 --- /dev/null +++ b/client/coral-embed-stream/src/tabs/profile/containers/CommentHistory.js @@ -0,0 +1,103 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { compose, gql } from 'react-apollo'; +import CommentHistory from '../components/CommentHistory'; +import Comment from './Comment'; +import { withFragments } from 'coral-framework/hocs'; + +import { appendNewNodes } from 'plugin-api/beta/client/utils'; +import update from 'immutability-helper'; +import { getDefinitionName } from 'coral-framework/utils'; + +class CommentHistoryContainer extends Component { + navigate = url => { + this.context.pym.sendMessage('navigate', url); + }; + + loadMore = () => { + return this.props.data.fetchMore({ + query: LOAD_MORE_QUERY, + variables: { + limit: 5, + cursor: this.props.root.me.comments.endCursor, + }, + updateQuery: (previous, { fetchMoreResult: { me: { comments } } }) => { + const updated = update(previous, { + me: { + comments: { + nodes: { + $apply: nodes => appendNewNodes(nodes, comments.nodes), + }, + hasNextPage: { $set: comments.hasNextPage }, + endCursor: { $set: comments.endCursor }, + }, + }, + }); + return updated; + }, + }); + }; + + render() { + return ( + + ); + } +} + +CommentHistoryContainer.contextTypes = { + pym: PropTypes.object, +}; + +CommentHistoryContainer.propTypes = { + data: PropTypes.object, + root: PropTypes.object, +}; + +const LOAD_MORE_QUERY = gql` + query TalkEmbedStream_CommentHistory_LoadMoreComments($limit: Int, $cursor: Cursor) { + me { + comments(query: { limit: $limit, cursor: $cursor }) { + nodes { + ...${getDefinitionName(Comment.fragments.comment)} + } + endCursor + hasNextPage + } + } + } + ${Comment.fragments.comment} +`; + +const withCommentHistoryFragments = withFragments({ + root: gql` + fragment TalkEmbedStream_CommentHistory on RootQuery { + me { + comments(query: {limit: 10}) { + nodes { + ...${getDefinitionName(Comment.fragments.comment)} + } + endCursor + hasNextPage + } + } + } + ${Comment.fragments.comment} + `, +}); + +const mapStateToProps = state => ({ + currentUser: state.auth.user, +}); + +export default compose( + connect(mapStateToProps, null), + withCommentHistoryFragments +)(CommentHistoryContainer); diff --git a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js new file mode 100644 index 000000000..2fc768535 --- /dev/null +++ b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js @@ -0,0 +1,93 @@ +import React, { Component } from 'react'; +import PropTypes from 'prop-types'; +import { connect } from 'react-redux'; +import { compose, gql } from 'react-apollo'; +import { bindActionCreators } from 'redux'; +import { withQuery } from 'coral-framework/hocs'; +import NotLoggedIn from '../components/NotLoggedIn'; +import { Spinner } from 'coral-ui'; +import Profile from '../components/Profile'; +import CommentHistory from './CommentHistory'; +import { getDefinitionName } from 'coral-framework/utils'; + +import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; +import { getSlotFragmentSpreads } from 'coral-framework/utils'; + +class ProfileContainer extends Component { + componentWillReceiveProps(nextProps) { + if (!this.props.currentUser && nextProps.currentUser) { + // Refetch after login. + this.props.data.refetch(); + } + } + + render() { + const { currentUser, showSignInDialog, root, data } = this.props; + const { me } = this.props.root; + const loading = this.props.data.loading; + + if (this.props.data.error) { + return
    {this.props.data.error.message}
    ; + } + + if (!currentUser) { + return ; + } + + if (loading || !me) { + return ; + } + + const localProfile = currentUser.profiles.find(p => p.provider === 'local'); + const emailAddress = localProfile && localProfile.id; + + return ( + + ); + } +} + +ProfileContainer.propTypes = { + data: PropTypes.object, + root: PropTypes.object, + currentUser: PropTypes.object, + showSignInDialog: PropTypes.func, +}; + +const slots = ['profileSections']; + +const withProfileQuery = withQuery( + gql` + query CoralEmbedStream_Profile { + me { + id + username + } + ...${getDefinitionName(CommentHistory.fragments.root)} + ${getSlotFragmentSpreads(slots, 'root')} + } + ${CommentHistory.fragments.root} +`, + { + options: { + fetchPolicy: 'network-only', + }, + } +); + +const mapStateToProps = state => ({ + currentUser: state.auth.user, +}); + +const mapDispatchToProps = dispatch => + bindActionCreators({ showSignInDialog }, dispatch); + +export default compose( + connect(mapStateToProps, mapDispatchToProps), + withProfileQuery +)(ProfileContainer); diff --git a/client/coral-embed-stream/src/tabs/profile/containers/ProfileContainer.js b/client/coral-embed-stream/src/tabs/profile/containers/ProfileContainer.js deleted file mode 100644 index 57aa2b0fe..000000000 --- a/client/coral-embed-stream/src/tabs/profile/containers/ProfileContainer.js +++ /dev/null @@ -1,178 +0,0 @@ -import { connect } from 'react-redux'; -import { compose, gql } from 'react-apollo'; -import React, { Component } from 'react'; -import { bindActionCreators } from 'redux'; -import { withQuery } from 'coral-framework/hocs'; -import Slot from 'coral-framework/components/Slot'; -import cn from 'classnames'; -import { link } from 'coral-framework/services/pym'; -import NotLoggedIn from '../components/NotLoggedIn'; -import { Spinner } from 'coral-ui'; -import CommentHistory from '../components/CommentHistory'; - -import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; -import { appendNewNodes } from 'plugin-api/beta/client/utils'; -import update from 'immutability-helper'; -import { getSlotFragmentSpreads } from 'coral-framework/utils'; - -import t from 'coral-framework/services/i18n'; - -class ProfileContainer extends Component { - componentWillReceiveProps(nextProps) { - if (!this.props.currentUser && nextProps.currentUser) { - // Refetch after login. - this.props.data.refetch(); - } - } - - loadMore = () => { - return this.props.data.fetchMore({ - query: LOAD_MORE_QUERY, - variables: { - limit: 5, - cursor: this.props.root.me.comments.endCursor, - }, - updateQuery: (previous, { fetchMoreResult: { me: { comments } } }) => { - const updated = update(previous, { - me: { - comments: { - nodes: { - $apply: nodes => appendNewNodes(nodes, comments.nodes), - }, - hasNextPage: { $set: comments.hasNextPage }, - endCursor: { $set: comments.endCursor }, - }, - }, - }); - return updated; - }, - }); - }; - - render() { - const { currentUser, showSignInDialog, root, data } = this.props; - const { me } = this.props.root; - const loading = this.props.data.loading; - - if (this.props.data.error) { - return
    {this.props.data.error.message}
    ; - } - - if (!currentUser) { - return ; - } - - if (loading || !me) { - return ; - } - - const localProfile = currentUser.profiles.find(p => p.provider === 'local'); - const emailAddress = localProfile && localProfile.id; - - return ( -
    -

    {me.username}

    - {emailAddress ?

    {emailAddress}

    : null} - -
    -

    {t('framework.my_comments')}

    -
    - {me.comments.nodes.length ? ( - - ) : ( -

    - {t('user_no_comment')} -

    - )} -
    -
    - ); - } -} - -const slots = [ - 'profileSections', - - // TODO: These Slots should be included in `talk-plugin-history` instead. - 'commentContent', - 'historyCommentTimestamp', -]; - -const CommentFragment = gql` - fragment TalkSettings_CommentConnectionFragment on CommentConnection { - nodes { - id - body - replyCount - action_summaries { - count - __typename - } - asset { - id - title - url - ${getSlotFragmentSpreads(slots, 'asset')} - } - created_at - ${getSlotFragmentSpreads(slots, 'comment')} - } - endCursor - hasNextPage - } -`; - -const LOAD_MORE_QUERY = gql` - query TalkSettings_LoadMoreComments($limit: Int, $cursor: Cursor) { - me { - comments(query: { limit: $limit, cursor: $cursor }) { - ...TalkSettings_CommentConnectionFragment - } - } - } - ${CommentFragment} -`; - -const withProfileQuery = withQuery( - gql` - query CoralEmbedStream_Profile { - me { - id - username - comments(query: {limit: 10}) { - ...TalkSettings_CommentConnectionFragment - } - } - ${getSlotFragmentSpreads(slots, 'root')} - } - ${CommentFragment} -`, - { - options: { - fetchPolicy: 'network-only', - }, - } -); - -const mapStateToProps = state => ({ - currentUser: state.auth.user, -}); - -const mapDispatchToProps = dispatch => - bindActionCreators({ showSignInDialog }, dispatch); - -export default compose( - connect(mapStateToProps, mapDispatchToProps), - withProfileQuery -)(ProfileContainer); diff --git a/client/coral-framework/services/pym.js b/client/coral-framework/services/pym.js index be81fc518..b4eb193ee 100644 --- a/client/coral-framework/services/pym.js +++ b/client/coral-framework/services/pym.js @@ -1,9 +1,5 @@ import Pym from 'pym.js'; const pym = new Pym.Child({ polling: 100 }); -export default pym; -export const link = url => e => { - e.preventDefault(); - pym.sendMessage('navigate', url); -}; +export default pym; From 12497a1331cacf25367bbd5e4197f17213834307 Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 19 Feb 2018 12:07:57 -0300 Subject: [PATCH 18/30] Adding duration asHours and asDays --- .../src/components/AccountHistory.js | 22 ++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/components/AccountHistory.js b/client/coral-admin/src/components/AccountHistory.js index 04df552be..73add8120 100644 --- a/client/coral-admin/src/components/AccountHistory.js +++ b/client/coral-admin/src/components/AccountHistory.js @@ -19,14 +19,30 @@ const buildUserHistory = (userState = {}) => { ); }; -const buildActionResponse = (typename, until, status) => { +/** readaebleDuration returns a readaeble duration of the suspension/ban in hours or days + * @param {} startDate + * @param {} endDate + */ +const readaebleDuration = (startDate, endDate) => { + const dur = moment.duration(moment(endDate).diff(moment(startDate))); + const durAsDays = dur.asDays().toFixed(0); + const durAsHours = dur.asHours().toFixed(0); + + return durAsHours > 23 + ? `${durAsDays} ${durAsDays > 1 ? 'days' : 'day'}` + : `${durAsHours} ${durAsHours > 1 ? 'hours' : 'hour'}`; +}; + +const buildActionResponse = (typename, created_at, until, status) => { switch (typename) { case 'UsernameStatusHistory': return `Username ${status}`; case 'BannedStatusHistory': return status ? 'User banned' : 'Ban removed'; case 'SuspensionStatusHistory': - return until ? 'Account Suspended' : 'Suspension removed'; + return until + ? `Suspended, ${readaebleDuration(created_at, until)}` + : 'Suspension removed'; default: return '-'; } @@ -77,7 +93,7 @@ class AccountHistory extends React.Component { 'talk-admin-account-history-row-status' )} > - {buildActionResponse(__typename, until, status)} + {buildActionResponse(__typename, created_at, until, status)}
    Date: Mon, 19 Feb 2018 12:23:36 -0300 Subject: [PATCH 19/30] trim dates, style tooltip --- client/coral-admin/src/components/UserInfoTooltip.css | 8 ++++---- client/coral-admin/src/components/UserInfoTooltip.js | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/client/coral-admin/src/components/UserInfoTooltip.css b/client/coral-admin/src/components/UserInfoTooltip.css index 6b68e5cdf..10712273f 100644 --- a/client/coral-admin/src/components/UserInfoTooltip.css +++ b/client/coral-admin/src/components/UserInfoTooltip.css @@ -28,8 +28,8 @@ box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2); z-index: 10; top: 32px; - right: 0px; - width: 140px; + left: -10px; + width: 200px; text-align: left; color: #616161; } @@ -39,7 +39,7 @@ border: 10px solid transparent; border-top-color: #999; position: absolute; - right: 0px; + left: 8px; top: -20px; transform: rotate(180deg); } @@ -49,7 +49,7 @@ border: 10px solid transparent; border-top-color: white; position: absolute; - right: 0px; + left: 8px; top: -19px; transform: rotate(180deg); } diff --git a/client/coral-admin/src/components/UserInfoTooltip.js b/client/coral-admin/src/components/UserInfoTooltip.js index a5ff365fd..7af675d2c 100644 --- a/client/coral-admin/src/components/UserInfoTooltip.js +++ b/client/coral-admin/src/components/UserInfoTooltip.js @@ -67,7 +67,7 @@ class UserInfoTooltip extends React.Component { new Date( this.getLastHistoryItem(user, 'banned').created_at ) - ).format('MMMM Do YYYY, h:mm:ss a')} + ).format('MMM Do YYYY, h:mm:ss a')}
From 846df0a5e0dde53cd5ade3acbca3517aca54875e Mon Sep 17 00:00:00 2001 From: okbel Date: Mon, 19 Feb 2018 12:27:55 -0300 Subject: [PATCH 20/30] trim dates, style tooltip --- client/coral-admin/src/components/UserInfoTooltip.css | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/components/UserInfoTooltip.css b/client/coral-admin/src/components/UserInfoTooltip.css index 10712273f..3a766859e 100644 --- a/client/coral-admin/src/components/UserInfoTooltip.css +++ b/client/coral-admin/src/components/UserInfoTooltip.css @@ -28,7 +28,7 @@ box-shadow: 0 2px 2px 0 rgba(0,0,0,0.14), 0 1px 5px 0 rgba(0,0,0,0.12), 0 3px 1px -2px rgba(0,0,0,0.2); z-index: 10; top: 32px; - left: -10px; + left: -100px; width: 200px; text-align: left; color: #616161; @@ -39,7 +39,7 @@ border: 10px solid transparent; border-top-color: #999; position: absolute; - left: 8px; + left: 96px; top: -20px; transform: rotate(180deg); } @@ -49,7 +49,7 @@ border: 10px solid transparent; border-top-color: white; position: absolute; - left: 8px; + left: 96px; top: -19px; transform: rotate(180deg); } From 0ca8b978a13ea7ab1225f8c56bb063e9ab3af51a Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Tue, 20 Feb 2018 13:21:02 +0100 Subject: [PATCH 21/30] Implement profile settings --- .eslintignore | 3 +- .gitignore | 1 + .../coral-embed-stream/src/actions/profile.js | 3 ++ .../src/components/Embed.js | 10 ++-- .../src/constants/profile.js | 2 + .../coral-embed-stream/src/reducers/index.js | 2 + .../src/reducers/profile.js | 19 ++++++++ .../src/tabs/profile/components/Profile.css | 11 +++++ .../src/tabs/profile/components/Profile.js | 47 +++++++++++++++---- .../src/tabs/profile/containers/Comment.js | 2 +- .../src/tabs/profile/containers/Profile.js | 15 +++++- plugins.default.json | 3 +- .../talk-plugin-ignore-user/client/index.js | 2 +- .../.eslintrc.json | 3 ++ .../client/.eslintrc.json | 3 ++ .../client/components/Tab.js | 8 ++++ .../client/components/TabPane.js | 21 +++++++++ .../client/containers/TabPane.js | 26 ++++++++++ .../client/index.js | 11 +++++ .../client/translations.yml | 27 +++++++++++ plugins/talk-plugin-profile-settings/index.js | 1 + 21 files changed, 200 insertions(+), 20 deletions(-) create mode 100644 client/coral-embed-stream/src/actions/profile.js create mode 100644 client/coral-embed-stream/src/constants/profile.js create mode 100644 client/coral-embed-stream/src/reducers/profile.js create mode 100644 client/coral-embed-stream/src/tabs/profile/components/Profile.css create mode 100644 plugins/talk-plugin-profile-settings/.eslintrc.json create mode 100644 plugins/talk-plugin-profile-settings/client/.eslintrc.json create mode 100644 plugins/talk-plugin-profile-settings/client/components/Tab.js create mode 100644 plugins/talk-plugin-profile-settings/client/components/TabPane.js create mode 100644 plugins/talk-plugin-profile-settings/client/containers/TabPane.js create mode 100644 plugins/talk-plugin-profile-settings/client/index.js create mode 100644 plugins/talk-plugin-profile-settings/client/translations.yml create mode 100644 plugins/talk-plugin-profile-settings/index.js diff --git a/.eslintignore b/.eslintignore index 50d3f7109..5be504ff4 100644 --- a/.eslintignore +++ b/.eslintignore @@ -31,4 +31,5 @@ public !plugins/talk-plugin-sort-oldest !plugins/talk-plugin-subscriber !plugins/talk-plugin-toxic-comments -!plugins/talk-plugin-viewing-options \ No newline at end of file +!plugins/talk-plugin-viewing-options +!plugins/talk-plugin-profile-settings diff --git a/.gitignore b/.gitignore index a27a08f15..837bee402 100644 --- a/.gitignore +++ b/.gitignore @@ -52,6 +52,7 @@ plugins/* !plugins/talk-plugin-subscriber !plugins/talk-plugin-flag-details !plugins/talk-plugin-slack-notifications +!plugins/talk-plugin-profile-settings **/node_modules/* yarn-error.log diff --git a/client/coral-embed-stream/src/actions/profile.js b/client/coral-embed-stream/src/actions/profile.js new file mode 100644 index 000000000..4b0c8f2e2 --- /dev/null +++ b/client/coral-embed-stream/src/actions/profile.js @@ -0,0 +1,3 @@ +import * as actions from '../constants/profile'; + +export const setActiveTab = tab => ({ type: actions.SET_ACTIVE_TAB, tab }); diff --git a/client/coral-embed-stream/src/components/Embed.js b/client/coral-embed-stream/src/components/Embed.js index 71ff39240..0e251e9a3 100644 --- a/client/coral-embed-stream/src/components/Embed.js +++ b/client/coral-embed-stream/src/components/Embed.js @@ -15,10 +15,6 @@ import IfSlotIsNotEmpty from 'coral-framework/components/IfSlotIsNotEmpty'; import cn from 'classnames'; export default class Embed extends React.Component { - changeTab = tab => { - this.props.setActiveTab(tab); - }; - getTabs() { const tabs = [ ( -
-

{username}

- {emailAddress ?

{emailAddress}

: null} +const Profile = ({ + username, + emailAddress, + data, + root, + activeTab, + setActiveTab, +}) => ( +
+
+

{username}

+ {emailAddress ?

{emailAddress}

: null} +
-
-

{t('framework.my_comments')}

- + + {t('framework.my_comments')} + , + ]} + tabPanes={[ + + + , + ]} + sub + />
); @@ -21,6 +50,8 @@ Profile.propTypes = { emailAddress: PropTypes.string, data: PropTypes.object, root: PropTypes.object, + activeTab: PropTypes.string.isRequired, + setActiveTab: PropTypes.func.isRequired, }; export default Profile; diff --git a/client/coral-embed-stream/src/tabs/profile/containers/Comment.js b/client/coral-embed-stream/src/tabs/profile/containers/Comment.js index 7406d4c5e..b8fb7fad8 100644 --- a/client/coral-embed-stream/src/tabs/profile/containers/Comment.js +++ b/client/coral-embed-stream/src/tabs/profile/containers/Comment.js @@ -7,7 +7,7 @@ const slots = ['commentContent', 'historyCommentTimestamp']; const withCommentFragments = withFragments({ comment: gql` - fragment TalkEmbedStream_Comment_Fragment on Comment { + fragment TalkEmbedStream_ProfileComment_comment on Comment { id body replyCount diff --git a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js index 2fc768535..82a0a49eb 100644 --- a/client/coral-embed-stream/src/tabs/profile/containers/Profile.js +++ b/client/coral-embed-stream/src/tabs/profile/containers/Profile.js @@ -11,6 +11,7 @@ import CommentHistory from './CommentHistory'; import { getDefinitionName } from 'coral-framework/utils'; import { showSignInDialog } from 'coral-embed-stream/src/actions/login'; +import { setActiveTab } from '../../../actions/profile'; import { getSlotFragmentSpreads } from 'coral-framework/utils'; class ProfileContainer extends Component { @@ -47,6 +48,8 @@ class ProfileContainer extends Component { emailAddress={emailAddress} data={data} root={root} + activeTab={this.props.activeTab} + setActiveTab={this.props.setActiveTab} /> ); } @@ -57,9 +60,16 @@ ProfileContainer.propTypes = { root: PropTypes.object, currentUser: PropTypes.object, showSignInDialog: PropTypes.func, + activeTab: PropTypes.string.isRequired, + setActiveTab: PropTypes.func.isRequired, }; -const slots = ['profileSections']; +const slots = [ + 'profileSections', + 'profileTabs', + 'profileTabsPrepend', + 'profileTabPanes', +]; const withProfileQuery = withQuery( gql` @@ -82,10 +92,11 @@ const withProfileQuery = withQuery( const mapStateToProps = state => ({ currentUser: state.auth.user, + activeTab: state.profile.activeTab, }); const mapDispatchToProps = dispatch => - bindActionCreators({ showSignInDialog }, dispatch); + bindActionCreators({ showSignInDialog, setActiveTab }, dispatch); export default compose( connect(mapStateToProps, mapDispatchToProps), diff --git a/plugins.default.json b/plugins.default.json index 066c94478..b54c6a54c 100644 --- a/plugins.default.json +++ b/plugins.default.json @@ -21,6 +21,7 @@ "talk-plugin-sort-most-respected", "talk-plugin-sort-newest", "talk-plugin-sort-oldest", - "talk-plugin-viewing-options" + "talk-plugin-viewing-options", + "talk-plugin-profile-settings" ] } diff --git a/plugins/talk-plugin-ignore-user/client/index.js b/plugins/talk-plugin-ignore-user/client/index.js index 3284915db..a7cb6c886 100644 --- a/plugins/talk-plugin-ignore-user/client/index.js +++ b/plugins/talk-plugin-ignore-user/client/index.js @@ -8,7 +8,7 @@ export default { slots: { authorMenuActions: [IgnoreUserAction], ignoreUserConfirmation: [IgnoreUserConfirmation], - profileSections: [IgnoredUserSection], + profileSettings: [IgnoredUserSection], }, translations, mutations: { diff --git a/plugins/talk-plugin-profile-settings/.eslintrc.json b/plugins/talk-plugin-profile-settings/.eslintrc.json new file mode 100644 index 000000000..78f7c2397 --- /dev/null +++ b/plugins/talk-plugin-profile-settings/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk" +} diff --git a/plugins/talk-plugin-profile-settings/client/.eslintrc.json b/plugins/talk-plugin-profile-settings/client/.eslintrc.json new file mode 100644 index 000000000..c8a6db18a --- /dev/null +++ b/plugins/talk-plugin-profile-settings/client/.eslintrc.json @@ -0,0 +1,3 @@ +{ + "extends": "@coralproject/eslint-config-talk/client" +} diff --git a/plugins/talk-plugin-profile-settings/client/components/Tab.js b/plugins/talk-plugin-profile-settings/client/components/Tab.js new file mode 100644 index 000000000..86df0e8d1 --- /dev/null +++ b/plugins/talk-plugin-profile-settings/client/components/Tab.js @@ -0,0 +1,8 @@ +import React from 'react'; +import { t } from 'plugin-api/beta/client/services'; + +const Tab = () => { + return {t('talk-plugin-profile-settings.tab')}; +}; + +export default Tab; diff --git a/plugins/talk-plugin-profile-settings/client/components/TabPane.js b/plugins/talk-plugin-profile-settings/client/components/TabPane.js new file mode 100644 index 000000000..c0347a95c --- /dev/null +++ b/plugins/talk-plugin-profile-settings/client/components/TabPane.js @@ -0,0 +1,21 @@ +import React from 'react'; +import PropTypes from 'prop-types'; +import { Slot } from 'plugin-api/beta/client/components'; + +class TabPane extends React.Component { + render() { + const { data, root } = this.props; + return ( +
+ +
+ ); + } +} + +TabPane.propTypes = { + data: PropTypes.object, + root: PropTypes.object, +}; + +export default TabPane; diff --git a/plugins/talk-plugin-profile-settings/client/containers/TabPane.js b/plugins/talk-plugin-profile-settings/client/containers/TabPane.js new file mode 100644 index 000000000..6384c7160 --- /dev/null +++ b/plugins/talk-plugin-profile-settings/client/containers/TabPane.js @@ -0,0 +1,26 @@ +import React from 'react'; +import { compose, gql } from 'react-apollo'; +import TabPane from '../components/TabPane'; +import { withFragments } from 'plugin-api/beta/client/hocs'; +import { getSlotFragmentSpreads } from 'plugin-api/beta/client/utils'; + +const slots = ['profileSettings']; + +class TabPaneContainer extends React.Component { + render() { + return ; + } +} + +const enhance = compose( + withFragments({ + root: gql` + fragment TalkProfileSettings_TabPane_root on RootQuery { + __typename + ${getSlotFragmentSpreads(slots, 'root')} + } + `, + }) +); + +export default enhance(TabPaneContainer); diff --git a/plugins/talk-plugin-profile-settings/client/index.js b/plugins/talk-plugin-profile-settings/client/index.js new file mode 100644 index 000000000..00e37a0f1 --- /dev/null +++ b/plugins/talk-plugin-profile-settings/client/index.js @@ -0,0 +1,11 @@ +import Tab from './components/Tab'; +import TabPane from './containers/TabPane'; +import translations from './translations.yml'; + +export default { + slots: { + profileTabs: [Tab], + profileTabPanes: [TabPane], + }, + translations, +}; diff --git a/plugins/talk-plugin-profile-settings/client/translations.yml b/plugins/talk-plugin-profile-settings/client/translations.yml new file mode 100644 index 000000000..cd8edb415 --- /dev/null +++ b/plugins/talk-plugin-profile-settings/client/translations.yml @@ -0,0 +1,27 @@ +en: + talk-plugin-profile-settings: + tab: Settings +de: + talk-plugin-profile-settings: + tab: Einstellungen +es: + talk-plugin-profile-settings: + tab: Configuración +fr: + talk-plugin-profile-settings: + tab: Paramètres +nl_NL: + talk-plugin-profile-settings: + tab: Instellingen +da: + talk-plugin-profile-settings: + tab: Indstillinger +pt_PR: + talk-plugin-profile-settings: + tab: Configurações +zh_TW: + talk-plugin-profile-settings: + tab: 設置 +zh_CN: + talk-plugin-profile-settings: + tab: 设置 diff --git a/plugins/talk-plugin-profile-settings/index.js b/plugins/talk-plugin-profile-settings/index.js new file mode 100644 index 000000000..f053ebf79 --- /dev/null +++ b/plugins/talk-plugin-profile-settings/index.js @@ -0,0 +1 @@ +module.exports = {}; From 233c514251358a59f8749d5913ab7c655d9a42e1 Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Feb 2018 10:37:36 -0500 Subject: [PATCH 22/30] Fix small typo --- client/coral-admin/src/components/AccountHistory.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/client/coral-admin/src/components/AccountHistory.js b/client/coral-admin/src/components/AccountHistory.js index 73add8120..52f3af92d 100644 --- a/client/coral-admin/src/components/AccountHistory.js +++ b/client/coral-admin/src/components/AccountHistory.js @@ -19,11 +19,11 @@ const buildUserHistory = (userState = {}) => { ); }; -/** readaebleDuration returns a readaeble duration of the suspension/ban in hours or days +/** readableDuration returns a readable duration of the suspension/ban in hours or days * @param {} startDate * @param {} endDate */ -const readaebleDuration = (startDate, endDate) => { +const readableDuration = (startDate, endDate) => { const dur = moment.duration(moment(endDate).diff(moment(startDate))); const durAsDays = dur.asDays().toFixed(0); const durAsHours = dur.asHours().toFixed(0); @@ -41,7 +41,7 @@ const buildActionResponse = (typename, created_at, until, status) => { return status ? 'User banned' : 'Ban removed'; case 'SuspensionStatusHistory': return until - ? `Suspended, ${readaebleDuration(created_at, until)}` + ? `Suspended, ${readableDuration(created_at, until)}` : 'Suspension removed'; default: return '-'; From a16640434701c5230aa47e775ac5a43be35c8fcb Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 20 Feb 2018 09:31:08 -0700 Subject: [PATCH 23/30] fix for action count null --- graph/loaders/actions.js | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/graph/loaders/actions.js b/graph/loaders/actions.js index b5ea31a3f..77cc7138d 100644 --- a/graph/loaders/actions.js +++ b/graph/loaders/actions.js @@ -1,6 +1,6 @@ const DataLoader = require('dataloader'); const util = require('./util'); -const { first, get, merge, remove, groupBy, reduce } = require('lodash'); +const { first, get, merge, remove, groupBy, reduce, isNil } = require('lodash'); /** * Gets actions based on their item id's. @@ -35,10 +35,12 @@ const genActionsAuthoredWithID = ( * @param {Object} action_counts the action count object */ const iterateActionCounts = action_counts => - Object.keys(action_counts).map(action_type => ({ - count: action_counts[action_type], - action_type: action_type.toUpperCase(), - })); + !isNil(action_counts) + ? Object.keys(action_counts).map(action_type => ({ + count: action_counts[action_type], + action_type: action_type.toUpperCase(), + })) + : []; /** * getUserActions will get the actions made by the user for this specific From f918bbea20c47ea5f29013758d1edc6f7f5a237e Mon Sep 17 00:00:00 2001 From: Kim Gardner Date: Tue, 20 Feb 2018 11:45:36 -0500 Subject: [PATCH 24/30] Update version to 4.2.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 4b1fd68cf..c61a5db45 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "talk", - "version": "4.2.1", + "version": "4.2.2", "description": "A better commenting experience from Mozilla, The New York Times, and the Washington Post. https://coralproject.net", "main": "app.js", "private": true, From e5f6188bd21fd11022da39f9d3c4f9d22c565eb5 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 20 Feb 2018 21:39:32 -0700 Subject: [PATCH 25/30] replaced custom loading with yarn workspaces --- bin/cli-plugins | 71 +-------- package.json | 4 +- plugins/talk-plugin-akismet/yarn.lock | 141 ------------------ plugins/talk-plugin-comment-content/yarn.lock | 112 -------------- plugins/talk-plugin-facebook-auth/yarn.lock | 34 ----- .../talk-plugin-featured-comments/yarn.lock | 4 - plugins/talk-plugin-permalink/yarn.lock | 4 - plugins/talk-plugin-subscriber/yarn.lock | 11 -- plugins/talk-plugin-toxic-comments/yarn.lock | 7 - yarn.lock | 67 ++++++++- 10 files changed, 71 insertions(+), 384 deletions(-) delete mode 100644 plugins/talk-plugin-akismet/yarn.lock delete mode 100644 plugins/talk-plugin-comment-content/yarn.lock delete mode 100644 plugins/talk-plugin-facebook-auth/yarn.lock delete mode 100644 plugins/talk-plugin-featured-comments/yarn.lock delete mode 100644 plugins/talk-plugin-permalink/yarn.lock delete mode 100644 plugins/talk-plugin-subscriber/yarn.lock delete mode 100644 plugins/talk-plugin-toxic-comments/yarn.lock diff --git a/bin/cli-plugins b/bin/cli-plugins index 883682ede..7b829dbc8 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -135,18 +135,11 @@ function reconcilePackages({ quiet = false, upgradeRemote = false }) { return { local, fetchable, upgradable }; } -async function reconcileRemotePlugins({ skipLocal, dryRun, upgradeRemote }) { - console.log( - `\n[${skipLocal ? '1/2' : '2/3'}] ${emoji.get( - 'mag' - )} Reconciling plugins...`.yellow - ); +async function reconcileRemotePlugins({ dryRun, upgradeRemote }) { + console.log(`\n['1/2'] ${emoji.get('mag')} Reconciling plugins...`.yellow); const { fetchable, upgradable } = reconcilePackages({ upgradeRemote }); - console.log( - `[${skipLocal ? '2/2' : '3/3'}] ${emoji.get('truck')} Fetching plugins...\n` - .yellow - ); + console.log(`['2/2'] ${emoji.get('truck')} Fetching plugins...\n`.yellow); if (fetchable.length > 0) { console.log( @@ -206,70 +199,21 @@ async function reconcileRemotePlugins({ skipLocal, dryRun, upgradeRemote }) { return { upgradable, fetchable }; } -async function reconcileLocalPlugins({ skipRemote, dryRun }) { - console.log( - `\n[${skipRemote ? '1/1' : '1/3'}] ${emoji.get( - 'pick' - )} Installing local plugin dependencies...\n`.yellow - ); - const { local } = reconcilePackages({ quiet: true }); - - for (let i in local) { - let { name } = local[i]; - - if (!fs.existsSync(path.join(dir, 'plugins', name, 'package.json'))) { - continue; - } - - let wd = path.join(dir, 'plugins', name); - - console.log(`$ cd ${wd.cyan} && yarn`); - - if (!dryRun) { - let args = []; - - let output = spawn.sync('yarn', args, { - stdio: ['ignore', 'pipe', 'inherit'], - cwd: wd, - }); - - if (output.status) { - throw new Error( - 'Could not install local plugin dependencies, errors occurred during install' - ); - } - - console.log(output.stdout.toString()); - } - } -} - // This traverses the local plugins and installs any dependencies listed there, // this only is really needed for plugins that are installed via docker because // core plugins will have their dependencies already included in core. -async function reconcilePluginDeps({ - skipLocal, - skipRemote, - dryRun, - upgradeRemote, -}) { +async function reconcilePluginDeps({ skipRemote, dryRun, upgradeRemote }) { try { let startTime = new Date(); // We don't need to do anything if we skip everything.... - if (skipLocal && skipRemote) { + if (skipRemote) { return; } - // Traverse local plugins and install dependencies if enabled. - if (!skipLocal) { - await reconcileLocalPlugins({ skipRemote, dryRun }); - } - // Locate any external plugins and install them. if (!skipRemote) { const results = await reconcileRemotePlugins({ - skipLocal, skipRemote, dryRun, upgradeRemote, @@ -440,15 +384,12 @@ program program .command('reconcile') - .description( - 'reconciles local plugin dependencies and downloads external plugins' - ) + .description('reconciles dependencies by downloading external plugins') .option('-u, --upgrade-remote', 'upgrades remote dependencies') .option( '-d, --dry-run', 'does not actually change anything on the filesystem acts only as a simulation' ) - .option('--skip-local', 'skips the local dependancy reconciliation') .option('--skip-remote', 'skips the remote plugin reconciliation') .action(reconcilePluginDeps); diff --git a/package.json b/package.json index c61a5db45..1a74f1189 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,6 @@ "main": "app.js", "private": true, "scripts": { - "postinstall": "./bin/cli plugins reconcile --skip-remote", "generate-introspection": "WEBPACK=TRUE NODE_ENV=test ./scripts/generateIntrospectionResult.js", "clean": "rm -rf dist client/coral-framework/graphql/introspection.json", "watch": "npm-run-all clean generate-introspection --parallel watch:*", @@ -32,6 +31,9 @@ "minVersion": 1516920160 } }, + "workspaces": [ + "plugins/*" + ], "repository": { "type": "git", "url": "git+https://github.com/coralproject/talk.git" diff --git a/plugins/talk-plugin-akismet/yarn.lock b/plugins/talk-plugin-akismet/yarn.lock deleted file mode 100644 index ac97e590f..000000000 --- a/plugins/talk-plugin-akismet/yarn.lock +++ /dev/null @@ -1,141 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -akismet-api@^4.0.1: - version "4.0.1" - resolved "https://registry.yarnpkg.com/akismet-api/-/akismet-api-4.0.1.tgz#1c771442f09316847132aa16171bb4fb708b6519" - dependencies: - bluebird "^3.1.1" - superagent "^3.8.0" - -asynckit@^0.4.0: - version "0.4.0" - resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79" - -bluebird@^3.1.1: - version "3.5.1" - resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" - -combined-stream@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009" - dependencies: - delayed-stream "~1.0.0" - -component-emitter@^1.2.0: - version "1.2.1" - resolved "https://registry.yarnpkg.com/component-emitter/-/component-emitter-1.2.1.tgz#137918d6d78283f7df7a6b7c5a63e140e69425e6" - -cookiejar@^2.1.0: - version "2.1.1" - resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" - -core-util-is@~1.0.0: - version "1.0.2" - resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7" - -debug@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261" - dependencies: - ms "2.0.0" - -delayed-stream@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" - -extend@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.1.tgz#a755ea7bc1adfcc5a31ce7e762dbaadc5e636444" - -form-data@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf" - dependencies: - asynckit "^0.4.0" - combined-stream "^1.0.5" - mime-types "^2.1.12" - -formidable@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" - -inherits@~2.0.3: - version "2.0.3" - resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de" - -isarray@~1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" - -methods@^1.1.1: - version "1.1.2" - resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" - -mime-db@~1.30.0: - version "1.30.0" - resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01" - -mime-types@^2.1.12: - version "2.1.17" - resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a" - dependencies: - mime-db "~1.30.0" - -mime@^1.4.1: - version "1.6.0" - resolved "https://registry.yarnpkg.com/mime/-/mime-1.6.0.tgz#32cd9e5c64553bd58d19a568af452acff04981b1" - -ms@2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" - -process-nextick-args@~1.0.6: - version "1.0.7" - resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-1.0.7.tgz#150e20b756590ad3f91093f25a4f2ad8bff30ba3" - -qs@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" - -readable-stream@^2.0.5: - version "2.3.3" - resolved "https://registry.yarnpkg.com/readable-stream/-/readable-stream-2.3.3.tgz#368f2512d79f9d46fdfc71349ae7878bbc1eb95c" - dependencies: - core-util-is "~1.0.0" - inherits "~2.0.3" - isarray "~1.0.0" - process-nextick-args "~1.0.6" - safe-buffer "~5.1.1" - string_decoder "~1.0.3" - util-deprecate "~1.0.1" - -safe-buffer@~5.1.0, safe-buffer@~5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" - -string_decoder@~1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.0.3.tgz#0fc67d7c141825de94282dd536bec6b9bce860ab" - dependencies: - safe-buffer "~5.1.0" - -superagent@^3.8.0: - version "3.8.2" - resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" - dependencies: - component-emitter "^1.2.0" - cookiejar "^2.1.0" - debug "^3.1.0" - extend "^3.0.0" - form-data "^2.3.1" - formidable "^1.1.1" - methods "^1.1.1" - mime "^1.4.1" - qs "^6.5.1" - readable-stream "^2.0.5" - -util-deprecate@~1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" diff --git a/plugins/talk-plugin-comment-content/yarn.lock b/plugins/talk-plugin-comment-content/yarn.lock deleted file mode 100644 index 9f312c11d..000000000 --- a/plugins/talk-plugin-comment-content/yarn.lock +++ /dev/null @@ -1,112 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -asap@~2.0.3: - version "2.0.5" - resolved "https://registry.yarnpkg.com/asap/-/asap-2.0.5.tgz#522765b50c3510490e52d7dcfe085ef9ba96958f" - -core-js@^1.0.0: - version "1.2.7" - resolved "https://registry.yarnpkg.com/core-js/-/core-js-1.2.7.tgz#652294c14651db28fa93bd2d5ff2983a4f08c636" - -encoding@^0.1.11: - version "0.1.12" - resolved "https://registry.yarnpkg.com/encoding/-/encoding-0.1.12.tgz#538b66f3ee62cd1ab51ec323829d1f9480c74beb" - dependencies: - iconv-lite "~0.4.13" - -fbjs@^0.8.9: - version "0.8.12" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.12.tgz#10b5d92f76d45575fd63a217d4ea02bea2f8ed04" - dependencies: - core-js "^1.0.0" - isomorphic-fetch "^2.1.1" - loose-envify "^1.0.0" - object-assign "^4.1.0" - promise "^7.1.1" - setimmediate "^1.0.5" - ua-parser-js "^0.7.9" - -iconv-lite@~0.4.13: - version "0.4.18" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.18.tgz#23d8656b16aae6742ac29732ea8f0336a4789cf2" - -is-stream@^1.0.1: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-1.1.0.tgz#12d4a3dd4e68e0b79ceb8dbc84173ae80d91ca44" - -isomorphic-fetch@^2.1.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/isomorphic-fetch/-/isomorphic-fetch-2.2.1.tgz#611ae1acf14f5e81f729507472819fe9733558a9" - dependencies: - node-fetch "^1.0.1" - whatwg-fetch ">=0.10.0" - -js-tokens@^3.0.0: - version "3.0.1" - resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-3.0.1.tgz#08e9f132484a2c45a30907e9dc4d5567b7f114d7" - -linkify-it@^1.2.0: - version "1.2.4" - resolved "https://registry.yarnpkg.com/linkify-it/-/linkify-it-1.2.4.tgz#0773526c317c8fd13bd534ee1d180ff88abf881a" - dependencies: - uc.micro "^1.0.1" - -loose-envify@^1.0.0, loose-envify@^1.3.1: - version "1.3.1" - resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.3.1.tgz#d1a8ad33fa9ce0e713d65fdd0ac8b748d478c848" - dependencies: - js-tokens "^3.0.0" - -node-fetch@^1.0.1: - version "1.7.1" - resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-1.7.1.tgz#899cb3d0a3c92f952c47f1b876f4c8aeabd400d5" - dependencies: - encoding "^0.1.11" - is-stream "^1.0.1" - -object-assign@^4.1.0: - version "4.1.1" - resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" - -promise@^7.1.1: - version "7.1.1" - resolved "https://registry.yarnpkg.com/promise/-/promise-7.1.1.tgz#489654c692616b8aa55b0724fa809bb7db49c5bf" - dependencies: - asap "~2.0.3" - -prop-types@^15.5.8: - version "15.5.10" - resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.5.10.tgz#2797dfc3126182e3a95e3dfbb2e893ddd7456154" - dependencies: - fbjs "^0.8.9" - loose-envify "^1.3.1" - -react-linkify@^0.2.1: - version "0.2.1" - resolved "https://registry.yarnpkg.com/react-linkify/-/react-linkify-0.2.1.tgz#b28d3f9544539a622fec8d42b4800eb9d23bf981" - dependencies: - linkify-it "^1.2.0" - prop-types "^15.5.8" - tlds "^1.57.0" - -setimmediate@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/setimmediate/-/setimmediate-1.0.5.tgz#290cbb232e306942d7d7ea9b83732ab7856f8285" - -tlds@^1.57.0: - version "1.189.0" - resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.189.0.tgz#b8cb46ea76dc2f4a01d45b8d907bf19a66e9f729" - -ua-parser-js@^0.7.9: - version "0.7.12" - resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-0.7.12.tgz#04c81a99bdd5dc52263ea29d24c6bf8d4818a4bb" - -uc.micro@^1.0.1: - version "1.0.3" - resolved "https://registry.yarnpkg.com/uc.micro/-/uc.micro-1.0.3.tgz#7ed50d5e0f9a9fb0a573379259f2a77458d50192" - -whatwg-fetch@>=0.10.0: - version "2.0.3" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-2.0.3.tgz#9c84ec2dcf68187ff00bc64e1274b442176e1c84" diff --git a/plugins/talk-plugin-facebook-auth/yarn.lock b/plugins/talk-plugin-facebook-auth/yarn.lock deleted file mode 100644 index a2886dfbe..000000000 --- a/plugins/talk-plugin-facebook-auth/yarn.lock +++ /dev/null @@ -1,34 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -oauth@0.9.x: - version "0.9.15" - resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" - -passport-facebook@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/passport-facebook/-/passport-facebook-2.1.1.tgz#c39d0b52ae4d59163245a4e21a7b9b6321303311" - dependencies: - passport-oauth2 "1.x.x" - -passport-oauth2@1.x.x: - version "1.4.0" - resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad" - dependencies: - oauth "0.9.x" - passport-strategy "1.x.x" - uid2 "0.0.x" - utils-merge "1.x.x" - -passport-strategy@1.x.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" - -uid2@0.0.x: - version "0.0.3" - resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" - -utils-merge@1.x.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.0.tgz#0294fb922bb9375153541c4f7096231f287c8af8" diff --git a/plugins/talk-plugin-featured-comments/yarn.lock b/plugins/talk-plugin-featured-comments/yarn.lock deleted file mode 100644 index fb57ccd13..000000000 --- a/plugins/talk-plugin-featured-comments/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - diff --git a/plugins/talk-plugin-permalink/yarn.lock b/plugins/talk-plugin-permalink/yarn.lock deleted file mode 100644 index fb57ccd13..000000000 --- a/plugins/talk-plugin-permalink/yarn.lock +++ /dev/null @@ -1,4 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - diff --git a/plugins/talk-plugin-subscriber/yarn.lock b/plugins/talk-plugin-subscriber/yarn.lock deleted file mode 100644 index 60fdd97df..000000000 --- a/plugins/talk-plugin-subscriber/yarn.lock +++ /dev/null @@ -1,11 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -moment@^2.18.1: - version "2.18.1" - resolved "https://registry.yarnpkg.com/moment/-/moment-2.18.1.tgz#c36193dd3ce1c2eed2adb7c802dbbc77a81b1c0f" - -momentjs@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/momentjs/-/momentjs-2.0.0.tgz#73df904b4fa418f6e3c605e831cef6ed5518ebd4" diff --git a/plugins/talk-plugin-toxic-comments/yarn.lock b/plugins/talk-plugin-toxic-comments/yarn.lock deleted file mode 100644 index f73f43463..000000000 --- a/plugins/talk-plugin-toxic-comments/yarn.lock +++ /dev/null @@ -1,7 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -ms@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" diff --git a/yarn.lock b/yarn.lock index bc59a3be3..89ef658db 100644 --- a/yarn.lock +++ b/yarn.lock @@ -200,6 +200,13 @@ ajv@^5.1.0, ajv@^5.1.5, ajv@^5.2.3, ajv@^5.3.0: fast-json-stable-stringify "^2.0.0" json-schema-traverse "^0.3.0" +akismet-api@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/akismet-api/-/akismet-api-4.0.1.tgz#1c771442f09316847132aa16171bb4fb708b6519" + dependencies: + bluebird "^3.1.1" + superagent "^3.8.0" + align-text@^0.1.1, align-text@^0.1.3: version "0.1.4" resolved "https://registry.yarnpkg.com/align-text/-/align-text-0.1.4.tgz#0cd90a561093f35d0a99256c22b7069433fad117" @@ -1237,7 +1244,7 @@ bluebird@3.5.0: version "3.5.0" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.0.tgz#791420d7f551eea2897453a8a77653f96606d67c" -bluebird@^3.0.6, bluebird@^3.3.4, bluebird@^3.4.6, bluebird@^3.5.0: +bluebird@^3.0.6, bluebird@^3.1.1, bluebird@^3.3.4, bluebird@^3.4.6, bluebird@^3.5.0: version "3.5.1" resolved "https://registry.yarnpkg.com/bluebird/-/bluebird-3.5.1.tgz#d9551f9de98f1fcda1e683d17ee91a0602ee2eb9" @@ -2031,6 +2038,10 @@ cookiejar@2.0.x, cookiejar@^2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.0.6.tgz#0abf356ad00d1c5a219d88d44518046dd026acfe" +cookiejar@^2.1.0: + version "2.1.1" + resolved "https://registry.yarnpkg.com/cookiejar/-/cookiejar-2.1.1.tgz#41ad57b1b555951ec171412a81942b1e8200d34a" + copy-concurrently@^1.0.0: version "1.0.5" resolved "https://registry.yarnpkg.com/copy-concurrently/-/copy-concurrently-1.0.5.tgz#92297398cae34937fcafd6ec8139c18051f0b5e0" @@ -3397,7 +3408,7 @@ formatio@1.2.0, formatio@^1.2.0: dependencies: samsam "1.x" -formidable@^1.0.17: +formidable@^1.0.17, formidable@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/formidable/-/formidable-1.1.1.tgz#96b8886f7c3c3508b932d6bd70c4d3a88f35f1a9" @@ -6398,6 +6409,10 @@ oauth-sign@~0.8.1, oauth-sign@~0.8.2: version "0.8.2" resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43" +oauth@0.9.x: + version "0.9.15" + resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" + object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" @@ -6665,6 +6680,12 @@ parseurl@~1.3.2: version "1.3.2" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.2.tgz#fc289d4ed8993119460c156253262cdc8de65bf3" +passport-facebook@^2.1.1: + version "2.1.1" + resolved "https://registry.yarnpkg.com/passport-facebook/-/passport-facebook-2.1.1.tgz#c39d0b52ae4d59163245a4e21a7b9b6321303311" + dependencies: + passport-oauth2 "1.x.x" + passport-jwt@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-3.0.1.tgz#e4f7276dad8bd251d43c6fc38883130b963272f6" @@ -6678,6 +6699,15 @@ passport-local@^1.0.0: dependencies: passport-strategy "1.x.x" +passport-oauth2@1.x.x: + version "1.4.0" + resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad" + dependencies: + oauth "0.9.x" + passport-strategy "1.x.x" + uid2 "0.0.x" + utils-merge "1.x.x" + passport-strategy@1.x.x, passport-strategy@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" @@ -7555,7 +7585,7 @@ q@^1.1.2: version "1.5.0" resolved "https://registry.yarnpkg.com/q/-/q-1.5.0.tgz#dd01bac9d06d30e6f219aecb8253ee9ebdc308f1" -qs@6.5.1, qs@^6.1.0, qs@^6.2.0, qs@~6.5.1: +qs@6.5.1, qs@^6.1.0, qs@^6.2.0, qs@^6.5.1, qs@~6.5.1: version "6.5.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8" @@ -7699,6 +7729,14 @@ react-input-autosize@^1.1.4: create-react-class "^15.5.2" prop-types "^15.5.8" +react-linkify@^0.2.1: + version "0.2.2" + resolved "https://registry.yarnpkg.com/react-linkify/-/react-linkify-0.2.2.tgz#55b99b1cc7244446a0f9bdebbe13b2c30f789e65" + dependencies: + linkify-it "^2.0.3" + prop-types "^15.5.8" + tlds "^1.57.0" + react-mdl-selectfield@^0.2.0: version "0.2.0" resolved "https://registry.yarnpkg.com/react-mdl-selectfield/-/react-mdl-selectfield-0.2.0.tgz#36e1a97233036c057ab2bdb31ec09ad8d9988411" @@ -8880,6 +8918,21 @@ superagent@^2.0.0: qs "^6.1.0" readable-stream "^2.0.5" +superagent@^3.8.0: + version "3.8.2" + resolved "https://registry.yarnpkg.com/superagent/-/superagent-3.8.2.tgz#e4a11b9d047f7d3efeb3bbe536d9ec0021d16403" + dependencies: + component-emitter "^1.2.0" + cookiejar "^2.1.0" + debug "^3.1.0" + extend "^3.0.0" + form-data "^2.3.1" + formidable "^1.1.1" + methods "^1.1.1" + mime "^1.4.1" + qs "^6.5.1" + readable-stream "^2.0.5" + supports-color@3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-3.1.2.tgz#72a262894d9d408b956ca05ff37b2ed8a6e2a2d5" @@ -9069,7 +9122,7 @@ title-case-minors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/title-case-minors/-/title-case-minors-1.0.0.tgz#51f17037c294747a1d1cda424b5004c86d8eb115" -tlds@^1.196.0: +tlds@^1.196.0, tlds@^1.57.0: version "1.199.0" resolved "https://registry.yarnpkg.com/tlds/-/tlds-1.199.0.tgz#a4fc8c3058216488a80aaaebb427925007e55217" @@ -9270,6 +9323,10 @@ uid-number@^0.0.6: version "0.0.6" resolved "https://registry.yarnpkg.com/uid-number/-/uid-number-0.0.6.tgz#0ea10e8035e8eb5b8e4449f06da1c730663baa81" +uid2@0.0.x: + version "0.0.3" + resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" + ultron@~1.1.0: version "1.1.0" resolved "https://registry.yarnpkg.com/ultron/-/ultron-1.1.0.tgz#b07a2e6a541a815fc6a34ccd4533baec307ca864" @@ -9399,7 +9456,7 @@ util@0.10.3, "util@>=0.10.3 <1", util@^0.10.3: dependencies: inherits "2.0.1" -utils-merge@1.0.1: +utils-merge@1.0.1, utils-merge@1.x.x: version "1.0.1" resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" From 85f3598e99f059080b82caf3c57bbc5bb96b3b1c Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Tue, 20 Feb 2018 21:45:35 -0700 Subject: [PATCH 26/30] added advisory excemption --- .nsprc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.nsprc b/.nsprc index 583560bdd..da6cb9865 100644 --- a/.nsprc +++ b/.nsprc @@ -1,6 +1,7 @@ { "exceptions": [ "https://nodesecurity.io/advisories/531", - "https://nodesecurity.io/advisories/532" + "https://nodesecurity.io/advisories/532", + "https://nodesecurity.io/advisories/566" ] } From 7dd63dc81242e665586bbcf5cce9607a2405c83d Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 22 Feb 2018 10:38:18 +0100 Subject: [PATCH 27/30] Fix CI --- .../tabs/profile/components/CommentHistory.js | 2 +- plugins.json.tmp | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 plugins.json.tmp diff --git a/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js b/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js index f16e8a119..39f3bfd04 100644 --- a/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js +++ b/client/coral-embed-stream/src/tabs/profile/components/CommentHistory.js @@ -23,7 +23,7 @@ class CommentHistory extends React.Component { render() { const { navigate, comments, data, root } = this.props; return ( -
+
{comments.nodes.map((comment, i) => { return ( diff --git a/plugins.json.tmp b/plugins.json.tmp new file mode 100644 index 000000000..f8fa64268 --- /dev/null +++ b/plugins.json.tmp @@ -0,0 +1,33 @@ +{ + "server": [ + "talk-plugin-auth", + "talk-plugin-featured-comments", + "talk-plugin-offtopic", + "talk-plugin-respect", + "talk-plugin-toxic-comments", + "talk-plugin-notifications", + "talk-plugin-notifications-category-reply" + ], + "client": [ + "talk-plugin-auth", + "talk-plugin-author-menu", + "talk-plugin-comment-content", + "talk-plugin-featured-comments", + "talk-plugin-flag-details", + "talk-plugin-member-since", + "talk-plugin-moderation-actions", + "talk-plugin-offtopic", + "talk-plugin-permalink", + "talk-plugin-respect", + "talk-plugin-sort-most-replied", + "talk-plugin-sort-most-respected", + "talk-plugin-sort-newest", + "talk-plugin-sort-oldest", + "talk-plugin-viewing-options", + "talk-plugin-toxic-comments", + "talk-plugin-profile-settings", + "talk-plugin-notifications", + "talk-plugin-notifications-category-reply", + "talk-plugin-ignore-user" + ] +} From 4d943629d97bcfacefa521f4ad61764af7f514d1 Mon Sep 17 00:00:00 2001 From: Chi Vinh Le Date: Thu, 22 Feb 2018 10:47:13 +0100 Subject: [PATCH 28/30] Rm accident added file --- plugins.json.tmp | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 plugins.json.tmp diff --git a/plugins.json.tmp b/plugins.json.tmp deleted file mode 100644 index f8fa64268..000000000 --- a/plugins.json.tmp +++ /dev/null @@ -1,33 +0,0 @@ -{ - "server": [ - "talk-plugin-auth", - "talk-plugin-featured-comments", - "talk-plugin-offtopic", - "talk-plugin-respect", - "talk-plugin-toxic-comments", - "talk-plugin-notifications", - "talk-plugin-notifications-category-reply" - ], - "client": [ - "talk-plugin-auth", - "talk-plugin-author-menu", - "talk-plugin-comment-content", - "talk-plugin-featured-comments", - "talk-plugin-flag-details", - "talk-plugin-member-since", - "talk-plugin-moderation-actions", - "talk-plugin-offtopic", - "talk-plugin-permalink", - "talk-plugin-respect", - "talk-plugin-sort-most-replied", - "talk-plugin-sort-most-respected", - "talk-plugin-sort-newest", - "talk-plugin-sort-oldest", - "talk-plugin-viewing-options", - "talk-plugin-toxic-comments", - "talk-plugin-profile-settings", - "talk-plugin-notifications", - "talk-plugin-notifications-category-reply", - "talk-plugin-ignore-user" - ] -} From 5bba2ea0ae75560b13c67e40feb64adee56659b3 Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 22 Feb 2018 14:27:14 -0700 Subject: [PATCH 29/30] updated plugin reconciler, replaced docker commands --- Dockerfile | 1 - bin/cli-plugins | 59 +++++++++++++++++++++---------------------------- 2 files changed, 25 insertions(+), 35 deletions(-) diff --git a/Dockerfile b/Dockerfile index 95aa74d8a..4d0fea440 100644 --- a/Dockerfile +++ b/Dockerfile @@ -18,7 +18,6 @@ ENV NODE_ENV production # Install app dependencies and build static assets. RUN yarn global add node-gyp && \ yarn install --frozen-lockfile && \ - cli plugins reconcile && \ yarn build && \ yarn cache clean diff --git a/bin/cli-plugins b/bin/cli-plugins index 7b829dbc8..ee331bff2 100755 --- a/bin/cli-plugins +++ b/bin/cli-plugins @@ -202,46 +202,38 @@ async function reconcileRemotePlugins({ dryRun, upgradeRemote }) { // This traverses the local plugins and installs any dependencies listed there, // this only is really needed for plugins that are installed via docker because // core plugins will have their dependencies already included in core. -async function reconcilePluginDeps({ skipRemote, dryRun, upgradeRemote }) { +async function reconcilePluginDeps({ dryRun, upgradeRemote }) { try { let startTime = new Date(); - // We don't need to do anything if we skip everything.... - if (skipRemote) { - return; - } - // Locate any external plugins and install them. - if (!skipRemote) { - const results = await reconcileRemotePlugins({ - skipRemote, - dryRun, - upgradeRemote, - }); + const results = await reconcileRemotePlugins({ + dryRun, + upgradeRemote, + }); - let status; - if (dryRun) { - status = '[dry-run] success'.green; - } else { - status = 'success'.green; - } - - let message; - if (results.upgradable.length === 0 && results.fetchable.length === 0) { - message = 'Already up-to-date.'; - } else if (results.upgradable.length === 0) { - message = `Fetched ${results.fetchable.length} new plugins.`; - } else if (results.fetchable.length === 0) { - message = `Upgraded ${results.upgradable.length} new plugins.`; - } else { - message = `Fetched ${results.fetchable.length} new plugins, upgraded ${ - results.upgradable.length - } plugins.`; - } - - console.log(`\n${status} ${message}`); + let status; + if (dryRun) { + status = '[dry-run] success'.green; + } else { + status = 'success'.green; } + let message; + if (results.upgradable.length === 0 && results.fetchable.length === 0) { + message = 'Already up-to-date.'; + } else if (results.upgradable.length === 0) { + message = `Fetched ${results.fetchable.length} new plugins.`; + } else if (results.fetchable.length === 0) { + message = `Upgraded ${results.upgradable.length} new plugins.`; + } else { + message = `Fetched ${results.fetchable.length} new plugins, upgraded ${ + results.upgradable.length + } plugins.`; + } + + console.log(`\n${status} ${message}`); + let endTime = new Date(); let totalTime = ((endTime.getTime() - startTime.getTime()) / 1000).toFixed( @@ -390,7 +382,6 @@ program '-d, --dry-run', 'does not actually change anything on the filesystem acts only as a simulation' ) - .option('--skip-remote', 'skips the remote plugin reconciliation') .action(reconcilePluginDeps); program.parse(process.argv); From 2485386b4568668cd959a4d7e78e4f51bbb4b37b Mon Sep 17 00:00:00 2001 From: Wyatt Johnson Date: Thu, 22 Feb 2018 14:29:56 -0700 Subject: [PATCH 30/30] moved lock file --- plugins/talk-plugin-google-auth/yarn.lock | 34 ----------------------- yarn.lock | 8 +++++- 2 files changed, 7 insertions(+), 35 deletions(-) delete mode 100644 plugins/talk-plugin-google-auth/yarn.lock diff --git a/plugins/talk-plugin-google-auth/yarn.lock b/plugins/talk-plugin-google-auth/yarn.lock deleted file mode 100644 index e1f5c4202..000000000 --- a/plugins/talk-plugin-google-auth/yarn.lock +++ /dev/null @@ -1,34 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -oauth@0.9.x: - version "0.9.15" - resolved "https://registry.yarnpkg.com/oauth/-/oauth-0.9.15.tgz#bd1fefaf686c96b75475aed5196412ff60cfb9c1" - -passport-google-oauth2@^0.1.6: - version "0.1.6" - resolved "https://registry.yarnpkg.com/passport-google-oauth2/-/passport-google-oauth2-0.1.6.tgz#dfd7016ac7449fe27cfeb252ae974afc23257a0d" - dependencies: - passport-oauth2 "^1.1.2" - -passport-oauth2@^1.1.2: - version "1.4.0" - resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad" - dependencies: - oauth "0.9.x" - passport-strategy "1.x.x" - uid2 "0.0.x" - utils-merge "1.x.x" - -passport-strategy@1.x.x: - version "1.0.0" - resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4" - -uid2@0.0.x: - version "0.0.3" - resolved "https://registry.yarnpkg.com/uid2/-/uid2-0.0.3.tgz#483126e11774df2f71b8b639dcd799c376162b82" - -utils-merge@1.x.x: - version "1.0.1" - resolved "https://registry.yarnpkg.com/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" diff --git a/yarn.lock b/yarn.lock index 89ef658db..95ad1ad47 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6686,6 +6686,12 @@ passport-facebook@^2.1.1: dependencies: passport-oauth2 "1.x.x" +passport-google-oauth2@^0.1.6: + version "0.1.6" + resolved "https://registry.yarnpkg.com/passport-google-oauth2/-/passport-google-oauth2-0.1.6.tgz#dfd7016ac7449fe27cfeb252ae974afc23257a0d" + dependencies: + passport-oauth2 "^1.1.2" + passport-jwt@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/passport-jwt/-/passport-jwt-3.0.1.tgz#e4f7276dad8bd251d43c6fc38883130b963272f6" @@ -6699,7 +6705,7 @@ passport-local@^1.0.0: dependencies: passport-strategy "1.x.x" -passport-oauth2@1.x.x: +passport-oauth2@1.x.x, passport-oauth2@^1.1.2: version "1.4.0" resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.4.0.tgz#f62f81583cbe12609be7ce6f160b9395a27b86ad" dependencies: