diff --git a/backend/src/middleware/handleNotifications/handleNotifications.js b/backend/src/middleware/handleNotifications/handleNotifications.js index 6b8368d44..cbb305fe5 100644 --- a/backend/src/middleware/handleNotifications/handleNotifications.js +++ b/backend/src/middleware/handleNotifications/handleNotifications.js @@ -15,16 +15,27 @@ const notifyUsers = async (label, id, idsOfUsers, reason, context) => { const session = context.driver.session() const createdAt = new Date().toISOString() - const cypher = ` - MATCH (source) - WHERE source.id = $id AND $label IN LABELS(source) - MATCH (source)<-[:WROTE]-(author: User) - MATCH (u: User) - WHERE u.id in $idsOfUsers - AND NOT (u)<-[:BLOCKED]-(author) - CREATE (n: Notification { id: apoc.create.uuid(), read: false, reason: $reason, createdAt: $createdAt }) - MERGE (source)-[:NOTIFIED]->(n)-[:NOTIFIED]->(u) + 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) + ` + } await session.run(cypher, { label, id, @@ -65,12 +76,9 @@ 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 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 notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context) @@ -80,10 +88,7 @@ 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 idsOfUsers = extractMentionedUsers(args.content) - - // removes classes from the content const comment = await resolve(root, args, context, resolveInfo) await notifyUsers('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context) diff --git a/backend/src/middleware/handleNotifications/handleNotifications.spec.js b/backend/src/middleware/handleNotifications/handleNotifications.spec.js index fb79c0475..205c1ecb0 100644 --- a/backend/src/middleware/handleNotifications/handleNotifications.spec.js +++ b/backend/src/middleware/handleNotifications/handleNotifications.spec.js @@ -60,6 +60,9 @@ describe('notifications', () => { post { content } + comment { + content + } } } } @@ -71,12 +74,12 @@ describe('notifications', () => { }) describe('given another user', () => { - let author + let postAuthor beforeEach(async () => { - author = await instance.create('User', { - email: 'author@example.org', + postAuthor = await instance.create('User', { + email: 'post-author@example.org', password: '1234', - id: 'author', + id: 'postAuthor', }) }) @@ -95,19 +98,19 @@ describe('notifications', () => { } } ` - authenticatedUser = await author.toJson() + authenticatedUser = await postAuthor.toJson() await mutate({ mutation: createPostMutation, variables: { id: 'p47', title, - content + content, }, }) authenticatedUser = await user.toJson() } - it('sends you a notification', async () => { + it.only('sends you a notification', async () => { await createPostAction() const expectedContent = 'Hey @al-capone how do you do?' @@ -118,9 +121,10 @@ describe('notifications', () => { read: false, reason: 'mentioned_in_post', post: { - content: expectedContent - } - }] + content: expectedContent, + }, + comment: null, + }, ], }, }, }) @@ -131,8 +135,8 @@ describe('notifications', () => { query({ query: notificationQuery, variables: { - read: false - } + read: false, + }, }), ).resolves.toEqual(expected) }) @@ -161,7 +165,7 @@ describe('notifications', () => { } } ` - authenticatedUser = await author.toJson() + authenticatedUser = await postAuthor.toJson() await mutate({ mutation: updatePostMutation, variables: { @@ -185,15 +189,17 @@ describe('notifications', () => { read: false, reason: 'mentioned_in_post', post: { - content: expectedContent - } + content: expectedContent, + }, + comment: null, }, { read: false, reason: 'mentioned_in_post', post: { - content: expectedContent - } + content: expectedContent, + }, + comment: null, }, ], }, @@ -203,8 +209,8 @@ describe('notifications', () => { query({ query: notificationQuery, variables: { - read: false - } + read: false, + }, }), ).resolves.toEqual(expected) }) @@ -212,7 +218,7 @@ describe('notifications', () => { describe('but the author of the post blocked me', () => { beforeEach(async () => { - await author.relateTo(user, 'blocked') + await postAuthor.relateTo(user, 'blocked') }) it('sends no notification', async () => { @@ -220,8 +226,8 @@ describe('notifications', () => { const expected = expect.objectContaining({ data: { currentUser: { - notifications: [] - } + notifications: [], + }, }, }) const { @@ -231,8 +237,66 @@ describe('notifications', () => { query({ query: notificationQuery, variables: { - read: false + read: false, + }, + }), + ).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 .', + }, + }) + authenticatedUser = await user.toJson() + } + let commentMentioner + + beforeEach(async () => { + await postAuthor.relateTo(user, 'blocked') + commentMentioner = await instance.create('User', { + id: 'mentioner', + name: 'Mr Mentioner', + slug: 'mr-mentioner', + email: 'mentioner@example.org', + password: '1234', + }) + }) + + it('sends no notification', async () => { + await createCommentOnPostAction() + const expected = expect.objectContaining({ + data: { + currentUser: { + notifications: [], + }, + }, + }) + const { + query + } = createTestClient(server) + await expect( + query({ + query: notificationQuery, + variables: { + read: false, + }, }), ).resolves.toEqual(expected) }) @@ -287,11 +351,15 @@ 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, - variables: postWithHastagsVariables + variables: postWithHastagsVariables, }), ).resolves.toEqual( expect.objectContaining({ @@ -328,18 +396,22 @@ describe('Hashtags', () => { }, }) - const expected = [{ id: 'Elections' }, { id: 'Liberty' }] + const expected = [{ + id: 'Elections' + }, { + id: 'Liberty' + }] await expect( query({ query: postWithHastagsQuery, - variables: postWithHastagsVariables + variables: postWithHastagsVariables, }), ).resolves.toEqual( expect.objectContaining({ data: { Post: [{ - tags: expect.arrayContaining(expected) - }], + tags: expect.arrayContaining(expected), + }, ], }, }), ) diff --git a/webapp/components/notifications/Notification/Notification.vue b/webapp/components/notifications/Notification/Notification.vue index a982e33a9..5ccc38993 100644 --- a/webapp/components/notifications/Notification/Notification.vue +++ b/webapp/components/notifications/Notification/Notification.vue @@ -3,17 +3,19 @@ + {{ $t(notificationTextIdents[notification.reason]) }} @@ -24,9 +26,14 @@ class="notifications-card" > - -
- +
{{ post.contentExcerpt | removeHtml }}
+
+ + Comment: + + + {{ comment.contentExcerpt | removeHtml }} +
@@ -57,11 +64,8 @@ export default { } }, computed: { - excerpt() { - const excerpt = this.post.id ? this.post.contentExcerpt : this.comment.contentExcerpt - return ( - (!this.post.id ? 'Comment: ' : '') + excerpt.replace(/<(?:.|\n)*?>/gm, '').trim() - ) + resourceType() { + return this.post.id ? 'Post' : 'Comment' }, post() { return this.notification.post || {} @@ -69,7 +73,7 @@ export default { comment() { return this.notification.comment || {} }, - postParams() { + params() { return { id: this.post.id || this.comment.post.id, slug: this.post.slug || this.comment.post.slug, diff --git a/webapp/components/notifications/NotificationMenu/spec.js b/webapp/components/notifications/NotificationMenu/NotificationMenu.spec.js similarity index 97% rename from webapp/components/notifications/NotificationMenu/spec.js rename to webapp/components/notifications/NotificationMenu/NotificationMenu.spec.js index 673a85944..b8d988b58 100644 --- a/webapp/components/notifications/NotificationMenu/spec.js +++ b/webapp/components/notifications/NotificationMenu/NotificationMenu.spec.js @@ -1,5 +1,5 @@ import { config, shallowMount, createLocalVue } from '@vue/test-utils' -import NotificationMenu from '.' +import NotificationMenu from './NotificationMenu' import Styleguide from '@human-connection/styleguide' import Filters from '~/plugins/vue-filters' diff --git a/webapp/components/notifications/NotificationMenu/index.vue b/webapp/components/notifications/NotificationMenu/NotificationMenu.vue similarity index 54% rename from webapp/components/notifications/NotificationMenu/index.vue rename to webapp/components/notifications/NotificationMenu/NotificationMenu.vue index 82c1c0dff..c534f2986 100644 --- a/webapp/components/notifications/NotificationMenu/index.vue +++ b/webapp/components/notifications/NotificationMenu/NotificationMenu.vue @@ -17,81 +17,9 @@