From a6eaea70dcf2bb94a07f8548d1929fff5b860039 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 15 Aug 2019 15:47:13 +0200 Subject: [PATCH 01/43] Add UpdateComment support for Mentions in Comment --- backend/src/middleware/index.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index ba5b9d324..c44e19edd 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -1,4 +1,6 @@ -import { applyMiddleware } from 'graphql-middleware' +import { + applyMiddleware +} from 'graphql-middleware' import CONFIG from './../config' import activityPub from './activityPubMiddleware' @@ -12,7 +14,7 @@ import user from './userMiddleware' import includedFields from './includedFieldsMiddleware' import orderBy from './orderByMiddleware' import validation from './validation/validationMiddleware' -import handleContentData from './handleHtmlContent/handleContentData' +import handleNotifications from './handleNotifications/handleNotifications' import email from './email/emailMiddleware' export default schema => { @@ -23,13 +25,15 @@ export default schema => { validation: validation, sluggify: sluggify, excerpt: excerpt, - handleContentData: handleContentData, + handleNotifications: handleNotifications, xss: xss, softDelete: softDelete, user: user, includedFields: includedFields, orderBy: orderBy, - email: email({ isEnabled: CONFIG.SMTP_HOST && CONFIG.SMTP_PORT }), + email: email({ + isEnabled: CONFIG.SMTP_HOST && CONFIG.SMTP_PORT + }), } let order = [ @@ -40,7 +44,7 @@ export default schema => { 'sluggify', 'excerpt', 'email', - 'handleContentData', + 'handleNotifications', 'xss', 'softDelete', 'user', @@ -62,4 +66,4 @@ export default schema => { const appliedMiddlewares = order.map(key => middlewares[key]) return applyMiddleware(schema, ...appliedMiddlewares) -} +} \ No newline at end of file From 5b5489232f13de63fcbe04eee1a7f84eae685fd7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 15 Aug 2019 15:47:53 +0200 Subject: [PATCH 02/43] Rename files --- .../handleNotifications.js} | 4 ++-- .../handleNotifications.spec.js} | 0 .../hashtags/extractHashtags.js | 0 .../hashtags/extractHashtags.spec.js | 0 .../notifications/extractMentionedUsers.js | 0 .../notifications/extractMentionedUsers.spec.js | 0 6 files changed, 2 insertions(+), 2 deletions(-) rename backend/src/middleware/{handleHtmlContent/handleContentData.js => handleNotifications/handleNotifications.js} (98%) rename backend/src/middleware/{handleHtmlContent/handleContentData.spec.js => handleNotifications/handleNotifications.spec.js} (100%) rename backend/src/middleware/{handleHtmlContent => handleNotifications}/hashtags/extractHashtags.js (100%) rename backend/src/middleware/{handleHtmlContent => handleNotifications}/hashtags/extractHashtags.spec.js (100%) rename backend/src/middleware/{handleHtmlContent => handleNotifications}/notifications/extractMentionedUsers.js (100%) rename backend/src/middleware/{handleHtmlContent => handleNotifications}/notifications/extractMentionedUsers.spec.js (100%) diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.js b/backend/src/middleware/handleNotifications/handleNotifications.js similarity index 98% rename from backend/src/middleware/handleHtmlContent/handleContentData.js rename to backend/src/middleware/handleNotifications/handleNotifications.js index ab0456f9a..d00e6295c 100644 --- a/backend/src/middleware/handleHtmlContent/handleContentData.js +++ b/backend/src/middleware/handleNotifications/handleNotifications.js @@ -86,6 +86,6 @@ export default { CreatePost: handleContentDataOfPost, UpdatePost: handleContentDataOfPost, CreateComment: handleContentDataOfComment, - // UpdateComment: handleContentDataOfComment, + UpdateComment: handleContentDataOfComment, }, -} +} \ No newline at end of file diff --git a/backend/src/middleware/handleHtmlContent/handleContentData.spec.js b/backend/src/middleware/handleNotifications/handleNotifications.spec.js similarity index 100% rename from backend/src/middleware/handleHtmlContent/handleContentData.spec.js rename to backend/src/middleware/handleNotifications/handleNotifications.spec.js diff --git a/backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.js b/backend/src/middleware/handleNotifications/hashtags/extractHashtags.js similarity index 100% rename from backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.js rename to backend/src/middleware/handleNotifications/hashtags/extractHashtags.js diff --git a/backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.spec.js b/backend/src/middleware/handleNotifications/hashtags/extractHashtags.spec.js similarity index 100% rename from backend/src/middleware/handleHtmlContent/hashtags/extractHashtags.spec.js rename to backend/src/middleware/handleNotifications/hashtags/extractHashtags.spec.js diff --git a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js b/backend/src/middleware/handleNotifications/notifications/extractMentionedUsers.js similarity index 100% rename from backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.js rename to backend/src/middleware/handleNotifications/notifications/extractMentionedUsers.js diff --git a/backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js b/backend/src/middleware/handleNotifications/notifications/extractMentionedUsers.spec.js similarity index 100% rename from backend/src/middleware/handleHtmlContent/notifications/extractMentionedUsers.spec.js rename to backend/src/middleware/handleNotifications/notifications/extractMentionedUsers.spec.js From af70d4f717124940078fd3a9e7bd2bf14db72e12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 15 Aug 2019 16:59:42 +0200 Subject: [PATCH 03/43] Get Notification for author for Commenting their Posts --- .../handleNotifications.js | 41 +++++++++++++++---- 1 file changed, 32 insertions(+), 9 deletions(-) diff --git a/backend/src/middleware/handleNotifications/handleNotifications.js b/backend/src/middleware/handleNotifications/handleNotifications.js index d00e6295c..c51bfce25 100644 --- a/backend/src/middleware/handleNotifications/handleNotifications.js +++ b/backend/src/middleware/handleNotifications/handleNotifications.js @@ -1,8 +1,8 @@ import extractMentionedUsers from './notifications/extractMentionedUsers' import extractHashtags from './hashtags/extractHashtags' -const notifyMentions = async (label, id, idsOfMentionedUsers, context) => { - if (!idsOfMentionedUsers.length) return +const notifyUsers = async (label, id, idsOfUsers, context) => { + if (!idsOfUsers.length) return const session = context.driver.session() const createdAt = new Date().toISOString() @@ -11,13 +11,13 @@ const notifyMentions = async (label, id, idsOfMentionedUsers, context) => { WHERE source.id = $id AND $label IN LABELS(source) MATCH (source)<-[:WROTE]-(author: User) MATCH (u: User) - WHERE u.id in $idsOfMentionedUsers + WHERE u.id in $idsOfUsers AND NOT (u)<-[:BLOCKED]-(author) CREATE (n: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt }) MERGE (source)-[:NOTIFIED]->(n)-[:NOTIFIED]->(u) ` await session.run(cypher, { - idsOfMentionedUsers, + idsOfUsers, label, createdAt, id, @@ -56,14 +56,14 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => { const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { // extract user ids before xss-middleware removes classes via the following "resolve" call - const idsOfMentionedUsers = extractMentionedUsers(args.content) + const idsOfUsers = extractMentionedUsers(args.content) // extract tag (hashtag) ids before xss-middleware removes classes via the following "resolve" call const hashtags = extractHashtags(args.content) // removes classes from the content const post = await resolve(root, args, context, resolveInfo) - await notifyMentions('Post', post.id, idsOfMentionedUsers, context) + await notifyUsers('Post', post.id, idsOfUsers, context) await updateHashtagsOfPost(post.id, hashtags, context) return post @@ -71,12 +71,35 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => { // extract user ids before xss-middleware removes classes via the following "resolve" call - const idsOfMentionedUsers = extractMentionedUsers(args.content) + const idsOfUsers = extractMentionedUsers(args.content) // removes classes from the content const comment = await resolve(root, args, context, resolveInfo) - await notifyMentions('Comment', comment.id, idsOfMentionedUsers, context) + await notifyUsers('Comment', comment.id, idsOfUsers, context) + + return comment +} + +const handleCreateComment = async (resolve, root, args, context, resolveInfo) => { + // removes classes from the content + const comment = await handleContentDataOfComment(resolve, root, args, context, resolveInfo) + + const session = context.driver.session() + const cypherFindUser = ` + MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId }) + RETURN user { .id } + ` + const result = await session.run(cypherFindUser, { + commentId: comment.id, + }) + session.close() + const [userWrotePost] = await result.records.map(record => { + return record.get('user') + }) + if (context.user.id !== userWrotePost.id) { + await notifyUsers('Comment', comment.id, [userWrotePost.id], context) + } return comment } @@ -85,7 +108,7 @@ export default { Mutation: { CreatePost: handleContentDataOfPost, UpdatePost: handleContentDataOfPost, - CreateComment: handleContentDataOfComment, + CreateComment: handleCreateComment, UpdateComment: handleContentDataOfComment, }, } \ No newline at end of file From 8acccc99d0c8ba9d92a0b3ce04e0320ac067a866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 15 Aug 2019 19:29:06 +0200 Subject: [PATCH 04/43] Implemented a `reason` in the Notification Used this for displaying in the mentions menu in frontend. Rewrite backend test. --- .../handleNotifications.js | 24 ++- .../handleNotifications.spec.js | 140 +++++++++++++----- backend/src/middleware/index.js | 8 +- backend/src/models/Notification.js | 24 ++- .../schema/resolvers/notifications.spec.js | 34 +++-- .../src/schema/types/type/Notification.gql | 3 +- backend/src/seed/factories/index.js | 25 +++- .../Notification/Notification.vue | 13 +- .../notifications/NotificationMenu/index.vue | 1 + webapp/locales/de.json | 4 +- webapp/locales/en.json | 4 +- 11 files changed, 207 insertions(+), 73 deletions(-) diff --git a/backend/src/middleware/handleNotifications/handleNotifications.js b/backend/src/middleware/handleNotifications/handleNotifications.js index c51bfce25..6b8368d44 100644 --- a/backend/src/middleware/handleNotifications/handleNotifications.js +++ b/backend/src/middleware/handleNotifications/handleNotifications.js @@ -1,9 +1,18 @@ +import { + UserInputError +} from 'apollo-server' import extractMentionedUsers from './notifications/extractMentionedUsers' import extractHashtags from './hashtags/extractHashtags' -const notifyUsers = async (label, id, idsOfUsers, context) => { +const notifyUsers = async (label, id, idsOfUsers, reason, context) => { if (!idsOfUsers.length) return + // Done here, because Neode validation is not working. + const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_your_post'] + if (!(reasonsAllowed.includes(reason))) { + throw new UserInputError("Notification reason is not allowed!") + } + const session = context.driver.session() const createdAt = new Date().toISOString() const cypher = ` @@ -13,14 +22,15 @@ const notifyUsers = async (label, id, idsOfUsers, context) => { MATCH (u: User) WHERE u.id in $idsOfUsers AND NOT (u)<-[:BLOCKED]-(author) - CREATE (n: Notification {id: apoc.create.uuid(), read: false, createdAt: $createdAt }) + CREATE (n: Notification { id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) MERGE (source)-[:NOTIFIED]->(n)-[:NOTIFIED]->(u) ` await session.run(cypher, { - idsOfUsers, label, - createdAt, id, + idsOfUsers, + reason, + createdAt, }) session.close() } @@ -63,7 +73,7 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo // removes classes from the content const post = await resolve(root, args, context, resolveInfo) - await notifyUsers('Post', post.id, idsOfUsers, context) + await notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context) await updateHashtagsOfPost(post.id, hashtags, context) return post @@ -76,7 +86,7 @@ const handleContentDataOfComment = async (resolve, root, args, context, resolveI // removes classes from the content const comment = await resolve(root, args, context, resolveInfo) - await notifyUsers('Comment', comment.id, idsOfUsers, context) + await notifyUsers('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context) return comment } @@ -98,7 +108,7 @@ const handleCreateComment = async (resolve, root, args, context, resolveInfo) => return record.get('user') }) if (context.user.id !== userWrotePost.id) { - await notifyUsers('Comment', comment.id, [userWrotePost.id], context) + await notifyUsers('Comment', comment.id, [userWrotePost.id], 'comment_on_your_post', context) } return comment diff --git a/backend/src/middleware/handleNotifications/handleNotifications.spec.js b/backend/src/middleware/handleNotifications/handleNotifications.spec.js index 40d8a2481..559c1d49a 100644 --- a/backend/src/middleware/handleNotifications/handleNotifications.spec.js +++ b/backend/src/middleware/handleNotifications/handleNotifications.spec.js @@ -1,7 +1,14 @@ -import { gql } from '../../jest/helpers' +import { + gql +} from '../../jest/helpers' import Factory from '../../seed/factories' -import { createTestClient } from 'apollo-server-testing' -import { neode, getDriver } from '../../bootstrap/neo4j' +import { + createTestClient +} from 'apollo-server-testing' +import { + neode, + getDriver +} from '../../bootstrap/neo4j' import createServer from '../../server' const factory = Factory() @@ -44,11 +51,12 @@ afterEach(async () => { }) describe('notifications', () => { - const notificationQuery = gql` + const notificationQuery = gql ` query($read: Boolean) { currentUser { notifications(read: $read, orderBy: createdAt_desc) { read + reason post { content } @@ -78,7 +86,7 @@ describe('notifications', () => { 'Hey @al-capone how do you do?' const createPostAction = async () => { - const createPostMutation = gql` + const createPostMutation = gql ` mutation($id: ID, $title: String!, $content: String!) { CreatePost(id: $id, title: $title, content: $content) { id @@ -90,7 +98,11 @@ describe('notifications', () => { authenticatedUser = await author.toJson() await mutate({ mutation: createPostMutation, - variables: { id: 'p47', title, content }, + variables: { + id: 'p47', + title, + content + }, }) authenticatedUser = await user.toJson() } @@ -101,12 +113,27 @@ describe('notifications', () => { 'Hey @al-capone how do you do?' const expected = expect.objectContaining({ data: { - currentUser: { notifications: [{ read: false, post: { content: expectedContent } }] }, + currentUser: { + notifications: [{ + read: false, + reason: 'mentioned_in_post', + post: { + content: expectedContent + } + }] + }, }, }) - const { query } = createTestClient(server) + const { + query + } = createTestClient(server) await expect( - query({ query: notificationQuery, variables: { read: false } }), + query({ + query: notificationQuery, + variables: { + read: false + } + }), ).resolves.toEqual(expected) }) @@ -126,7 +153,7 @@ describe('notifications', () => { @al-capone ` - const updatePostMutation = gql` + const updatePostMutation = gql ` mutation($id: ID!, $title: String!, $content: String!) { UpdatePost(id: $id, content: $content, title: $title) { title @@ -154,15 +181,31 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { currentUser: { - notifications: [ - { read: false, post: { content: expectedContent } }, - { read: false, post: { content: expectedContent } }, + notifications: [{ + read: false, + reason: 'mentioned_in_post', + post: { + content: expectedContent + } + }, + { + read: false, + reason: 'mentioned_in_post', + post: { + content: expectedContent + } + }, ], }, }, }) await expect( - query({ query: notificationQuery, variables: { read: false } }), + query({ + query: notificationQuery, + variables: { + read: false + } + }), ).resolves.toEqual(expected) }) }) @@ -175,11 +218,22 @@ describe('notifications', () => { it('sends no notification', async () => { await createPostAction() const expected = expect.objectContaining({ - data: { currentUser: { notifications: [] } }, + data: { + currentUser: { + notifications: [] + } + }, }) - const { query } = createTestClient(server) + const { + query + } = createTestClient(server) await expect( - query({ query: notificationQuery, variables: { read: false } }), + query({ + query: notificationQuery, + variables: { + read: false + } + }), ).resolves.toEqual(expected) }) }) @@ -193,7 +247,7 @@ describe('Hashtags', () => { const postTitle = 'Two Hashtags' const postContent = '

Hey Dude, #Democracy should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' - const postWithHastagsQuery = gql` + const postWithHastagsQuery = gql ` query($id: ID) { Post(id: $id) { tags { @@ -206,7 +260,7 @@ describe('Hashtags', () => { const postWithHastagsVariables = { id: postId, } - const createPostMutation = gql` + const createPostMutation = gql ` mutation($postId: ID, $postTitle: String!, $postContent: String!) { CreatePost(id: $postId, title: $postTitle, content: $postContent) { id @@ -234,20 +288,26 @@ describe('Hashtags', () => { }) it('both Hashtags are created with the "id" set to their "name"', async () => { - const expected = [ - { id: 'Democracy', name: 'Democracy' }, - { id: 'Liberty', name: 'Liberty' }, + const expected = [{ + id: 'Democracy', + name: 'Democracy' + }, + { + id: 'Liberty', + name: 'Liberty' + }, ] await expect( - query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }), + query({ + query: postWithHastagsQuery, + variables: postWithHastagsVariables + }), ).resolves.toEqual( expect.objectContaining({ data: { - Post: [ - { - tags: expect.arrayContaining(expected), - }, - ], + Post: [{ + tags: expect.arrayContaining(expected), + }, ], }, }), ) @@ -257,7 +317,7 @@ describe('Hashtags', () => { // The already existing Hashtag has no class at this point. const updatedPostContent = '

Hey Dude, #Elections should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' - const updatePostMutation = gql` + const updatePostMutation = gql ` mutation($postId: ID!, $postTitle: String!, $updatedPostContent: String!) { UpdatePost(id: $postId, title: $postTitle, content: $updatedPostContent) { id @@ -277,16 +337,26 @@ describe('Hashtags', () => { }, }) - const expected = [ - { id: 'Elections', name: 'Elections' }, - { id: 'Liberty', name: 'Liberty' }, + const expected = [{ + id: 'Elections', + name: 'Elections' + }, + { + id: 'Liberty', + name: 'Liberty' + }, ] await expect( - query({ query: postWithHastagsQuery, variables: postWithHastagsVariables }), + query({ + query: postWithHastagsQuery, + variables: postWithHastagsVariables + }), ).resolves.toEqual( expect.objectContaining({ data: { - Post: [{ tags: expect.arrayContaining(expected) }], + Post: [{ + tags: expect.arrayContaining(expected) + }], }, }), ) @@ -294,4 +364,4 @@ describe('Hashtags', () => { }) }) }) -}) +}) \ No newline at end of file diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index c44e19edd..f155f5648 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -1,6 +1,4 @@ -import { - applyMiddleware -} from 'graphql-middleware' +import { applyMiddleware } from 'graphql-middleware' import CONFIG from './../config' import activityPub from './activityPubMiddleware' @@ -32,7 +30,7 @@ export default schema => { includedFields: includedFields, orderBy: orderBy, email: email({ - isEnabled: CONFIG.SMTP_HOST && CONFIG.SMTP_PORT + isEnabled: CONFIG.SMTP_HOST && CONFIG.SMTP_PORT, }), } @@ -66,4 +64,4 @@ export default schema => { const appliedMiddlewares = order.map(key => middlewares[key]) return applyMiddleware(schema, ...appliedMiddlewares) -} \ No newline at end of file +} diff --git a/backend/src/models/Notification.js b/backend/src/models/Notification.js index b8690b8c1..89de27aec 100644 --- a/backend/src/models/Notification.js +++ b/backend/src/models/Notification.js @@ -1,9 +1,25 @@ import uuid from 'uuid/v4' module.exports = { - id: { type: 'uuid', primary: true, default: uuid }, - createdAt: { type: 'string', isoDate: true, default: () => new Date().toISOString() }, - read: { type: 'boolean', default: false }, + id: { + type: 'uuid', + primary: true, + default: uuid, + }, + read: { + type: 'boolean', + default: false, + }, + reason: { + type: 'string', + valid: ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_your_post'], + default: 'mentioned_in_post', + }, + createdAt: { + type: 'string', + isoDate: true, + default: () => new Date().toISOString(), + }, user: { type: 'relationship', relationship: 'NOTIFIED', @@ -16,4 +32,4 @@ module.exports = { target: 'Post', direction: 'in', }, -} +} \ No newline at end of file diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index 5e7108be3..313376a25 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -1,6 +1,12 @@ -import { GraphQLClient } from 'graphql-request' +import { + GraphQLClient +} from 'graphql-request' import Factory from '../../seed/factories' -import { host, login, gql } from '../../jest/helpers' +import { + host, + login, + gql +} from '../../jest/helpers' const factory = Factory() let client @@ -19,7 +25,7 @@ afterEach(async () => { }) describe('query for notification', () => { - const notificationQuery = gql` + const notificationQuery = gql ` { Notification { id @@ -61,23 +67,29 @@ describe('currentUser notifications', () => { factory.create('User', neighborParams), factory.create('Notification', { id: 'post-mention-not-for-you', + reason: 'mentioned_in_post', }), factory.create('Notification', { id: 'post-mention-already-seen', read: true, + reason: 'mentioned_in_post', }), factory.create('Notification', { id: 'post-mention-unseen', + reason: 'mentioned_in_post', }), factory.create('Notification', { id: 'comment-mention-not-for-you', + reason: 'mentioned_in_comment', }), factory.create('Notification', { id: 'comment-mention-already-seen', read: true, + reason: 'mentioned_in_comment', }), factory.create('Notification', { id: 'comment-mention-unseen', + reason: 'mentioned_in_comment', }), ]) await factory.authenticateAs(neighborParams) @@ -149,7 +161,7 @@ describe('currentUser notifications', () => { }) describe('filter for read: false', () => { - const queryCurrentUserNotificationsFilterRead = gql` + const queryCurrentUserNotificationsFilterRead = gql ` query($read: Boolean) { currentUser { notifications(read: $read, orderBy: createdAt_desc) { @@ -170,8 +182,7 @@ describe('currentUser notifications', () => { it('returns only unread notifications of current user', async () => { const expected = { currentUser: { - notifications: expect.arrayContaining([ - { + notifications: expect.arrayContaining([{ id: 'post-mention-unseen', post: { id: 'p1', @@ -195,7 +206,7 @@ describe('currentUser notifications', () => { }) describe('no filters', () => { - const queryCurrentUserNotifications = gql` + const queryCurrentUserNotifications = gql ` { currentUser { notifications(orderBy: createdAt_desc) { @@ -213,8 +224,7 @@ describe('currentUser notifications', () => { it('returns all notifications of current user', async () => { const expected = { currentUser: { - notifications: expect.arrayContaining([ - { + notifications: expect.arrayContaining([{ id: 'post-mention-unseen', post: { id: 'p1', @@ -255,7 +265,7 @@ describe('currentUser notifications', () => { }) describe('UpdateNotification', () => { - const mutationUpdateNotification = gql` + const mutationUpdateNotification = gql ` mutation($id: ID!, $read: Boolean) { UpdateNotification(id: $id, read: $read) { id @@ -286,9 +296,11 @@ describe('UpdateNotification', () => { factory.create('User', mentionedParams), factory.create('Notification', { id: 'post-mention-to-be-updated', + reason: 'mentioned_in_post', }), factory.create('Notification', { id: 'comment-mention-to-be-updated', + reason: 'mentioned_in_comment', }), ]) await factory.authenticateAs(userParams) @@ -390,4 +402,4 @@ describe('UpdateNotification', () => { }) }) }) -}) +}) \ No newline at end of file diff --git a/backend/src/schema/types/type/Notification.gql b/backend/src/schema/types/type/Notification.gql index 0f94c2301..a03b86769 100644 --- a/backend/src/schema/types/type/Notification.gql +++ b/backend/src/schema/types/type/Notification.gql @@ -1,8 +1,9 @@ type Notification { id: ID! read: Boolean + reason: String + createdAt: String user: User @relation(name: "NOTIFIED", direction: "OUT") post: Post @relation(name: "NOTIFIED", direction: "IN") comment: Comment @relation(name: "NOTIFIED", direction: "IN") - createdAt: String } diff --git a/backend/src/seed/factories/index.js b/backend/src/seed/factories/index.js index df3886a6c..56518bd06 100644 --- a/backend/src/seed/factories/index.js +++ b/backend/src/seed/factories/index.js @@ -62,15 +62,26 @@ export default function Factory(options = {}) { lastResponse: null, neodeInstance, async authenticateAs({ email, password }) { - const headers = await authenticatedHeaders({ email, password }, seedServerHost) + const headers = await authenticatedHeaders( + { + email, + password, + }, + seedServerHost, + ) this.lastResponse = headers - this.graphQLClient = new GraphQLClient(seedServerHost, { headers }) + this.graphQLClient = new GraphQLClient(seedServerHost, { + headers, + }) return this }, async create(node, args = {}) { const { factory, mutation, variables } = this.factories[node](args) if (factory) { - this.lastResponse = await factory({ args, neodeInstance }) + this.lastResponse = await factory({ + args, + neodeInstance, + }) return this.lastResponse } else { this.lastResponse = await this.graphQLClient.request(mutation, variables) @@ -121,11 +132,15 @@ export default function Factory(options = {}) { }, async invite({ email }) { const mutation = ` mutation($email: String!) { invite( email: $email) } ` - this.lastResponse = await this.graphQLClient.request(mutation, { email }) + this.lastResponse = await this.graphQLClient.request(mutation, { + email, + }) return this }, async cleanDatabase() { - this.lastResponse = await cleanDatabase({ driver: this.neo4jDriver }) + this.lastResponse = await cleanDatabase({ + driver: this.neo4jDriver, + }) return this }, async emote({ to, data }) { diff --git a/webapp/components/notifications/Notification/Notification.vue b/webapp/components/notifications/Notification/Notification.vue index ae1eeddc9..a982e33a9 100644 --- a/webapp/components/notifications/Notification/Notification.vue +++ b/webapp/components/notifications/Notification/Notification.vue @@ -8,9 +8,7 @@ :trunc="35" /> - - {{ $t('notifications.menu.mentioned', { resource: post.id ? 'post' : 'comment' }) }} - + {{ $t(notificationTextIdents[notification.reason]) }} Date: Mon, 19 Aug 2019 22:13:42 +0200 Subject: [PATCH 05/43] Write backend tests Rename files. Co-Authored-By: mattwr18 --- ...ns.js => handleNotificationsMiddleware.js} | 44 ++--- ... => handleNotificationsMiddleware.spec.js} | 167 ++++++++++-------- backend/src/middleware/index.js | 2 +- backend/src/models/Notification.js | 2 +- .../schema/resolvers/notifications.spec.js | 26 ++- 5 files changed, 127 insertions(+), 114 deletions(-) rename backend/src/middleware/handleNotifications/{handleNotifications.js => handleNotificationsMiddleware.js} (80%) rename backend/src/middleware/handleNotifications/{handleNotifications.spec.js => handleNotificationsMiddleware.spec.js} (79%) diff --git a/backend/src/middleware/handleNotifications/handleNotifications.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js similarity index 80% rename from backend/src/middleware/handleNotifications/handleNotifications.js rename to backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js index cbb305fe5..d04eeeece 100644 --- a/backend/src/middleware/handleNotifications/handleNotifications.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js @@ -1,6 +1,4 @@ -import { - UserInputError -} from 'apollo-server' +import { UserInputError } from 'apollo-server' import extractMentionedUsers from './notifications/extractMentionedUsers' import extractHashtags from './hashtags/extractHashtags' @@ -9,8 +7,8 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => { // Done here, because Neode validation is not working. const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_your_post'] - if (!(reasonsAllowed.includes(reason))) { - throw new UserInputError("Notification reason is not allowed!") + if (!reasonsAllowed.includes(reason)) { + throw new UserInputError('Notification reason is not allowed!') } const session = context.driver.session() @@ -81,8 +79,10 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo const post = await resolve(root, args, context, resolveInfo) - await notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context) - await updateHashtagsOfPost(post.id, hashtags, context) + if (post) { + await notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context) + await updateHashtagsOfPost(post.id, hashtags, context) + } return post } @@ -91,7 +91,9 @@ const handleContentDataOfComment = async (resolve, root, args, context, resolveI const idsOfUsers = extractMentionedUsers(args.content) const comment = await resolve(root, args, context, resolveInfo) - await notifyUsers('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context) + if (comment) { + await notifyUsers('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context) + } return comment } @@ -100,20 +102,22 @@ const handleCreateComment = async (resolve, root, args, context, resolveInfo) => // removes classes from the content const comment = await handleContentDataOfComment(resolve, root, args, context, resolveInfo) - const session = context.driver.session() - const cypherFindUser = ` + if (comment) { + const session = context.driver.session() + const cypherFindUser = ` MATCH (user: User)-[:WROTE]->(:Post)<-[:COMMENTS]-(:Comment { id: $commentId }) RETURN user { .id } ` - const result = await session.run(cypherFindUser, { - commentId: comment.id, - }) - session.close() - const [userWrotePost] = await result.records.map(record => { - return record.get('user') - }) - if (context.user.id !== userWrotePost.id) { - await notifyUsers('Comment', comment.id, [userWrotePost.id], 'comment_on_your_post', context) + const result = await session.run(cypherFindUser, { + commentId: comment.id, + }) + session.close() + const [postAuthor] = await result.records.map(record => { + return record.get('user') + }) + if (context.user.id !== postAuthor.id) { + await notifyUsers('Comment', comment.id, [postAuthor.id], 'comment_on_your_post', context) + } } return comment @@ -126,4 +130,4 @@ export default { CreateComment: handleCreateComment, UpdateComment: handleContentDataOfComment, }, -} \ No newline at end of file +} diff --git a/backend/src/middleware/handleNotifications/handleNotifications.spec.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js similarity index 79% rename from backend/src/middleware/handleNotifications/handleNotifications.spec.js rename to backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js index 205c1ecb0..cb6aa9844 100644 --- a/backend/src/middleware/handleNotifications/handleNotifications.spec.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js @@ -1,14 +1,7 @@ -import { - gql -} from '../../jest/helpers' +import { gql } from '../../jest/helpers' import Factory from '../../seed/factories' -import { - createTestClient -} from 'apollo-server-testing' -import { - neode, - getDriver -} from '../../bootstrap/neo4j' +import { createTestClient } from 'apollo-server-testing' +import { neode, getDriver } from '../../bootstrap/neo4j' import createServer from '../../server' const factory = Factory() @@ -51,7 +44,7 @@ afterEach(async () => { }) describe('notifications', () => { - const notificationQuery = gql ` + const notificationQuery = gql` query($read: Boolean) { currentUser { notifications(read: $read, orderBy: createdAt_desc) { @@ -74,6 +67,30 @@ describe('notifications', () => { }) describe('given another user', () => { + let title + let content + const createPostAction = async () => { + const createPostMutation = gql` + mutation($id: ID, $title: String!, $content: String!) { + CreatePost(id: $id, title: $title, content: $content) { + id + title + content + } + } + ` + authenticatedUser = await postAuthor.toJson() + await mutate({ + mutation: createPostMutation, + variables: { + id: 'p47', + title, + content, + }, + }) + authenticatedUser = await user.toJson() + } + let postAuthor beforeEach(async () => { postAuthor = await instance.create('User', { @@ -83,54 +100,42 @@ describe('notifications', () => { }) }) - describe('who mentions me in a post', () => { - const title = 'Mentioning Al Capone' - const content = + describe('comments on my post', () => { + title = 'My post' + content = 'My content' + + // it('sends me a notification', async () => { + // await createPostAction() + // XXX + // }) + }) + + describe('mentions me in a post', () => { + title = 'Mentioning Al Capone' + content = 'Hey @al-capone how do you do?' - const createPostAction = async () => { - const createPostMutation = gql ` - mutation($id: ID, $title: String!, $content: String!) { - CreatePost(id: $id, title: $title, content: $content) { - id - title - content - } - } - ` - authenticatedUser = await postAuthor.toJson() - await mutate({ - mutation: createPostMutation, - variables: { - id: 'p47', - title, - content, - }, - }) - authenticatedUser = await user.toJson() - } - - it.only('sends you a notification', async () => { + it('sends you a notification', async () => { await createPostAction() const expectedContent = 'Hey @al-capone how do you do?' const expected = expect.objectContaining({ data: { currentUser: { - notifications: [{ - read: false, - reason: 'mentioned_in_post', - post: { - content: expectedContent, + notifications: [ + { + read: false, + reason: 'mentioned_in_post', + post: { + content: expectedContent, + }, + comment: null, }, - comment: null, - }, ], + ], }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -157,7 +162,7 @@ describe('notifications', () => { @al-capone ` - const updatePostMutation = gql ` + const updatePostMutation = gql` mutation($id: ID!, $title: String!, $content: String!) { UpdatePost(id: $id, content: $content, title: $title) { title @@ -185,7 +190,8 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { currentUser: { - notifications: [{ + notifications: [ + { read: false, reason: 'mentioned_in_post', post: { @@ -230,9 +236,7 @@ describe('notifications', () => { }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -247,7 +251,7 @@ describe('notifications', () => { describe('but the author of the post blocked me and a mentioner mentions me in a comment', () => { const createCommentOnPostAction = async () => { await createPostAction() - const createCommentMutation = gql ` + const createCommentMutation = gql` mutation($id: ID, $postId: ID!, $commentContent: String!) { CreateComment(id: $id, postId: $postId, content: $commentContent) { id @@ -261,7 +265,8 @@ describe('notifications', () => { variables: { id: 'c47', postId: 'p47', - commentContent: 'One mention of me with .', + commentContent: + 'One mention of me with .', }, }) authenticatedUser = await user.toJson() @@ -288,9 +293,7 @@ describe('notifications', () => { }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -311,7 +314,7 @@ describe('Hashtags', () => { const postTitle = 'Two Hashtags' const postContent = '

Hey Dude, #Democracy should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' - const postWithHastagsQuery = gql ` + const postWithHastagsQuery = gql` query($id: ID) { Post(id: $id) { tags { @@ -323,7 +326,7 @@ describe('Hashtags', () => { const postWithHastagsVariables = { id: postId, } - const createPostMutation = gql ` + const createPostMutation = gql` mutation($postId: ID, $postTitle: String!, $postContent: String!) { CreatePost(id: $postId, title: $postTitle, content: $postContent) { id @@ -351,11 +354,14 @@ describe('Hashtags', () => { }) it('both Hashtags are created with the "id" set to their "name"', async () => { - const expected = [{ - id: 'Democracy' - }, { - id: 'Liberty' - }] + const expected = [ + { + id: 'Democracy', + }, + { + id: 'Liberty', + }, + ] await expect( query({ query: postWithHastagsQuery, @@ -364,9 +370,11 @@ describe('Hashtags', () => { ).resolves.toEqual( expect.objectContaining({ data: { - Post: [{ - tags: expect.arrayContaining(expected), - }, ], + Post: [ + { + tags: expect.arrayContaining(expected), + }, + ], }, }), ) @@ -376,7 +384,7 @@ describe('Hashtags', () => { // The already existing Hashtag has no class at this point. const updatedPostContent = '

Hey Dude, #Elections should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' - const updatePostMutation = gql ` + const updatePostMutation = gql` mutation($postId: ID!, $postTitle: String!, $updatedPostContent: String!) { UpdatePost(id: $postId, title: $postTitle, content: $updatedPostContent) { id @@ -396,11 +404,14 @@ describe('Hashtags', () => { }, }) - const expected = [{ - id: 'Elections' - }, { - id: 'Liberty' - }] + const expected = [ + { + id: 'Elections', + }, + { + id: 'Liberty', + }, + ] await expect( query({ query: postWithHastagsQuery, @@ -409,9 +420,11 @@ describe('Hashtags', () => { ).resolves.toEqual( expect.objectContaining({ data: { - Post: [{ - tags: expect.arrayContaining(expected), - }, ], + Post: [ + { + tags: expect.arrayContaining(expected), + }, + ], }, }), ) @@ -419,4 +432,4 @@ describe('Hashtags', () => { }) }) }) -}) \ No newline at end of file +}) diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index f155f5648..9d9ce7e35 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -12,7 +12,7 @@ import user from './userMiddleware' import includedFields from './includedFieldsMiddleware' import orderBy from './orderByMiddleware' import validation from './validation/validationMiddleware' -import handleNotifications from './handleNotifications/handleNotifications' +import handleNotifications from './handleNotifications/handleNotificationsMiddleware' import email from './email/emailMiddleware' export default schema => { diff --git a/backend/src/models/Notification.js b/backend/src/models/Notification.js index 89de27aec..93c4cd6cb 100644 --- a/backend/src/models/Notification.js +++ b/backend/src/models/Notification.js @@ -32,4 +32,4 @@ module.exports = { target: 'Post', direction: 'in', }, -} \ No newline at end of file +} diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index 313376a25..99dc2fcd3 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -1,12 +1,6 @@ -import { - GraphQLClient -} from 'graphql-request' +import { GraphQLClient } from 'graphql-request' import Factory from '../../seed/factories' -import { - host, - login, - gql -} from '../../jest/helpers' +import { host, login, gql } from '../../jest/helpers' const factory = Factory() let client @@ -25,7 +19,7 @@ afterEach(async () => { }) describe('query for notification', () => { - const notificationQuery = gql ` + const notificationQuery = gql` { Notification { id @@ -161,7 +155,7 @@ describe('currentUser notifications', () => { }) describe('filter for read: false', () => { - const queryCurrentUserNotificationsFilterRead = gql ` + const queryCurrentUserNotificationsFilterRead = gql` query($read: Boolean) { currentUser { notifications(read: $read, orderBy: createdAt_desc) { @@ -182,7 +176,8 @@ describe('currentUser notifications', () => { it('returns only unread notifications of current user', async () => { const expected = { currentUser: { - notifications: expect.arrayContaining([{ + notifications: expect.arrayContaining([ + { id: 'post-mention-unseen', post: { id: 'p1', @@ -206,7 +201,7 @@ describe('currentUser notifications', () => { }) describe('no filters', () => { - const queryCurrentUserNotifications = gql ` + const queryCurrentUserNotifications = gql` { currentUser { notifications(orderBy: createdAt_desc) { @@ -224,7 +219,8 @@ describe('currentUser notifications', () => { it('returns all notifications of current user', async () => { const expected = { currentUser: { - notifications: expect.arrayContaining([{ + notifications: expect.arrayContaining([ + { id: 'post-mention-unseen', post: { id: 'p1', @@ -265,7 +261,7 @@ describe('currentUser notifications', () => { }) describe('UpdateNotification', () => { - const mutationUpdateNotification = gql ` + const mutationUpdateNotification = gql` mutation($id: ID!, $read: Boolean) { UpdateNotification(id: $id, read: $read) { id @@ -402,4 +398,4 @@ describe('UpdateNotification', () => { }) }) }) -}) \ No newline at end of file +}) From 3d2df56141aa67af3172f21fdf28e02c994b825d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Wed, 21 Aug 2019 14:27:32 +0200 Subject: [PATCH 06/43] Backend tests --- .../handleNotificationsMiddleware.js | 65 +++-- .../handleNotificationsMiddleware.spec.js | 257 ++++++++++++++---- backend/src/models/Notification.js | 3 +- .../schema/types/enum/ReasonNotification.gql | 5 + .../src/schema/types/type/Notification.gql | 2 +- .../Notification/Notification.vue | 2 +- 6 files changed, 254 insertions(+), 80 deletions(-) create mode 100644 backend/src/schema/types/enum/ReasonNotification.gql diff --git a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js index d04eeeece..723dbdc05 100644 --- a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js @@ -6,33 +6,56 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => { if (!idsOfUsers.length) return // Done here, because Neode validation is not working. - const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_your_post'] + const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_post'] if (!reasonsAllowed.includes(reason)) { throw new UserInputError('Notification reason is not allowed!') } + if ( + (label === 'Post' && reason !== 'mentioned_in_post') || + (label === 'Comment' && !['mentioned_in_comment', 'comment_on_post'].includes(reason)) + ) { + throw new UserInputError('Notification fits not to reason!') + } const session = context.driver.session() const createdAt = new Date().toISOString() let cypher - if (label === 'Post') { - cypher = ` - MATCH (post: Post { id: $id })<-[:WROTE]-(author: User) - MATCH (user: User) - WHERE user.id in $idsOfUsers - AND NOT (user)<-[:BLOCKED]-(author) - CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) - MERGE (post)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) - ` - } else { - cypher = ` - MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) - MATCH (user: User) - WHERE user.id in $idsOfUsers - AND NOT (user)<-[:BLOCKED]-(author) - AND NOT (user)<-[:BLOCKED]-(postAuthor) - CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) - MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) - ` + switch (reason) { + case 'mentioned_in_post': { + cypher = ` + MATCH (post: Post { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfUsers + AND NOT (user)<-[:BLOCKED]-(author) + CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) + MERGE (post)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) + ` + break + } + case 'mentioned_in_comment': { + cypher = ` + MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfUsers + AND NOT (user)<-[:BLOCKED]-(author) + AND NOT (user)<-[:BLOCKED]-(postAuthor) + CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) + MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) + ` + break + } + case 'comment_on_post': { + cypher = ` + MATCH (postAuthor: User)-[:WROTE]->(post: Post)<-[:COMMENTS]-(comment: Comment { id: $id })<-[:WROTE]-(author: User) + MATCH (user: User) + WHERE user.id in $idsOfUsers + AND NOT (user)<-[:BLOCKED]-(author) + AND NOT (author)<-[:BLOCKED]-(user) + CREATE (notification: Notification {id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) + MERGE (comment)-[:NOTIFIED]->(notification)-[:NOTIFIED]->(user) + ` + break + } } await session.run(cypher, { label, @@ -116,7 +139,7 @@ const handleCreateComment = async (resolve, root, args, context, resolveInfo) => return record.get('user') }) if (context.user.id !== postAuthor.id) { - await notifyUsers('Comment', comment.id, [postAuthor.id], 'comment_on_your_post', context) + await notifyUsers('Comment', comment.id, [postAuthor.id], 'comment_on_post', context) } } diff --git a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js index cb6aa9844..f5d2c61c8 100644 --- a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js @@ -63,16 +63,17 @@ describe('notifications', () => { describe('authenticated', () => { beforeEach(async () => { - authenticatedUser = user + authenticatedUser = await user.toJson() }) describe('given another user', () => { - let title - let content + let postTitle + let postContent + let postAuthor const createPostAction = async () => { const createPostMutation = gql` - mutation($id: ID, $title: String!, $content: String!) { - CreatePost(id: $id, title: $title, content: $content) { + mutation($id: ID, $postTitle: String!, $postContent: String!) { + CreatePost(id: $id, title: $postTitle, content: $postContent) { id title content @@ -84,38 +85,154 @@ describe('notifications', () => { mutation: createPostMutation, variables: { id: 'p47', - title, - content, + postTitle, + postContent, }, }) authenticatedUser = await user.toJson() } - let postAuthor - beforeEach(async () => { - postAuthor = await instance.create('User', { - email: 'post-author@example.org', - password: '1234', - id: 'postAuthor', + let commentContent + let commentAuthor + const createCommentOnPostAction = async () => { + await createPostAction() + const createCommentMutation = gql` + mutation($id: ID, $postId: ID!, $commentContent: String!) { + CreateComment(id: $id, postId: $postId, content: $commentContent) { + id + content + } + } + ` + authenticatedUser = await commentAuthor.toJson() + await mutate({ + mutation: createCommentMutation, + variables: { + id: 'c47', + postId: 'p47', + commentContent, + }, + }) + authenticatedUser = await user.toJson() + } + + describe('comments on my post', () => { + beforeEach(async () => { + postTitle = 'My post' + postContent = 'My post content.' + postAuthor = user + }) + + describe('commenter is not me', () => { + beforeEach(async () => { + commentContent = 'Commenters comment.' + commentAuthor = await instance.create('User', { + id: 'commentAuthor', + name: 'Mrs Comment', + slug: 'mrs-comment', + email: 'commentauthor@example.org', + password: '1234', + }) + }) + + it('sends me a notification', async () => { + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [ + { + read: false, + reason: 'comment_on_post', + post: null, + comment: { + content: commentContent, + }, + }, + ], + }, + }, + }) + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + + it('sends me no notification if I block the comment author', async () => { + await user.relateTo(commentAuthor, 'blocked') + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [], + }, + }, + }) + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + }) + + describe('commenter is me', () => { + beforeEach(async () => { + commentContent = 'My comment.' + commentAuthor = user + }) + + it('sends me no notification', async () => { + await user.relateTo(commentAuthor, 'blocked') + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [], + }, + }, + }) + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) }) }) - describe('comments on my post', () => { - title = 'My post' - content = 'My content' - - // it('sends me a notification', async () => { - // await createPostAction() - // XXX - // }) + beforeEach(async () => { + postAuthor = await instance.create('User', { + id: 'postAuthor', + name: 'Mrs Post', + slug: 'mrs-post', + email: 'post-author@example.org', + password: '1234', + }) }) describe('mentions me in a post', () => { - title = 'Mentioning Al Capone' - content = - 'Hey @al-capone how do you do?' + beforeEach(async () => { + postTitle = 'Mentioning Al Capone' + postContent = + 'Hey @al-capone how do you do?' + }) - it('sends you a notification', async () => { + it('sends me a notification', async () => { await createPostAction() const expectedContent = 'Hey @al-capone how do you do?' @@ -163,8 +280,8 @@ describe('notifications', () => { ` const updatePostMutation = gql` - mutation($id: ID!, $title: String!, $content: String!) { - UpdatePost(id: $id, content: $content, title: $title) { + mutation($id: ID!, $postTitle: String!, $postContent: String!) { + UpdatePost(id: $id, content: $postContent, title: $postTitle) { title content } @@ -175,8 +292,8 @@ describe('notifications', () => { mutation: updatePostMutation, variables: { id: 'p47', - title, - content: updatedContent, + postTitle, + postContent: updatedContent, }, }) authenticatedUser = await user.toJson() @@ -247,39 +364,67 @@ describe('notifications', () => { ).resolves.toEqual(expected) }) }) + }) - describe('but the author of the post blocked me and a mentioner mentions me in a comment', () => { - const createCommentOnPostAction = async () => { - await createPostAction() - const createCommentMutation = gql` - mutation($id: ID, $postId: ID!, $commentContent: String!) { - CreateComment(id: $id, postId: $postId, content: $commentContent) { - id - content - } - } - ` - authenticatedUser = await commentMentioner.toJson() - await mutate({ - mutation: createCommentMutation, - variables: { - id: 'c47', - postId: 'p47', - commentContent: - 'One mention of me with .', + describe('mentions me in a comment', () => { + beforeEach(async () => { + postTitle = 'Post where I get mentioned in a comment' + postContent = 'Content of post where I get mentioned in a comment.' + }) + + describe('I am not blocked at all', () => { + beforeEach(async () => { + commentContent = + 'One mention about me with @al-capone.' + commentAuthor = await instance.create('User', { + id: 'commentAuthor', + name: 'Mrs Comment', + slug: 'mrs-comment', + email: 'comment-author@example.org', + password: '1234', + }) + }) + + it('sends a notification', async () => { + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [ + { + read: false, + reason: 'mentioned_in_comment', + post: null, + comment: { + content: commentContent, + }, + }, + ], + }, }, }) - authenticatedUser = await user.toJson() - } - let commentMentioner + const { query } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, + }), + ).resolves.toEqual(expected) + }) + }) + describe('but the author of the post blocked me', () => { beforeEach(async () => { await postAuthor.relateTo(user, 'blocked') - commentMentioner = await instance.create('User', { - id: 'mentioner', - name: 'Mr Mentioner', - slug: 'mr-mentioner', - email: 'mentioner@example.org', + commentContent = + 'One mention about me with @al-capone.' + commentAuthor = await instance.create('User', { + id: 'commentAuthor', + name: 'Mrs Comment', + slug: 'mrs-comment', + email: 'comment-author@example.org', password: '1234', }) }) diff --git a/backend/src/models/Notification.js b/backend/src/models/Notification.js index 93c4cd6cb..b54a99574 100644 --- a/backend/src/models/Notification.js +++ b/backend/src/models/Notification.js @@ -12,7 +12,8 @@ module.exports = { }, reason: { type: 'string', - valid: ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_your_post'], + valid: ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_post'], + invalid: [null], default: 'mentioned_in_post', }, createdAt: { diff --git a/backend/src/schema/types/enum/ReasonNotification.gql b/backend/src/schema/types/enum/ReasonNotification.gql new file mode 100644 index 000000000..a66c446be --- /dev/null +++ b/backend/src/schema/types/enum/ReasonNotification.gql @@ -0,0 +1,5 @@ +enum ReasonNotification { + mentioned_in_post + mentioned_in_comment + comment_on_post +} \ No newline at end of file diff --git a/backend/src/schema/types/type/Notification.gql b/backend/src/schema/types/type/Notification.gql index a03b86769..a3543445f 100644 --- a/backend/src/schema/types/type/Notification.gql +++ b/backend/src/schema/types/type/Notification.gql @@ -1,7 +1,7 @@ type Notification { id: ID! read: Boolean - reason: String + reason: ReasonNotification createdAt: String user: User @relation(name: "NOTIFIED", direction: "OUT") post: Post @relation(name: "NOTIFIED", direction: "IN") diff --git a/webapp/components/notifications/Notification/Notification.vue b/webapp/components/notifications/Notification/Notification.vue index 1aa930fc7..8afde5793 100644 --- a/webapp/components/notifications/Notification/Notification.vue +++ b/webapp/components/notifications/Notification/Notification.vue @@ -56,7 +56,7 @@ export default { notificationTextIdents: { mentioned_in_post: 'notifications.menu.mentionedInPost', mentioned_in_comment: 'notifications.menu.mentionedInComment', - comment_on_your_post: 'notifications.menu.commentedOnPost', + comment_on_post: 'notifications.menu.commentedOnPost', }, } }, From 3e91a66673891d75ca71e7efaf465d8a90c3ee72 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 22 Aug 2019 08:32:19 +0200 Subject: [PATCH 07/43] Correct wrong merge of master --- .../handleNotifications/handleNotificationsMiddleware.js | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js index 723dbdc05..6b880cfd7 100644 --- a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js @@ -1,4 +1,6 @@ -import { UserInputError } from 'apollo-server' +import { + UserInputError +} from 'apollo-server' import extractMentionedUsers from './notifications/extractMentionedUsers' import extractHashtags from './hashtags/extractHashtags' @@ -82,7 +84,7 @@ const updateHashtagsOfPost = async (postId, hashtags, context) => { const cypherCreateNewTagsAndRelations = ` MATCH (p: Post { id: $postId}) UNWIND $hashtags AS tagName - MERGE (t: Tag { id: tagName, name: tagName, disabled: false, deleted: false }) + MERGE (t: Tag { id: tagName, disabled: false, deleted: false }) MERGE (p)-[:TAGGED]->(t) RETURN p, t ` @@ -153,4 +155,4 @@ export default { CreateComment: handleCreateComment, UpdateComment: handleContentDataOfComment, }, -} +} \ No newline at end of file From 793485c0fc5fe9eee1f776874bafc6656c422f0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Thu, 22 Aug 2019 15:20:40 +0200 Subject: [PATCH 08/43] Write frontend tests --- .../handleNotificationsMiddleware.js | 6 +- .../handleNotificationsMiddleware.spec.js | 126 +++++++-------- .../Notification/Notification.spec.js | 149 ++++++++++++++++-- .../Notification/Notification.vue | 13 +- webapp/graphql/User.js | 2 +- webapp/locales/de.json | 12 +- webapp/locales/en.json | 12 +- 7 files changed, 213 insertions(+), 107 deletions(-) diff --git a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js index 6b880cfd7..b56a1384e 100644 --- a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js @@ -1,6 +1,4 @@ -import { - UserInputError -} from 'apollo-server' +import { UserInputError } from 'apollo-server' import extractMentionedUsers from './notifications/extractMentionedUsers' import extractHashtags from './hashtags/extractHashtags' @@ -155,4 +153,4 @@ export default { CreateComment: handleCreateComment, UpdateComment: handleContentDataOfComment, }, -} \ No newline at end of file +} diff --git a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js index d982f318d..3b52d13e0 100644 --- a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.spec.js @@ -1,14 +1,7 @@ -import { - gql -} from '../../jest/helpers' +import { gql } from '../../jest/helpers' import Factory from '../../seed/factories' -import { - createTestClient -} from 'apollo-server-testing' -import { - neode, - getDriver -} from '../../bootstrap/neo4j' +import { createTestClient } from 'apollo-server-testing' +import { neode, getDriver } from '../../bootstrap/neo4j' import createServer from '../../server' let server @@ -20,7 +13,7 @@ const factory = Factory() const driver = getDriver() const instance = neode() const categoryIds = ['cat9'] -const createPostMutation = gql ` +const createPostMutation = gql` mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) { CreatePost(id: $id, title: $title, content: $postContent, categoryIds: $categoryIds) { id @@ -29,7 +22,7 @@ const createPostMutation = gql ` } } ` -const updatePostMutation = gql ` +const updatePostMutation = gql` mutation($id: ID!, $title: String!, $postContent: String!, $categoryIds: [ID]!) { UpdatePost(id: $id, content: $postContent, title: $title, categoryIds: $categoryIds) { title @@ -37,7 +30,7 @@ const updatePostMutation = gql ` } } ` -const createCommentMutation = gql ` +const createCommentMutation = gql` mutation($id: ID, $postId: ID!, $commentContent: String!) { CreateComment(id: $id, postId: $postId, content: $commentContent) { id @@ -82,7 +75,7 @@ afterEach(async () => { }) describe('notifications', () => { - const notificationQuery = gql ` + const notificationQuery = gql` query($read: Boolean) { currentUser { notifications(read: $read, orderBy: createdAt_desc) { @@ -162,20 +155,20 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { currentUser: { - notifications: [{ - read: false, - reason: 'comment_on_post', - post: null, - comment: { - content: commentContent, + notifications: [ + { + read: false, + reason: 'comment_on_post', + post: null, + comment: { + content: commentContent, + }, }, - }, ], + ], }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -196,9 +189,7 @@ describe('notifications', () => { }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -226,9 +217,7 @@ describe('notifications', () => { }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -265,20 +254,20 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { currentUser: { - notifications: [{ - read: false, - reason: 'mentioned_in_post', - post: { - content: expectedContent, + notifications: [ + { + read: false, + reason: 'mentioned_in_post', + post: { + content: expectedContent, + }, + comment: null, }, - comment: null, - }, ], + ], }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -326,7 +315,8 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { currentUser: { - notifications: [{ + notifications: [ + { read: false, reason: 'mentioned_in_post', post: { @@ -371,9 +361,7 @@ describe('notifications', () => { }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -410,20 +398,20 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { currentUser: { - notifications: [{ - read: false, - reason: 'mentioned_in_comment', - post: null, - comment: { - content: commentContent, + notifications: [ + { + read: false, + reason: 'mentioned_in_comment', + post: null, + comment: { + content: commentContent, + }, }, - }, ], + ], }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -458,9 +446,7 @@ describe('notifications', () => { }, }, }) - const { - query - } = createTestClient(server) + const { query } = createTestClient(server) await expect( query({ query: notificationQuery, @@ -481,7 +467,7 @@ describe('Hashtags', () => { const title = 'Two Hashtags' const postContent = '

Hey Dude, #Democracy should work equal for everybody!? That seems to be the only way to have equal #Liberty for everyone.

' - const postWithHastagsQuery = gql ` + const postWithHastagsQuery = gql` query($id: ID) { Post(id: $id) { tags { @@ -513,7 +499,8 @@ describe('Hashtags', () => { }) it('both Hashtags are created with the "id" set to their "name"', async () => { - const expected = [{ + const expected = [ + { id: 'Democracy', }, { @@ -528,9 +515,11 @@ describe('Hashtags', () => { ).resolves.toEqual( expect.objectContaining({ data: { - Post: [{ - tags: expect.arrayContaining(expected), - }, ], + Post: [ + { + tags: expect.arrayContaining(expected), + }, + ], }, }), ) @@ -552,7 +541,8 @@ describe('Hashtags', () => { }, }) - const expected = [{ + const expected = [ + { id: 'Elections', }, { @@ -567,9 +557,11 @@ describe('Hashtags', () => { ).resolves.toEqual( expect.objectContaining({ data: { - Post: [{ - tags: expect.arrayContaining(expected), - }, ], + Post: [ + { + tags: expect.arrayContaining(expected), + }, + ], }, }), ) @@ -577,4 +569,4 @@ describe('Hashtags', () => { }) }) }) -}) \ No newline at end of file +}) diff --git a/webapp/components/notifications/Notification/Notification.spec.js b/webapp/components/notifications/Notification/Notification.spec.js index 8fbc524fb..8cc5fa8cc 100644 --- a/webapp/components/notifications/Notification/Notification.spec.js +++ b/webapp/components/notifications/Notification/Notification.spec.js @@ -14,10 +14,11 @@ describe('Notification', () => { let stubs let mocks let propsData + let wrapper beforeEach(() => { propsData = {} mocks = { - $t: jest.fn(), + $t: key => key, } stubs = { NuxtLink: RouterLinkStub, @@ -33,37 +34,159 @@ describe('Notification', () => { }) } - describe('given a notification', () => { + describe('given a notification about a comment on a post', () => { beforeEach(() => { propsData.notification = { - post: { - title: "It's a title", - id: 'post-1', - slug: 'its-a-title', - contentExcerpt: '@jenny-rostock is the best', + reason: 'comment_on_post', + post: null, + comment: { + id: 'comment-1', + contentExcerpt: + '@dagobert-duck is the best on this comment.', + post: { + title: "It's a post title", + id: 'post-1', + slug: 'its-a-title', + contentExcerpt: 'Post content.', + }, }, } }) + it('renders reason', () => { + wrapper = Wrapper() + expect(wrapper.find('.reason-text-for-test').text()).toEqual( + 'notifications.menu.comment_on_post', + ) + }) it('renders title', () => { - expect(Wrapper().text()).toContain("It's a title") + wrapper = Wrapper() + expect(wrapper.text()).toContain("It's a post title") + }) + it('renders the "Comment:"', () => { + wrapper = Wrapper() + expect(wrapper.text()).toContain('Comment:') }) - it('renders the contentExcerpt', () => { - expect(Wrapper().text()).toContain('@jenny-rostock is the best') + wrapper = Wrapper() + expect(wrapper.text()).toContain('@dagobert-duck is the best on this comment.') }) - it('has no class "read"', () => { - expect(Wrapper().classes()).not.toContain('read') + wrapper = Wrapper() + expect(wrapper.classes()).not.toContain('read') }) describe('that is read', () => { beforeEach(() => { propsData.notification.read = true + wrapper = Wrapper() }) it('has class "read"', () => { - expect(Wrapper().classes()).toContain('read') + expect(wrapper.classes()).toContain('read') + }) + }) + }) + + describe('given a notification about a mention in a post', () => { + beforeEach(() => { + propsData.notification = { + reason: 'mentioned_in_post', + post: { + title: "It's a post title", + id: 'post-1', + slug: 'its-a-title', + contentExcerpt: + '@jenny-rostock is the best on this post.', + }, + comment: null, + } + }) + + it('renders reason', () => { + wrapper = Wrapper() + expect(wrapper.find('.reason-text-for-test').text()).toEqual( + 'notifications.menu.mentioned_in_post', + ) + }) + it('renders title', () => { + wrapper = Wrapper() + expect(wrapper.text()).toContain("It's a post title") + }) + it('renders the contentExcerpt', () => { + wrapper = Wrapper() + expect(wrapper.text()).toContain('@jenny-rostock is the best on this post.') + }) + it('has no class "read"', () => { + wrapper = Wrapper() + expect(wrapper.classes()).not.toContain('read') + }) + + describe('that is read', () => { + beforeEach(() => { + propsData.notification.read = true + wrapper = Wrapper() + }) + + it('has class "read"', () => { + expect(wrapper.classes()).toContain('read') + }) + }) + }) + + describe('given a notification about a mention in a comment', () => { + beforeEach(() => { + propsData.notification = { + reason: 'mentioned_in_comment', + post: null, + comment: { + id: 'comment-1', + contentExcerpt: + '@dagobert-duck is the best on this comment.', + post: { + title: "It's a post title", + id: 'post-1', + slug: 'its-a-title', + contentExcerpt: 'Post content.', + }, + }, + } + }) + + it('renders reason', () => { + wrapper = Wrapper() + expect(wrapper.find('.reason-text-for-test').text()).toEqual( + 'notifications.menu.mentioned_in_comment', + ) + }) + it('renders title', () => { + wrapper = Wrapper() + expect(wrapper.text()).toContain("It's a post title") + }) + + it('renders the "Comment:"', () => { + wrapper = Wrapper() + expect(wrapper.text()).toContain('Comment:') + }) + + it('renders the contentExcerpt', () => { + wrapper = Wrapper() + expect(wrapper.text()).toContain('@dagobert-duck is the best on this comment.') + }) + + it('has no class "read"', () => { + wrapper = Wrapper() + expect(wrapper.classes()).not.toContain('read') + }) + + describe('that is read', () => { + beforeEach(() => { + propsData.notification.read = true + wrapper = Wrapper() + }) + + it('has class "read"', () => { + expect(wrapper.classes()).toContain('read') }) }) }) diff --git a/webapp/components/notifications/Notification/Notification.vue b/webapp/components/notifications/Notification/Notification.vue index 8afde5793..8c36cb900 100644 --- a/webapp/components/notifications/Notification/Notification.vue +++ b/webapp/components/notifications/Notification/Notification.vue @@ -10,7 +10,9 @@ />
- {{ $t(notificationTextIdents[notification.reason]) }} + + {{ $t(`notifications.menu.${notification.reason}`) }} + { export const currentUserNotificationsQuery = () => { return gql` - { + query { currentUser { id notifications(read: false, orderBy: createdAt_desc) { diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 2328caaf5..e824d565c 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -10,7 +10,7 @@ "all": "Alle" }, "general": { - "header": "Filtern nach..." + "header": "Filtern nach …" }, "followers": { "label": "Benutzern, denen ich folge" @@ -92,7 +92,7 @@ } }, "editor": { - "placeholder": "Schreib etwas Inspirierendes...", + "placeholder": "Schreib etwas Inspirierendes …", "mention": { "noUsersFound": "Keine Benutzer gefunden" }, @@ -124,9 +124,9 @@ }, "notifications": { "menu": { - "mentionedInPost": "Hat dich in einem Beitrag erwähnt …", - "mentionedInComment": "Hat dich in einem Kommentar erwähnt …", - "commentedOnPost": "Hat deinen Beitrag kommentiert …" + "mentioned_in_post": "Hat dich in einem Beitrag erwähnt …", + "mentioned_in_comment": "Hat dich in einem Kommentar erwähnt …", + "comment_on_post": "Hat deinen Beitrag kommentiert …" } }, "search": { @@ -298,7 +298,7 @@ }, "comment": { "content": { - "unavailable-placeholder": "...dieser Kommentar ist nicht mehr verfügbar" + "unavailable-placeholder": "… dieser Kommentar ist nicht mehr verfügbar" }, "menu": { "edit": "Kommentar bearbeiten", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 04f2310f8..edc6e2d28 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -10,7 +10,7 @@ "all": "All" }, "general": { - "header": "Filter by..." + "header": "Filter by …" }, "followers": { "label": "Users I follow" @@ -92,7 +92,7 @@ } }, "editor": { - "placeholder": "Leave your inspirational thoughts...", + "placeholder": "Leave your inspirational thoughts …", "mention": { "noUsersFound": "No users found" }, @@ -124,9 +124,9 @@ }, "notifications": { "menu": { - "mentionedInPost": "Mentioned you in a post …", - "mentionedInComment": "Mentioned you in a comment …", - "commentedOnPost": "Commented on your post …" + "mentioned_in_post": "Mentioned you in a post …", + "mentioned_in_comment": "Mentioned you in a comment …", + "comment_on_post": "Commented on your post …" } }, "search": { @@ -298,7 +298,7 @@ }, "comment": { "content": { - "unavailable-placeholder": "...this comment is not available anymore" + "unavailable-placeholder": "… this comment is not available anymore" }, "menu": { "edit": "Edit Comment", From 417339b7ccb42c85eea1896e0568a259dd5b0e8b Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Fri, 23 Aug 2019 04:34:42 +0000 Subject: [PATCH 09/43] Bump eslint-config-standard from 14.0.0 to 14.0.1 in /backend Bumps [eslint-config-standard](https://github.com/standard/eslint-config-standard) from 14.0.0 to 14.0.1. - [Release notes](https://github.com/standard/eslint-config-standard/releases) - [Changelog](https://github.com/standard/eslint-config-standard/blob/master/CHANGELOG.md) - [Commits](https://github.com/standard/eslint-config-standard/compare/v14.0.0...v14.0.1) Signed-off-by: dependabot-preview[bot] --- backend/package.json | 2 +- backend/yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/backend/package.json b/backend/package.json index 88de61106..3758b0ced 100644 --- a/backend/package.json +++ b/backend/package.json @@ -118,7 +118,7 @@ "cucumber": "~5.1.0", "eslint": "~6.2.1", "eslint-config-prettier": "~6.1.0", - "eslint-config-standard": "~14.0.0", + "eslint-config-standard": "~14.0.1", "eslint-plugin-import": "~2.18.2", "eslint-plugin-jest": "~22.15.2", "eslint-plugin-node": "~9.1.0", diff --git a/backend/yarn.lock b/backend/yarn.lock index 1f3afb9e0..7d66204d6 100644 --- a/backend/yarn.lock +++ b/backend/yarn.lock @@ -3371,10 +3371,10 @@ eslint-config-prettier@~6.1.0: dependencies: get-stdin "^6.0.0" -eslint-config-standard@~14.0.0: - version "14.0.0" - resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.0.0.tgz#1de7bf5af37542dc6eef879ab7eb5e5e0f830747" - integrity sha512-bV6e2LFvJEetrLjVAy4KWPOUsIhPWr040c649MigTPR6yUtaGuOt6CEAyNeez2lRiC+2+vjGWa02byjs25EB3A== +eslint-config-standard@~14.0.1: + version "14.0.1" + resolved "https://registry.yarnpkg.com/eslint-config-standard/-/eslint-config-standard-14.0.1.tgz#375c3636fb4bd453cb95321d873de12e4eef790b" + integrity sha512-1RWsAKTDTZgA8bIM6PSC9aTGDAUlKqNkYNJlTZ5xYD/HYkIM6GlcefFvgcJ8xi0SWG5203rttKYX28zW+rKNOg== eslint-import-resolver-node@^0.3.2: version "0.3.2" From 773779e5f1eed6ba7b9e90068c7221a7fb728a36 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 23 Aug 2019 10:57:11 +0200 Subject: [PATCH 10/43] Fix UpdatePost resolver/validations --- .../validation/validationMiddleware.js | 36 +++++- backend/src/schema/resolvers/posts.js | 24 ++-- backend/src/schema/resolvers/posts.spec.js | 103 ++++++++++++------ 3 files changed, 117 insertions(+), 46 deletions(-) diff --git a/backend/src/middleware/validation/validationMiddleware.js b/backend/src/middleware/validation/validationMiddleware.js index 6094911b1..867d7dc8e 100644 --- a/backend/src/middleware/validation/validationMiddleware.js +++ b/backend/src/middleware/validation/validationMiddleware.js @@ -2,6 +2,8 @@ import { UserInputError } from 'apollo-server' const COMMENT_MIN_LENGTH = 1 const NO_POST_ERR_MESSAGE = 'Comment cannot be created without a post!' +const NO_CATEGORIES_ERR_MESSAGE = + 'You cannot save a post without at least one category or more than three' const validateCommentCreation = async (resolve, root, args, context, info) => { const content = args.content.replace(/<(?:.|\n)*?>/gm, '').trim() @@ -19,6 +21,7 @@ const validateCommentCreation = async (resolve, root, args, context, info) => { postId, }, ) + session.close() const [post] = postQueryRes.records.map(record => { return record.get('post') }) @@ -43,9 +46,34 @@ const validateUpdateComment = async (resolve, root, args, context, info) => { const validatePost = async (resolve, root, args, context, info) => { const { categoryIds } = args if (!Array.isArray(categoryIds) || !categoryIds.length || categoryIds.length > 3) { - throw new UserInputError( - 'You cannot save a post without at least one category or more than three', - ) + throw new UserInputError(NO_CATEGORIES_ERR_MESSAGE) + } + return resolve(root, args, context, info) +} + +const validateUpdatePost = async (resolve, root, args, context, info) => { + const { id, categoryIds } = args + const session = context.driver.session() + const categoryQueryRes = await session.run( + ` + MATCH (post:Post {id: $id})-[:CATEGORIZED]->(category:Category) + RETURN category`, + { id }, + ) + session.close() + const [category] = categoryQueryRes.records.map(record => { + return record.get('category') + }) + + if (category) { + if (categoryIds && categoryIds.length > 3) { + throw new UserInputError(NO_CATEGORIES_ERR_MESSAGE) + } + // return resolve(root, args, context, info) + } else { + if (!Array.isArray(categoryIds) || !categoryIds.length || categoryIds.length > 3) { + throw new UserInputError(NO_CATEGORIES_ERR_MESSAGE) + } } return resolve(root, args, context, info) } @@ -55,6 +83,6 @@ export default { CreateComment: validateCommentCreation, UpdateComment: validateUpdateComment, CreatePost: validatePost, - UpdatePost: validatePost, + UpdatePost: validateUpdatePost, }, } diff --git a/backend/src/schema/resolvers/posts.js b/backend/src/schema/resolvers/posts.js index 5bb0c4f81..46d7c414f 100644 --- a/backend/src/schema/resolvers/posts.js +++ b/backend/src/schema/resolvers/posts.js @@ -75,22 +75,28 @@ export default { delete params.categoryIds params = await fileUpload(params, { file: 'imageUpload', url: 'image' }) const session = context.driver.session() - const cypherDeletePreviousRelations = ` - MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category) - DELETE previousRelations - RETURN post, category + + let updatePostCypher = `MATCH (post:Post {id: $params.id}) + SET post = $params ` - await session.run(cypherDeletePreviousRelations, { params }) + if (categoryIds && categoryIds.length) { + const cypherDeletePreviousRelations = ` + MATCH (post:Post { id: $params.id })-[previousRelations:CATEGORIZED]->(category:Category) + DELETE previousRelations + RETURN post, category + ` - const updatePostCypher = `MATCH (post:Post {id: $params.id}) - SET post = $params - WITH post + await session.run(cypherDeletePreviousRelations, { params }) + + updatePostCypher += `WITH post UNWIND $categoryIds AS categoryId MATCH (category:Category {id: categoryId}) MERGE (post)-[:CATEGORIZED]->(category) - RETURN post` + ` + } + updatePostCypher += `RETURN post` const updatePostVariables = { categoryIds, params } const transactionRes = await session.run(updatePostCypher, updatePostVariables) diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js index 44618ecdc..e8f4bff6e 100644 --- a/backend/src/schema/resolvers/posts.spec.js +++ b/backend/src/schema/resolvers/posts.spec.js @@ -13,6 +13,7 @@ let client let userParams let authorParams +const postId = 'p3589' const postTitle = 'I am a title' const postContent = 'Some content' const oldTitle = 'Old title' @@ -96,7 +97,7 @@ beforeEach(async () => { }), ]) createPostVariables = { - id: 'p3589', + id: postId, title: postTitle, content: postContent, categoryIds, @@ -218,7 +219,7 @@ describe('CreatePost', () => { Post: [ { title: postTitle, - id: 'p3589', + id: postId, categories: expect.arrayContaining(categoryIdsArray), }, ], @@ -246,17 +247,16 @@ describe('UpdatePost', () => { await asAuthor.create('User', authorParams) await asAuthor.authenticateAs(authorParams) await asAuthor.create('Post', { - id: 'p1', + id: postId, title: oldTitle, content: oldContent, categoryIds, }) updatePostVariables = { - id: 'p1', + id: postId, title: newTitle, content: newContent, - categoryIds: null, } }) @@ -291,55 +291,92 @@ describe('UpdatePost', () => { }) it('updates a post', async () => { - updatePostVariables.categoryIds = ['cat9'] - const expected = { UpdatePost: { id: 'p1', content: newContent } } + updatePostVariables.categoryIds = ['cat27'] + const expected = { UpdatePost: { id: postId, content: newContent } } await expect(client.request(updatePostMutation, updatePostVariables)).resolves.toEqual( expected, ) }) describe('categories', () => { - beforeEach(async () => { - await client.request(createPostMutation, createPostVariables) - updatePostVariables = { - id: 'p3589', - title: newTitle, - content: newContent, - categoryIds: ['cat27'], - } + it('allows a user to update other attributes without passing in categoryIds explicitly', async () => { + const expected = { UpdatePost: { id: postId, content: newContent } } + await expect(client.request(updatePostMutation, updatePostVariables)).resolves.toEqual( + expected, + ) }) it('allows a user to update the categories of a post', async () => { + updatePostVariables.categoryIds = ['cat27'] await client.request(updatePostMutation, updatePostVariables) const expected = [{ id: 'cat27' }] const postQueryWithCategoriesVariables = { - id: 'p3589', + id: postId, } await expect( client.request(postQueryWithCategories, postQueryWithCategoriesVariables), ).resolves.toEqual({ Post: [{ categories: expect.arrayContaining(expected) }] }) }) - it('throws an error if categoryIds is not an array', async () => { - updatePostVariables.categoryIds = null - await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow( - postSaveError, - ) - }) - - it('requires at least one category for successful update', async () => { - updatePostVariables.categoryIds = [] - await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow( - postSaveError, - ) - }) - it('allows a maximum of three category for a successful update', async () => { updatePostVariables.categoryIds = ['cat9', 'cat27', 'cat15', 'cat4'] await expect(client.request(updatePostMutation, updatePostVariables)).rejects.toThrow( postSaveError, ) }) + + describe('post created without categories somehow', () => { + let ownerNode, owner, postMutationAction + beforeEach(async () => { + authorParams.id = 'author-of-post-without-category' + const postSomehowCreated = await instance.create('Post', { + id: 'how-was-this-created', + title: postTitle, + content: postContent, + }) + ownerNode = await instance.create('User', authorParams) + owner = await ownerNode.toJson() + postSomehowCreated.relateTo(ownerNode, 'author') + postMutationAction = async (user, mutation, variables) => { + const { server } = createServer({ + context: () => { + return { + user, + neode: instance, + driver, + } + }, + }) + const { mutate } = createTestClient(server) + + return mutate({ + mutation, + variables, + }) + } + updatePostVariables.id = 'how-was-this-created' + }) + + it('throws an error if categoryIds is not an array', async () => { + updatePostVariables.categoryIds = null + const mustAddCategoryToPost = await postMutationAction( + owner, + updatePostMutation, + updatePostVariables, + ) + expect(mustAddCategoryToPost.errors[0]).toHaveProperty('message', postSaveError) + }) + + it('requires at least one category for successful update', async () => { + updatePostVariables.categoryIds = [] + const mustAddCategoryToPost = await postMutationAction( + owner, + updatePostMutation, + updatePostVariables, + ) + expect(mustAddCategoryToPost.errors[0]).toHaveProperty('message', postSaveError) + }) + }) }) }) }) @@ -355,7 +392,7 @@ describe('DeletePost', () => { ` const variables = { - id: 'p1', + id: postId, } beforeEach(async () => { @@ -363,7 +400,7 @@ describe('DeletePost', () => { await asAuthor.create('User', authorParams) await asAuthor.authenticateAs(authorParams) await asAuthor.create('Post', { - id: 'p1', + id: postId, content: 'To be deleted', categoryIds, }) @@ -396,7 +433,7 @@ describe('DeletePost', () => { }) it('deletes a post', async () => { - const expected = { DeletePost: { id: 'p1', content: 'To be deleted' } } + const expected = { DeletePost: { id: postId, content: 'To be deleted' } } await expect(client.request(mutation, variables)).resolves.toEqual(expected) }) }) From abbfc6d7570441fb822f0d2aedabc371ac0d5406 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 23 Aug 2019 11:30:20 +0200 Subject: [PATCH 11/43] Remove commented out, unneeded code --- backend/src/middleware/validation/validationMiddleware.js | 1 - 1 file changed, 1 deletion(-) diff --git a/backend/src/middleware/validation/validationMiddleware.js b/backend/src/middleware/validation/validationMiddleware.js index 867d7dc8e..134c85c0c 100644 --- a/backend/src/middleware/validation/validationMiddleware.js +++ b/backend/src/middleware/validation/validationMiddleware.js @@ -69,7 +69,6 @@ const validateUpdatePost = async (resolve, root, args, context, info) => { if (categoryIds && categoryIds.length > 3) { throw new UserInputError(NO_CATEGORIES_ERR_MESSAGE) } - // return resolve(root, args, context, info) } else { if (!Array.isArray(categoryIds) || !categoryIds.length || categoryIds.length > 3) { throw new UserInputError(NO_CATEGORIES_ERR_MESSAGE) From cd5e492569dff26682cd094b66f79c5099ec4b74 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 23 Aug 2019 12:18:53 +0200 Subject: [PATCH 12/43] Await relation be added --- backend/src/schema/resolvers/posts.spec.js | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/backend/src/schema/resolvers/posts.spec.js b/backend/src/schema/resolvers/posts.spec.js index e8f4bff6e..62507af0e 100644 --- a/backend/src/schema/resolvers/posts.spec.js +++ b/backend/src/schema/resolvers/posts.spec.js @@ -328,15 +328,20 @@ describe('UpdatePost', () => { describe('post created without categories somehow', () => { let ownerNode, owner, postMutationAction beforeEach(async () => { - authorParams.id = 'author-of-post-without-category' const postSomehowCreated = await instance.create('Post', { id: 'how-was-this-created', title: postTitle, content: postContent, }) - ownerNode = await instance.create('User', authorParams) + ownerNode = await instance.create('User', { + id: 'author-of-post-without-category', + name: 'Hacker', + slug: 'hacker', + email: 'hacker@example.org', + password: '1234', + }) owner = await ownerNode.toJson() - postSomehowCreated.relateTo(ownerNode, 'author') + await postSomehowCreated.relateTo(ownerNode, 'author') postMutationAction = async (user, mutation, variables) => { const { server } = createServer({ context: () => { @@ -358,7 +363,6 @@ describe('UpdatePost', () => { }) it('throws an error if categoryIds is not an array', async () => { - updatePostVariables.categoryIds = null const mustAddCategoryToPost = await postMutationAction( owner, updatePostMutation, From 79e6b5639697d9f09ad04c69382789f48d147826 Mon Sep 17 00:00:00 2001 From: Matt Rider Date: Fri, 23 Aug 2019 12:35:53 +0200 Subject: [PATCH 13/43] Use ES6 syntax to avoid repetition --- backend/src/middleware/index.js | 26 +++++++++++++------------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/backend/src/middleware/index.js b/backend/src/middleware/index.js index 3407c6874..57bdabfc9 100644 --- a/backend/src/middleware/index.js +++ b/backend/src/middleware/index.js @@ -18,19 +18,19 @@ import sentry from './sentryMiddleware' export default schema => { const middlewares = { - permissions: permissions, - sentry: sentry, - activityPub: activityPub, - dateTime: dateTime, - validation: validation, - sluggify: sluggify, - excerpt: excerpt, - handleContentData: handleContentData, - xss: xss, - softDelete: softDelete, - user: user, - includedFields: includedFields, - orderBy: orderBy, + permissions, + sentry, + activityPub, + dateTime, + validation, + sluggify, + excerpt, + handleContentData, + xss, + softDelete, + user, + includedFields, + orderBy, email: email({ isEnabled: CONFIG.SMTP_HOST && CONFIG.SMTP_PORT }), } From 66612801ce3085451a8775a4b60ff3359e1401cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 23 Aug 2019 13:35:22 +0200 Subject: [PATCH 14/43] Change text of invite form --- webapp/components/Registration/Signup.vue | 16 ++++++++++------ webapp/locales/de.json | 9 +++++++-- webapp/locales/en.json | 9 +++++++-- webapp/pages/admin/invite.vue | 11 ++++------- 4 files changed, 28 insertions(+), 17 deletions(-) diff --git a/webapp/components/Registration/Signup.vue b/webapp/components/Registration/Signup.vue index 175db1fd6..dcf4f0e88 100644 --- a/webapp/components/Registration/Signup.vue +++ b/webapp/components/Registration/Signup.vue @@ -9,17 +9,21 @@ :schema="formSchema" @submit="handleSubmit" > -

{{ $t('registration.signup.title') }}

+

{{ invitation ? $t('profile.invites.title') : $t('registration.signup.title') }}

- {{ $t('registration.signup.form.description') }} + {{ + invitation + ? $t('profile.invites.description') + : $t('registration.signup.form.description') + }}
@@ -73,11 +75,13 @@ export const SignupByInvitationMutation = gql` } ` export default { + name: 'Signup', components: { SweetalertIcon, }, props: { token: { type: String, default: null }, + invitation: { type: Boolean, default: false }, }, data() { return { diff --git a/webapp/locales/de.json b/webapp/locales/de.json index e249ce160..06b90826a 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -124,6 +124,11 @@ "followedByNobody": "wird von niemandem gefolgt.", "and": "und", "more": "weitere" + }, + "invites": { + "title": "Lade jemanden zu Human Connection ein!", + "description": "Für die Einladung trage seine E-Mail-Adresse hier ein.", + "emailPlaceholder": "E-Mail-Adresse für die Einladung" } }, "notifications": { @@ -271,8 +276,8 @@ }, "invites": { "name": "Benutzer einladen", - "title": "Benutzer als Admin anmelden", - "description": "Dieses Anmeldeformular ist zu sehen sobald die Anmeldung öffentlich zugänglich ist." + "title": "Leute einladen", + "description": "Einladungen sind ein wunderbarer Weg, deine Freund in deinem Netzwerk zu haben …" } }, "post": { diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 7f9e7286b..40ce5b6a2 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -124,6 +124,11 @@ "followedByNobody": "is not followed by anyone.", "and": "and", "more": "more" + }, + "invites": { + "title": "Invite somebody to Human Connection!", + "description": "Enter thier email address for invitation.", + "emailPlaceholder": "Email to invite" } }, "notifications": { @@ -271,8 +276,8 @@ }, "invites": { "name": "Invite users", - "title": "Signup users as admin", - "description": "This registration form will be visible as soon as the registration is open to the public." + "title": "Invite people", + "description": "Invitations are a wonderful way to have your friends in your network …" } }, "post": { diff --git a/webapp/pages/admin/invite.vue b/webapp/pages/admin/invite.vue index 36e679112..3f9b4d831 100644 --- a/webapp/pages/admin/invite.vue +++ b/webapp/pages/admin/invite.vue @@ -1,14 +1,11 @@ From a10e2e3a9bc37654113a1646d94271d978b81d90 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 23 Aug 2019 13:51:36 +0200 Subject: [PATCH 15/43] Make user profile follow(ed) lists exceeds 5 users `and X more` better translatable MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Slite adjustment of `more` to `more …` --- webapp/locales/de.json | 3 +-- webapp/locales/en.json | 3 +-- webapp/pages/profile/_id/_slug.vue | 14 ++++++++++---- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/webapp/locales/de.json b/webapp/locales/de.json index 06b90826a..99689ec44 100644 --- a/webapp/locales/de.json +++ b/webapp/locales/de.json @@ -122,8 +122,7 @@ "followingNobody": "folgt niemandem.", "followedBy": "wird gefolgt von:", "followedByNobody": "wird von niemandem gefolgt.", - "and": "und", - "more": "weitere" + "andMore": "und {number} weitere …" }, "invites": { "title": "Lade jemanden zu Human Connection ein!", diff --git a/webapp/locales/en.json b/webapp/locales/en.json index 40ce5b6a2..9c9d1cd73 100644 --- a/webapp/locales/en.json +++ b/webapp/locales/en.json @@ -122,8 +122,7 @@ "followingNobody": "follows nobody.", "followedBy": "is followed by:", "followedByNobody": "is not followed by anyone.", - "and": "and", - "more": "more" + "andMore": "and {number} more …" }, "invites": { "title": "Invite somebody to Human Connection!", diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index bc712e6e0..e4f99935d 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -95,8 +95,11 @@ - {{ $t('profile.network.and') }} {{ user.followingCount - user.following.length }} - {{ $t('profile.network.more') }} + {{ + $t('profile.network.andMore', { + number: user.followingCount - user.following.length, + }) + }} @@ -122,8 +125,11 @@ - {{ $t('profile.network.and') }} {{ user.followedByCount - user.followedBy.length }} - {{ $t('profile.network.more') }} + {{ + $t('profile.network.andMore', { + number: user.followedByCount - user.followedBy.length, + }) + }} From 123c30b3015f8a10fcbf3e9b8f7899f7632cf2be Mon Sep 17 00:00:00 2001 From: roschaefer Date: Fri, 23 Aug 2019 13:15:19 +0200 Subject: [PATCH 16/43] Fix "cannot read property id of null" in staging --- backend/src/middleware/sentryMiddleware.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backend/src/middleware/sentryMiddleware.js b/backend/src/middleware/sentryMiddleware.js index b1130ad37..da8ef32d0 100644 --- a/backend/src/middleware/sentryMiddleware.js +++ b/backend/src/middleware/sentryMiddleware.js @@ -14,13 +14,16 @@ if (sentryConfigs.SENTRY_DSN_BACKEND) { }, withScope: (scope, error, context) => { scope.setUser({ - id: context.user.id, + id: context.user && context.user.id, }) scope.setExtra('body', context.req.body) scope.setExtra('origin', context.req.headers.origin) scope.setExtra('user-agent', context.req.headers['user-agent']) }, }) +} else { + // eslint-disable-next-line no-console + if (process.env.NODE_ENV !== 'test') console.log('Warning: Sentry middleware inactive.') } export default sentryMiddleware From 2600c69185933525d894f34d89985aa0fd070214 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Wolfgang=20Hu=C3=9F?= Date: Fri, 23 Aug 2019 15:57:31 +0200 Subject: [PATCH 17/43] Change code after @mattwr18 suggestions Co-Authored-By: mattwr18 --- .../handleNotifications/handleNotificationsMiddleware.js | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js index b56a1384e..dcd1be1d9 100644 --- a/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js +++ b/backend/src/middleware/handleNotifications/handleNotificationsMiddleware.js @@ -1,20 +1,19 @@ -import { UserInputError } from 'apollo-server' import extractMentionedUsers from './notifications/extractMentionedUsers' import extractHashtags from './hashtags/extractHashtags' const notifyUsers = async (label, id, idsOfUsers, reason, context) => { if (!idsOfUsers.length) return - // Done here, because Neode validation is not working. + // Checked here, because it does not go through GraphQL checks at all in this file. const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'comment_on_post'] if (!reasonsAllowed.includes(reason)) { - throw new UserInputError('Notification reason is not allowed!') + throw new Error('Notification reason is not allowed!') } if ( (label === 'Post' && reason !== 'mentioned_in_post') || (label === 'Comment' && !['mentioned_in_comment', 'comment_on_post'].includes(reason)) ) { - throw new UserInputError('Notification fits not to reason!') + throw new Error('Notification does not fit the reason!') } const session = context.driver.session() From 894a59ecc7700472317c580d19df867c08c57b66 Mon Sep 17 00:00:00 2001 From: roschaefer Date: Fri, 23 Aug 2019 19:57:58 +0200 Subject: [PATCH 18/43] Fix flickering create-button --- webapp/pages/profile/_id/_slug.vue | 30 ++++++++++++++++-------------- 1 file changed, 16 insertions(+), 14 deletions(-) diff --git a/webapp/pages/profile/_id/_slug.vue b/webapp/pages/profile/_id/_slug.vue index e4f99935d..b33d88bda 100644 --- a/webapp/pages/profile/_id/_slug.vue +++ b/webapp/pages/profile/_id/_slug.vue @@ -201,16 +201,22 @@ - - + + + + diff --git a/webapp/components/EditCommentForm/EditCommentForm.vue b/webapp/components/EditCommentForm/EditCommentForm.vue index e647131b8..0a5cd7d98 100644 --- a/webapp/components/EditCommentForm/EditCommentForm.vue +++ b/webapp/components/EditCommentForm/EditCommentForm.vue @@ -2,7 +2,7 @@ diff --git a/webapp/components/User/index.vue b/webapp/components/User/index.vue index 0fefe4eb3..684220f38 100644 --- a/webapp/components/User/index.vue +++ b/webapp/components/User/index.vue @@ -25,9 +25,9 @@
- + - +
diff --git a/webapp/components/notifications/Notification/Notification.spec.js b/webapp/components/notifications/Notification/Notification.spec.js index 8fbc524fb..fce2a94b2 100644 --- a/webapp/components/notifications/Notification/Notification.spec.js +++ b/webapp/components/notifications/Notification/Notification.spec.js @@ -8,7 +8,7 @@ const localVue = createLocalVue() localVue.use(Styleguide) localVue.use(Filters) -config.stubs['no-ssr'] = '' +config.stubs['client-only'] = '' describe('Notification', () => { let stubs diff --git a/webapp/components/notifications/Notification/Notification.vue b/webapp/components/notifications/Notification/Notification.vue index 6aa4a5eeb..686938a85 100644 --- a/webapp/components/notifications/Notification/Notification.vue +++ b/webapp/components/notifications/Notification/Notification.vue @@ -1,6 +1,6 @@ diff --git a/webapp/pages/admin/index.vue b/webapp/pages/admin/index.vue index ca8ce4df7..f764238e3 100644 --- a/webapp/pages/admin/index.vue +++ b/webapp/pages/admin/index.vue @@ -1,23 +1,23 @@ diff --git a/webapp/pages/index.spec.js b/webapp/pages/index.spec.js index 6dacd6069..3a97e3709 100644 --- a/webapp/pages/index.spec.js +++ b/webapp/pages/index.spec.js @@ -15,7 +15,7 @@ localVue.use(Filters) localVue.use(VTooltip) localVue.use(InfiniteScroll) -config.stubs['no-ssr'] = '' +config.stubs['client-only'] = '' config.stubs['router-link'] = '' config.stubs['nuxt-link'] = '' diff --git a/webapp/pages/index.vue b/webapp/pages/index.vue index 910fa7a2f..1224f6f48 100644 --- a/webapp/pages/index.vue +++ b/webapp/pages/index.vue @@ -36,7 +36,7 @@
- + - +