diff --git a/backend/src/db/seed.js b/backend/src/db/seed.js index bb9243f90..f0a6480bb 100644 --- a/backend/src/db/seed.js +++ b/backend/src/db/seed.js @@ -553,7 +553,7 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] ]) authenticatedUser = null - const comments = await Promise.all([ + const [trollingCommentC1, trollingCommentC2] = await Promise.all([ factory.create('Comment', { author: jennyRostock, id: 'c1', @@ -610,8 +610,6 @@ const languages = ['de', 'en', 'es', 'fr', 'it', 'pt', 'pl'] postId: 'p15', }), ]) - const trollingCommentC1 = comments[0] - const trollingCommentC2 = comments[1] await Promise.all([ democracy.relateTo(p3, 'post'), diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index e927d5979..cf5657171 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -1,7 +1,7 @@ import extractMentionedUsers from './mentions/extractMentionedUsers' import { validateNotifyUsers } from '../validation/validationMiddleware' -const debug = require('debug')('backend:notificationsMiddleware') +const debug = require('debug')('human-connection-backend:notificationsMiddleware') const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { const idsOfUsers = extractMentionedUsers(args.content) @@ -73,7 +73,8 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { } mentionedCypher += ` SET notification.read = FALSE - SET ( CASE WHEN notification.createdAt IS NULL THEN notification END ).createdAt = toString(datetime()) + // Wolle SET ( CASE WHEN notification.createdAt IS NULL THEN notification END ).createdAt = toString(datetime()) + SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.updatedAt = toString(datetime()) ` const session = context.driver.session() @@ -100,7 +101,7 @@ const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, cont WHERE NOT (postAuthor)-[:BLOCKED]-(commenter) MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(postAuthor) SET notification.read = FALSE - SET ( CASE WHEN notification.createdAt IS NULL THEN notification END ).createdAt = toString(datetime()) + SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) SET notification.updatedAt = toString(datetime()) `, { commentId, postAuthorId, reason }, diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.js b/backend/src/middleware/notifications/notificationsMiddleware.spec.js index fb12c2c61..ac1ebf407 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.js @@ -1,13 +1,12 @@ import { gql } from '../../helpers/jest' import Factory from '../../factories' import { createTestClient } from 'apollo-server-testing' -import { getNeode, getDriver } from '../../db/neo4j' +import { getDriver } from '../../db/neo4j' import createServer from '../../server' let server, query, mutate, notifiedUser, authenticatedUser const factory = Factory() const driver = getDriver() -const neode = getNeode() const categoryIds = ['cat9'] const createPostMutation = gql` mutation($id: ID, $title: String!, $postContent: String!, $categoryIds: [ID]!) { @@ -52,7 +51,6 @@ beforeAll(async () => { context: () => { return { user: authenticatedUser, - neode: neode, driver, } }, @@ -64,14 +62,14 @@ beforeAll(async () => { }) beforeEach(async () => { - notifiedUser = await neode.create('User', { + notifiedUser = await factory.create('User', { id: 'you', name: 'Al Capone', slug: 'al-capone', email: 'test@example.org', password: '1234', }) - await neode.create('Category', { + await factory.create('Category', { id: 'cat9', name: 'Democracy & Politics', icon: 'university', @@ -175,7 +173,7 @@ describe('notifications', () => { describe('commenter is not me', () => { beforeEach(async () => { commentContent = 'Commenters comment.' - commentAuthor = await neode.create('User', { + commentAuthor = await factory.create('User', { id: 'commentAuthor', name: 'Mrs Comment', slug: 'mrs-comment', @@ -256,7 +254,7 @@ describe('notifications', () => { }) beforeEach(async () => { - postAuthor = await neode.create('User', { + postAuthor = await factory.create('User', { id: 'postAuthor', name: 'Mrs Post', slug: 'mrs-post', @@ -460,7 +458,7 @@ describe('notifications', () => { beforeEach(async () => { commentContent = 'One mention about me with @al-capone.' - commentAuthor = await neode.create('User', { + commentAuthor = await factory.create('User', { id: 'commentAuthor', name: 'Mrs Comment', slug: 'mrs-comment', @@ -470,11 +468,11 @@ describe('notifications', () => { }) it('sends only one notification with reason mentioned_in_comment', async () => { - postAuthor = await neode.create('User', { - id: 'MrPostAuthor', + postAuthor = await factory.create('User', { + id: 'MrAuthor', name: 'Mr Author', slug: 'mr-author', - email: 'post-author@example.org', + email: 'mr-author@example.org', password: '1234', }) @@ -546,7 +544,7 @@ describe('notifications', () => { await postAuthor.relateTo(notifiedUser, 'blocked') commentContent = 'One mention about me with @al-capone.' - commentAuthor = await neode.create('User', { + commentAuthor = await factory.create('User', { id: 'commentAuthor', name: 'Mrs Comment', slug: 'mrs-comment', @@ -630,7 +628,7 @@ describe('notifications', () => { describe('user', () => { it('sends me a notification for filing a report on a user', async () => { - await neode.create('User', reportedUserOrAuthorData) + await factory.create('User', reportedUserOrAuthorData) resourceId = 'reportedUser' await fileReportAction() @@ -655,7 +653,7 @@ describe('notifications', () => { beforeEach(async () => { title = 'My post' postContent = 'My post content.' - postAuthor = await neode.create('User', reportedUserOrAuthorData) + postAuthor = await factory.create('User', reportedUserOrAuthorData) }) describe('post', () => { @@ -684,7 +682,7 @@ describe('notifications', () => { describe('comment', () => { beforeEach(async () => { commentContent = "Commenter's comment." - commentAuthor = await neode.create('User', { + commentAuthor = await factory.create('User', { id: 'commentAuthor', name: 'Mrs Comment', slug: 'mrs-comment', diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js index 8451c7744..9bfc0a814 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -49,14 +49,16 @@ export default { const notificationsTransactionResponse = await transaction.run( ` MATCH (resource)-[notification:NOTIFIED]->(user:User {id:$id}) - WHERE ((labels(resource)[0] in ["Post", "Comment"] AND NOT resource.deleted AND NOT resource.disabled) OR labels(resource)[0] in ["Report"]) + WHERE + ((labels(resource)[0] in ["Post", "Comment"] AND NOT resource.deleted AND NOT resource.disabled) + OR labels(resource)[0] in ["Report"]) ${whereClause} WITH user, notification, resource, - [(resource)<-[:WROTE]-(author:User) | author {.*}] as authors, - [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post {.*, author: properties(author)} ] as posts, - [(reportedResource)<-[:BELONGS_TO]-(resource)<-[file:FILED]-(user) | file {.*, reportedResource: apoc.map.merge(properties(reportedResource), {__typename: labels(reportedResource)[0]})} ] as files + [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, + [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post {.*, author: properties(author)} ] AS posts, + [(reportedResource)<-[:BELONGS_TO]-(resource)<-[file:FILED]-(user) | file {.*, reportedResource: apoc.map.merge(properties(reportedResource), {__typename: labels(reportedResource)[0]})} ] AS files WITH resource, user, notification, authors, posts, files, - resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0], filed: files, resource: files[0].reportedResource} as finalResource + resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0], filed: files, resource: files[0].reportedResource} AS finalResource RETURN notification {.*, from: finalResource, to: properties(user)} ${orderByClause} ${offset} ${limit} diff --git a/webapp/components/utils/Notifications.js b/webapp/components/utils/Notifications.js index 0a022f1ac..9472136c4 100644 --- a/webapp/components/utils/Notifications.js +++ b/webapp/components/utils/Notifications.js @@ -63,94 +63,100 @@ export const testNotifications = [ ] export const extractNotificationDataOfCurrentUser = (notification, currentUser) => { - const from = notification.from - let triggerer - const id = from.id - const createdAt = notification.createdAt - const read = notification.read - const reason = notification.reason - let title - let author + const from = notification.from // for readability let user = null let post = null let comment = null let contentExcerpt = null let report = null let reasonExtention = '' + let triggerer + let title + let author + let linkName + let linkParams + let linkHashParam - if (from.__typename === 'Post') { - post = from - triggerer = post.author - } else if (from.__typename === 'Comment') { - comment = from - triggerer = comment.author - post = comment.post - } else if (from.__typename === 'Report') { - report = { - reasonCategory: from.filed[0].reasonCategory, - reasonDescription: from.filed[0].reasonDescription, - } - triggerer = currentUser - if (from.filed[0].reportedResource.__typename === 'User') { - user = from.filed[0].reportedResource - reasonExtention = '.user' - } else if (from.filed[0].reportedResource.__typename === 'Post') { - post = from.filed[0].reportedResource - reasonExtention = '.post' - } else if (from.filed[0].reportedResource.__typename === 'Comment') { - comment = from.filed[0].reportedResource - post = from.filed[0].reportedResource.post - reasonExtention = '.comment' - } + // extract data out of the deep structure of db response + + // leave undefined data as default, see above, so later by priority user, comment, post we get easely a clou what it is + switch (from.__typename) { + case 'Comment': + comment = from + post = comment.post + triggerer = comment.author + break + case 'Post': + post = from + triggerer = post.author + break + case 'Report': + { + const filed = from.filed[0] // for readability + report = { + reasonCategory: filed.reasonCategory, + reasonDescription: filed.reasonDescription, + } + triggerer = currentUser + switch (filed.reportedResource.__typename) { + case 'User': + user = filed.reportedResource + reasonExtention = '.user' + break + case 'Comment': + comment = filed.reportedResource + post = filed.reportedResource.post + reasonExtention = '.comment' + break + case 'Post': + post = filed.reportedResource + reasonExtention = '.post' + break + } + } + break } + // extract the content and link data by priority: user, comment, post if (user) { + // it's a user title = user.name author = user + linkName = 'profile-id-slug' + linkParams = { id: user.id, slug: user.slug } } else { + // it's a comment or post title = post.title + linkName = 'post-id-slug' + linkParams = { id: post.id, slug: post.slug } if (comment) { + // it's a comment author = comment.author contentExcerpt = comment.contentExcerpt + linkHashParam = { hash: `#commentId-${comment.id}` } } else { + // it's a post author = post.author contentExcerpt = post.contentExcerpt + linkHashParam = {} } } - const params = user - ? { - id: user.id, - slug: user.slug, - } - : post - ? { - id: post.id, - slug: post.slug, - } - : {} - const hashParam = comment ? { hash: `#commentId-${comment.id}` } : {} - const linkTo = { - name: user ? 'profile-id-slug' : 'post-id-slug', - params, - ...hashParam, - } - const data = { + createdAt: notification.createdAt, + read: notification.read, + reason: notification.reason, + id: from.id, triggerer, - id, - createdAt, - read, - reason, - title, user, - post, comment, - contentExcerpt, + post, author, + title, + contentExcerpt, report, reasonExtention, - linkTo, + linkTo: { name: linkName, params: linkParams, ...linkHashParam }, } return data }