From 873da1cea94f87efaa87d38730d5502a4ed3047b Mon Sep 17 00:00:00 2001 From: Benjamin Goering Date: Mon, 10 Apr 2017 14:40:25 -0700 Subject: [PATCH] Embed doesn't show comments from ignored users --- .../coral-framework/graphql/queries/index.js | 13 +-- .../graphql/queries/loadMore.graphql | 4 +- .../graphql/queries/streamQuery.graphql | 6 +- graph/loaders/comments.js | 16 +++- graph/mutators/user.js | 1 - graph/resolvers/asset.js | 5 +- graph/resolvers/comment.js | 6 +- graph/resolvers/root_query.js | 6 +- graph/typeDefs.graphql | 7 +- test/graph/queries/asset.js | 93 +++++++++++++++++++ 10 files changed, 135 insertions(+), 22 deletions(-) create mode 100644 test/graph/queries/asset.js diff --git a/client/coral-framework/graphql/queries/index.js b/client/coral-framework/graphql/queries/index.js index 5bbe58f7a..a1f8aecf0 100644 --- a/client/coral-framework/graphql/queries/index.js +++ b/client/coral-framework/graphql/queries/index.js @@ -29,10 +29,10 @@ export const getCounts = (data) => ({asset_id, limit, sort}) => { variables: { asset_id, limit, - sort + sort, + notIgnoredBy: data.variables.notIgnoredBy, }, updateQuery: (oldData, {fetchMoreResult:{asset}}) => { - return { ...oldData, asset: { @@ -53,7 +53,8 @@ export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, s cursor, // the date of the first/last comment depending on the sort order parent_id, // if null, we're loading more top-level comments, if not, we're loading more replies to a comment asset_id, // the id of the asset we're currently on - sort // CHRONOLOGICAL or REVERSE_CHRONOLOGICAL + sort, // CHRONOLOGICAL or REVERSE_CHRONOLOGICAL + notIgnoredBy: data.variables.notIgnoredBy, }, updateQuery: (oldData, {fetchMoreResult:{new_top_level_comments}}) => { let updatedAsset; @@ -122,18 +123,18 @@ export const loadMore = (data) => ({limit, cursor, parent_id = null, asset_id, s // load the comment stream. export const queryStream = graphql(STREAM_QUERY, { - options: () => { + options: ({auth} = {}) => { // where the query string is from the embeded iframe url let comment_id = getQueryVariable('comment_id'); let has_comment = comment_id != null; - return { variables: { asset_id: getQueryVariable('asset_id'), asset_url: getQueryVariable('asset_url'), comment_id: has_comment ? comment_id : 'no-comment', - has_comment + has_comment, + notIgnoredBy: (auth && auth.user) ? auth.user.id : undefined, } }; }, diff --git a/client/coral-framework/graphql/queries/loadMore.graphql b/client/coral-framework/graphql/queries/loadMore.graphql index 10b3c2e34..6b15117da 100644 --- a/client/coral-framework/graphql/queries/loadMore.graphql +++ b/client/coral-framework/graphql/queries/loadMore.graphql @@ -1,7 +1,7 @@ #import "../fragments/commentView.graphql" -query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER) { - new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort}) { +query LoadMoreComments($limit: Int = 5, $cursor: Date, $parent_id: ID, $asset_id: ID, $sort: SORT_ORDER, $notIgnoredBy: String) { + new_top_level_comments: comments(query: {limit: $limit, cursor: $cursor, parent_id: $parent_id, asset_id: $asset_id, sort: $sort, notIgnoredBy: $notIgnoredBy}) { ...commentView replyCount replies(limit: 3) { diff --git a/client/coral-framework/graphql/queries/streamQuery.graphql b/client/coral-framework/graphql/queries/streamQuery.graphql index 102c656e5..a5e3982c6 100644 --- a/client/coral-framework/graphql/queries/streamQuery.graphql +++ b/client/coral-framework/graphql/queries/streamQuery.graphql @@ -1,6 +1,6 @@ #import "../fragments/commentView.graphql" -query AssetQuery($asset_id: ID, $asset_url: String, $comment_id: ID!, $has_comment: Boolean!) { +query AssetQuery($asset_id: ID, $asset_url: String, $comment_id: ID!, $has_comment: Boolean!, $notIgnoredBy: String) { # the comment here is for loading one comment and it's children, probably after following a permalink # $has_comment is derived from the comment_id query param in the iframe url, # which is in turn pulled from the host page url @@ -38,10 +38,10 @@ query AssetQuery($asset_id: ID, $asset_url: String, $comment_id: ID!, $has_comme } commentCount totalCommentCount - comments(limit: 10) { + comments(limit: 10, notIgnoredBy: $notIgnoredBy) { ...commentView replyCount - replies(limit: 3) { + replies(limit: 3, notIgnoredBy: $notIgnoredBy) { ...commentView } } diff --git a/graph/loaders/comments.js b/graph/loaders/comments.js index e2ae29f65..684821345 100644 --- a/graph/loaders/comments.js +++ b/graph/loaders/comments.js @@ -6,6 +6,7 @@ const { const DataLoader = require('dataloader'); const CommentModel = require('../../models/comment'); +const UsersService = require('../../services/users'); /** * Returns the comment count for all comments that are public based on their @@ -142,7 +143,7 @@ const getCommentCountByQuery = (context, {ids, statuses, asset_id, parent_id}) = * @param {Object} context graph context * @param {Object} query query terms to apply to the comments query */ -const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sort}) => { +const getCommentsByQuery = async ({user}, {ids, statuses, asset_id, parent_id, author_id, limit, cursor, sort, notIgnoredBy}) => { let comments = CommentModel.find(); // Only administrators can search for comments with statuses that are not @@ -184,6 +185,19 @@ const getCommentsByQuery = ({user}, {ids, statuses, asset_id, parent_id, author_ comments = comments.where({parent_id}); } + if (notIgnoredBy) { + if (user.id !== notIgnoredBy) { + throw new Error(`You are not authorized to query for comments notIgnoredBy ${notIgnoredBy}`); + } + + // load afresh, as `user` may be from cache and not have recent ignores + const freshUser = await UsersService.findById(user.id); + const ignoredUsers = freshUser.ignoresUsers; + comments = comments.where({ + author_id: {$nin: ignoredUsers} + }); + } + if (cursor) { if (sort === 'REVERSE_CHRONOLOGICAL') { comments = comments.where({ diff --git a/graph/mutators/user.js b/graph/mutators/user.js index fd888a402..d68351701 100644 --- a/graph/mutators/user.js +++ b/graph/mutators/user.js @@ -18,7 +18,6 @@ const ignoreUser = async ({user}, userToIgnore) => { }; const stopIgnoringUser = async ({user}, userToStopIgnoring) => { - console.log('stopIgnoringUser!!'); return await UsersService.stopIgnoringUsers(user.id, [userToStopIgnoring.id]); }; diff --git a/graph/resolvers/asset.js b/graph/resolvers/asset.js index c2a0a2b66..6459f53cd 100644 --- a/graph/resolvers/asset.js +++ b/graph/resolvers/asset.js @@ -2,12 +2,13 @@ const Asset = { recentComments({id}, _, {loaders: {Comments}}) { return Comments.genRecentComments.load(id); }, - comments({id}, {sort, limit}, {loaders: {Comments}}) { + comments({id}, {sort, limit, notIgnoredBy}, {loaders: {Comments}}) { return Comments.getByQuery({ asset_id: id, sort, limit, - parent_id: null + parent_id: null, + notIgnoredBy, }); }, commentCount({id, commentCount}, _, {loaders: {Comments}}) { diff --git a/graph/resolvers/comment.js b/graph/resolvers/comment.js index 2752ad0e3..b99aeeee5 100644 --- a/graph/resolvers/comment.js +++ b/graph/resolvers/comment.js @@ -12,12 +12,14 @@ const Comment = { recentReplies({id}, _, {loaders: {Comments}}) { return Comments.genRecentReplies.load(id); }, - replies({id, asset_id}, {sort, limit}, {loaders: {Comments}}) { + replies({id, asset_id}, {sort, limit, notIgnoredBy}, {loaders: {Comments}}) { + console.log('replies notIgnoredBy', notIgnoredBy); return Comments.getByQuery({ asset_id, parent_id: id, sort, - limit + limit, + notIgnoredBy, }); }, replyCount({id}, _, {loaders: {Comments}}) { diff --git a/graph/resolvers/root_query.js b/graph/resolvers/root_query.js index 42f5a021d..530ee74d6 100644 --- a/graph/resolvers/root_query.js +++ b/graph/resolvers/root_query.js @@ -19,15 +19,15 @@ const RootQuery = { // This endpoint is used for loading moderation queues, so hide it in the // event that we aren't an admin. - comments(_, {query: {action_type, statuses, asset_id, parent_id, limit, cursor, sort}}, {user, loaders: {Comments, Actions}}) { - let query = {statuses, asset_id, parent_id, limit, cursor, sort}; + comments(_, {query: {action_type, statuses, asset_id, parent_id, limit, cursor, sort, notIgnoredBy}}, {user, loaders: {Comments, Actions}}) { + let query = {statuses, asset_id, parent_id, limit, cursor, sort, notIgnoredBy}; if (user != null && user.hasRoles('ADMIN') && action_type) { return Actions.getByTypes({action_type, item_type: 'COMMENTS'}) .then((ids) => { // Perform the query using the available resolver. - return Comments.getByQuery({ids, statuses, asset_id, parent_id, limit, cursor, sort}); + return Comments.getByQuery({ids, statuses, asset_id, parent_id, limit, cursor, sort, notIgnoredBy}); }); } diff --git a/graph/typeDefs.graphql b/graph/typeDefs.graphql index 87926a5da..6bc68c599 100644 --- a/graph/typeDefs.graphql +++ b/graph/typeDefs.graphql @@ -140,6 +140,9 @@ input CommentsQuery { # Sort the results by created_at. sort: SORT_ORDER = REVERSE_CHRONOLOGICAL + + # Exclude comments ignored by this user ID + notIgnoredBy: String } # CommentCountQuery allows the ability to query comment counts by specific @@ -185,7 +188,7 @@ type Comment { recentReplies: [Comment] # the replies that were made to the comment. - replies(sort: SORT_ORDER = CHRONOLOGICAL, limit: Int = 3): [Comment] + replies(sort: SORT_ORDER = CHRONOLOGICAL, limit: Int = 3, notIgnoredBy: String): [Comment] # The count of replies on a comment. replyCount: Int @@ -417,7 +420,7 @@ type Asset { recentComments: [Comment] # The top level comments that are attached to the asset. - comments(sort: SORT_ORDER = REVERSE_CHRONOLOGICAL, limit: Int = 10): [Comment] + comments(sort: SORT_ORDER = REVERSE_CHRONOLOGICAL, limit: Int = 10, notIgnoredBy: String): [Comment] # The count of top level comments on the asset. commentCount: Int diff --git a/test/graph/queries/asset.js b/test/graph/queries/asset.js new file mode 100644 index 000000000..5ee19697a --- /dev/null +++ b/test/graph/queries/asset.js @@ -0,0 +1,93 @@ +const expect = require('chai').expect; +const {graphql} = require('graphql'); + +const schema = require('../../../graph/schema'); +const Context = require('../../../graph/context'); +const UsersService = require('../../../services/users'); +const SettingsService = require('../../../services/settings'); +const Asset = require('../../../models/asset'); +const CommentsService = require('../../../services/comments'); + +describe('graph.queries.asset', () => { + beforeEach(async () => { + await SettingsService.init(); + }); + + it('can get comments edge', async () => { + const assetId = 'fakeAssetId'; + const assetUrl = 'https://bengo.is'; + await Asset.create({id: assetId, url: assetUrl}); + + const user = await UsersService.createLocalUser('usernameA@example.com', 'password', 'usernameA'); + const context = new Context({user}); + + await CommentsService.publicCreate([1, 2].map(() => ({ + author_id: user.id, + asset_id: assetId, + body: `hello there! ${ String(Math.random()).slice(2)}`, + }))); + + const assetCommentsQuery = ` + query assetCommentsQuery($assetId: ID!, $assetUrl: String!) { + asset(id: $assetId, url: $assetUrl) { + comments(limit: 10) { + id, + body, + } + } + } + `; + const assetCommentsResponse = await graphql(schema, assetCommentsQuery, {}, context, {assetId, assetUrl}); + const comments = assetCommentsResponse.data.asset.comments; + expect(comments.length).to.equal(2); + }); + + it('can query comments edge to exclude comments ignored by user', async () => { + const assetId = 'fakeAssetId1'; + const assetUrl = 'https://bengo.is/1'; + await Asset.create({id: assetId, url: assetUrl}); + + const userA = await UsersService.createLocalUser('usernameA@example.com', 'password', 'usernameA'); + const userB = await UsersService.createLocalUser('usernameB@example.com', 'password', 'usernameB'); + const userC = await UsersService.createLocalUser('usernameC@example.com', 'password', 'usernameC'); + const context = new Context({user: userA}); + + // create 2 comments each for userB, userC + await Promise.all([userB, userC].map(user => CommentsService.publicCreate([1, 2].map(() => ({ + author_id: user.id, + asset_id: assetId, + body: `hello there! ${ String(Math.random()).slice(2)}`, + }))))); + + // ignore userB + const ignoreUserMutation = ` + mutation ignoreUser ($id: ID!) { + ignoreUser(id:$id) { + errors { + translation_key + } + } + } + `; + const ignoreUserResponse = await graphql(schema, ignoreUserMutation, {}, context, {id: userB.id}); + if (ignoreUserResponse.errors && ignoreUserResponse.errors.length) { + console.error(ignoreUserResponse.errors); + } + expect(ignoreUserResponse.errors).to.be.empty; + + const assetCommentsWithoutIgnoredQuery = ` + query assetCommentsQuery($assetId: ID!, $assetUrl: String!, $notIgnoredBy: String!) { + asset(id: $assetId, url: $assetUrl) { + comments(limit: 10, notIgnoredBy: $notIgnoredBy) { + id, + body, + } + } + } + `; + const assetCommentsResponse = await graphql(schema, assetCommentsWithoutIgnoredQuery, {}, context, {assetId, assetUrl, notIgnoredBy: userA.id}); + const comments = assetCommentsResponse.data.asset.comments; + expect(comments.length).to.equal(2); + }); + +});