diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index a94832725..fb440e723 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -1,3 +1,4 @@ +import log from '../../schema/resolvers/helpers/databaseLogger' import extractMentionedUsers from './mentions/extractMentionedUsers' import { validateNotifyUsers } from '../validation/validationMiddleware' import { pubsub, NOTIFICATION_ADDED } from '../../server' @@ -80,6 +81,7 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { AND NOT (user)-[:BLOCKED]-(author) AND NOT (user)-[:BLOCKED]-(postAuthor) MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user) + WITH comment AS resource, notification, user ` break } @@ -91,19 +93,19 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { WITH resource, user, notification, authors, posts, resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource SET notification.read = FALSE - // 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()) RETURN notification {.*, from: finalResource, to: properties(user)} ` const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async transaction => { - const notificationTransactionResponse = await transaction.run(mentionedCypher, { + const notificationsTransactionResponse = await transaction.run(mentionedCypher, { id, idsOfUsers, reason, }) - return notificationTransactionResponse.records.map(record => record.get('notification')) + log(notificationsTransactionResponse) + return notificationsTransactionResponse.records.map(record => record.get('notification')) }) try { const notifications = await writeTxResultPromise @@ -120,7 +122,7 @@ const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, cont await validateNotifyUsers(label, reason) const session = context.driver.session() const writeTxResultPromise = await session.writeTransaction(async transaction => { - const notificationTransactionResponse = await transaction.run( + const notificationsTransactionResponse = await transaction.run( ` MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User) WHERE NOT (postAuthor)-[:BLOCKED]-(commenter) @@ -134,7 +136,8 @@ const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, cont `, { commentId, postAuthorId, reason }, ) - return notificationTransactionResponse.records.map(record => record.get('notification')) + log(notificationsTransactionResponse) + return notificationsTransactionResponse.records.map(record => record.get('notification')) }) try { const notifications = await writeTxResultPromise @@ -146,41 +149,53 @@ const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, cont } } -const notifyReportFiler = async (resolve, root, args, context, resolveInfo) => { - const report = await resolve(root, args, context, resolveInfo) +const handleFileReport = async (resolve, root, args, context, resolveInfo) => { + const filedReport = await resolve(root, args, context, resolveInfo) - if (report) { + if (filedReport) { + const { reportId } = filedReport const { resourceId } = args - const { driver, user } = context - const { id: reportId } = report - const session = driver.session() - try { - await session.writeTransaction(async transaction => { - await transaction.run( - ` - MATCH (resource {id: $resourceId})<-[:BELONGS_TO]-(report:Report {id: $reportId})<-[:FILED]-(submitter:User {id: $submitterId}) - WHERE resource: User OR resource: Post OR resource: Comment - MERGE (report)-[notification:NOTIFIED {reason: $reason}]->(submitter) - ON CREATE SET notification.createdAt = toString(datetime()), notification.updatedAt = notification.createdAt - ON MATCH SET notification.updatedAt = toString(datetime()) - SET notification.read = FALSE - `, - { - reportId, - resourceId, - submitterId: user.id, - reason: 'filed_report_on_resource', - }, - ) - }) - } catch (error) { - debug(error) - } finally { - session.close() - } + await publishNotifications(notifyReportFiler(reportId, resourceId, context)) } - return report + return filedReport +} + +const notifyReportFiler = async (reportId, resourceId, context) => { + const { driver, user } = context + const session = driver.session() + const writeTxResultPromise = await session.writeTransaction(async transaction => { + const notificationsTransactionResponse = await transaction.run( + ` + MATCH (resource {id: $resourceId})<-[:BELONGS_TO]-(report:Report {id: $reportId})<-[filed:FILED]-(submitter:User {id: $submitterId}) + WHERE resource: User OR resource: Post OR resource: Comment + MERGE (report)-[notification:NOTIFIED {reason: $reason}]->(submitter) + ON CREATE SET notification.createdAt = toString(datetime()), notification.updatedAt = notification.createdAt + ON MATCH SET notification.updatedAt = toString(datetime()) + SET notification.read = FALSE + WITH notification, submitter, + {__typename: "FiledReport", reportId: report.id, createdAt: filed.createdAt, reasonCategory: filed.reasonCategory, reasonDescription: filed.reasonDescription, resource: apoc.map.merge(properties(resource), {__typename: labels(resource)[0]})} AS finalResource + RETURN notification {.*, from: finalResource, to: properties(submitter)} + `, + { + reportId, + resourceId, + submitterId: user.id, + reason: 'filed_report_on_resource', + }, + ) + log(notificationsTransactionResponse) + return notificationsTransactionResponse.records.map(record => record.get('notification')) + }) + try { + const [notification] = await writeTxResultPromise + if (notification) return [notification] + else throw new Error(`Notification for filing a report could not be send!`) + } catch (error) { + debug(error) + } finally { + session.close() + } } export default { @@ -189,6 +204,6 @@ export default { UpdatePost: handleContentDataOfPost, CreateComment: handleContentDataOfComment, UpdateComment: handleContentDataOfComment, - fileReport: notifyReportFiler, + fileReport: handleFileReport, }, } diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.js b/backend/src/middleware/notifications/notificationsMiddleware.spec.js index 6da74ee22..87d24249c 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.js @@ -1,5 +1,5 @@ import { gql } from '../../helpers/jest' -import Factory, { cleanDatabase } from '../../factories' +import Factory, { cleanDatabase } from '../../db/factories' import { createTestClient } from 'apollo-server-testing' import { getDriver } from '../../db/neo4j' import createServer, { pubsub } from '../../server' @@ -40,7 +40,7 @@ const fileReportMutation = gql` reasonCategory: $reasonCategory reasonDescription: $reasonDescription ) { - id + reportId } } ` @@ -64,6 +64,12 @@ beforeAll(async () => { beforeEach(async () => { publishSpy.mockClear() + await Factory.build('category', { + id: 'cat9', + name: 'Democracy & Politics', + slug: 'democracy-politics', + icon: 'university', + }) notifiedUser = await Factory.build( 'user', { @@ -76,12 +82,7 @@ beforeEach(async () => { password: '1234', }, ) - await Factory.build('category', { - id: 'cat9', - name: 'Democracy & Politics', - slug: 'democracy-politics', - icon: 'university', - }) + authenticatedUser = await notifiedUser.toJson() }) afterEach(async () => { @@ -89,7 +90,7 @@ afterEach(async () => { }) describe('notifications', () => { - const notificationQuery = gql` + const notificationsQuery = gql` query($read: Boolean) { notifications(read: $read, orderBy: updatedAt_desc) { createdAt @@ -105,25 +106,23 @@ describe('notifications', () => { id content } - ... on Report { - id - filed { - reasonCategory - reasonDescription - reportedResource { - __typename - ... on User { - id - name - } - ... on Post { - id - content - } - ... on Comment { - id - content - } + ... on FiledReport { + reportId + reasonCategory + reasonDescription + resource { + __typename + ... on User { + id + name + } + ... on Post { + id + content + } + ... on Comment { + id + content } } } @@ -215,7 +214,7 @@ describe('notifications', () => { }) await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -232,7 +231,7 @@ describe('notifications', () => { await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -256,7 +255,7 @@ describe('notifications', () => { await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -275,7 +274,7 @@ describe('notifications', () => { slug: 'mrs-post', }, { - email: 'post-author@example.org', + email: 'mrs-post-author@example.org', password: '1234', }, ) @@ -295,7 +294,7 @@ describe('notifications', () => { 'Hey @al-capone how do you do?' await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -387,7 +386,7 @@ describe('notifications', () => { }) await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -416,7 +415,7 @@ describe('notifications', () => { notifications: [{ read: readBefore }], }, } = await query({ - query: notificationQuery, + query: notificationsQuery, }) await updatePostAction() const { @@ -424,7 +423,7 @@ describe('notifications', () => { notifications: [{ read: readAfter }], }, } = await query({ - query: notificationQuery, + query: notificationsQuery, }) expect(readBefore).toEqual(true) expect(readAfter).toEqual(false) @@ -438,7 +437,7 @@ describe('notifications', () => { notifications: [{ createdAt: createdAtBefore }], }, } = await query({ - query: notificationQuery, + query: notificationsQuery, }) await updatePostAction() const { @@ -446,7 +445,7 @@ describe('notifications', () => { notifications: [{ createdAt: createdAtAfter }], }, } = await query({ - query: notificationQuery, + query: notificationsQuery, }) expect(createdAtBefore).toBeTruthy() expect(Date.parse(createdAtBefore)).toEqual(expect.any(Number)) @@ -471,7 +470,7 @@ describe('notifications', () => { await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -519,7 +518,7 @@ describe('notifications', () => { slug: 'mr-author', }, { - email: 'post-author@example.org', + email: 'mr-post-author@example.org', password: '1234', }, ) @@ -544,7 +543,7 @@ describe('notifications', () => { await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -557,6 +556,7 @@ describe('notifications', () => { postContent = 'Content of post where I get mentioned in a comment.' postAuthor = notifiedUser }) + it('sends only one notification with reason commented_on_post, no notification with reason mentioned_in_comment', async () => { await createCommentOnPostAction() const expected = { @@ -578,7 +578,7 @@ describe('notifications', () => { await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -610,7 +610,7 @@ describe('notifications', () => { await createCommentOnPostAction() await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -641,11 +641,9 @@ describe('notifications', () => { }) describe('given I file a report on a', () => { - let resourceId + let resourceId, reportFiler, reportedUserOrAuthorData, expectedMeAsNotifiedForFilingReport const reasonCategory = 'discrimination_etc' const reasonDescription = 'I am free to be gay !!!' - let reportFiler - let reportedUserOrAuthorData const fileReportAction = async () => { authenticatedUser = await reportFiler.toJson() await mutate({ @@ -658,7 +656,7 @@ describe('notifications', () => { }) authenticatedUser = await notifiedUser.toJson() } - const setExpectedNotificationOfReportedResource = reportedResource => { + const setExpectedNotificationOfReportedResource = resource => { return expect.objectContaining({ data: { notifications: [ @@ -667,15 +665,11 @@ describe('notifications', () => { read: false, reason: 'filed_report_on_resource', from: { - __typename: 'Report', - id: expect.any(String), - filed: [ - { - reasonCategory: 'discrimination_etc', - reasonDescription: 'I am free to be gay !!!', - reportedResource, - }, - ], + __typename: 'FiledReport', + reportId: expect.any(String), + reasonCategory: 'discrimination_etc', + reasonDescription: 'I am free to be gay !!!', + resource, }, }, ], @@ -686,20 +680,38 @@ describe('notifications', () => { beforeEach(async () => { reportFiler = notifiedUser reportedUserOrAuthorData = { - id: 'reportedUser', - name: 'Mrs Badman', - slug: 'mrs-badman', - email: 'reported-user@example.org', - password: '1234', + userProperties: { + id: 'reportedUser', + name: 'Mrs Badman', + slug: 'mrs-badman', + }, + emailProperties: { + email: 'reported-user@example.org', + password: '1234', + }, } + expectedMeAsNotifiedForFilingReport = expect.objectContaining({ + notificationAdded: expect.objectContaining({ + reason: 'filed_report_on_resource', + to: expect.objectContaining({ + id: 'you', + }), + }), + }) }) describe('user', () => { - it('sends me a notification for filing a report on a user', async () => { - await Factory.create('User', reportedUserOrAuthorData) - resourceId = 'reportedUser' + beforeEach(async () => { + await Factory.build( + 'user', + reportedUserOrAuthorData.userProperties, + reportedUserOrAuthorData.emailProperties, + ) + resourceId = reportedUserOrAuthorData.userProperties.id await fileReportAction() + }) + it('sends me a notification for filing a report on a user', async () => { const expected = setExpectedNotificationOfReportedResource({ __typename: 'User', id: 'reportedUser', @@ -708,28 +720,41 @@ describe('notifications', () => { const { query } = createTestClient(server) await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, }), ).resolves.toEqual(expected) }) + + it('does publish `NOTIFICATION_ADDED` to authenticated user', async () => { + expect(publishSpy).toHaveBeenCalledWith( + 'NOTIFICATION_ADDED', + expectedMeAsNotifiedForFilingReport, + ) + }) }) describe('post or comment', () => { beforeEach(async () => { title = 'My post' postContent = 'My post content.' - postAuthor = await Factory.create('User', reportedUserOrAuthorData) + postAuthor = await Factory.build( + 'user', + reportedUserOrAuthorData.userProperties, + reportedUserOrAuthorData.emailProperties, + ) }) describe('post', () => { - it('sends me a notification for filing a report on a post', async () => { + beforeEach(async () => { await createPostAction() resourceId = 'p47' await fileReportAction() + }) + it('sends me a notification for filing a report on a post', async () => { const expected = setExpectedNotificationOfReportedResource({ __typename: 'Post', id: 'p47', @@ -738,32 +763,43 @@ describe('notifications', () => { const { query } = createTestClient(server) await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, }), ).resolves.toEqual(expected) }) + + it('does publish `NOTIFICATION_ADDED` to authenticated user', async () => { + expect(publishSpy).toHaveBeenCalledWith( + 'NOTIFICATION_ADDED', + expectedMeAsNotifiedForFilingReport, + ) + }) }) describe('comment', () => { beforeEach(async () => { commentContent = "Commenter's comment." - commentAuthor = await Factory.create('User', { - id: 'commentAuthor', - name: 'Mrs Comment', - slug: 'mrs-comment', - email: 'commentauthor@example.org', - password: '1234', - }) - }) - - it('sends me a notification for filing a report on a comment', async () => { + commentAuthor = await Factory.build( + 'user', + { + id: 'commentAuthor', + name: 'Mrs Comment', + slug: 'mrs-comment', + }, + { + email: 'commentauthor@example.org', + password: '1234', + }, + ) await createCommentOnPostAction() resourceId = 'c47' await fileReportAction() + }) + it('sends me a notification for filing a report on a comment', async () => { const expected = setExpectedNotificationOfReportedResource({ __typename: 'Comment', id: 'c47', @@ -772,7 +808,7 @@ describe('notifications', () => { const { query } = createTestClient(server) await expect( query({ - query: notificationQuery, + query: notificationsQuery, variables: { read: false, }, @@ -780,20 +816,11 @@ describe('notifications', () => { ).resolves.toEqual(expected) }) - it('does not publish `NOTIFICATION_ADDED` to authenticated user', async () => { - await createCommentOnPostAction() + it('does publish `NOTIFICATION_ADDED` to authenticated user', async () => { expect(publishSpy).toHaveBeenCalledWith( 'NOTIFICATION_ADDED', - expect.objectContaining({ - notificationAdded: expect.objectContaining({ - reason: 'commented_on_post', - to: expect.objectContaining({ - id: 'postAuthor', // that's expected, it's not me but the post author - }), - }), - }), + expectedMeAsNotifiedForFilingReport, ) - expect(publishSpy).toHaveBeenCalledTimes(1) }) }) }) diff --git a/backend/src/schema/resolvers/notifications.js b/backend/src/schema/resolvers/notifications.js index 2ddc137ba..7524cda32 100644 --- a/backend/src/schema/resolvers/notifications.js +++ b/backend/src/schema/resolvers/notifications.js @@ -2,6 +2,27 @@ import log from './helpers/databaseLogger' import { withFilter } from 'graphql-subscriptions' import { pubsub, NOTIFICATION_ADDED } from '../../server' +const cypherReturnNotificationsWithCollectedResourceData = ` + CALL apoc.case( + [ + labels(resource)[0] = "Post", ' + MATCH (resource)<-[:WROTE]-(author:User) + RETURN resource {.*, __typename: labels(resource)[0], author: author} AS finalResource', + labels(resource)[0] = "Comment", ' + MATCH (author:User)-[:WROTE]->(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(postAuthor:User) + RETURN resource {.*, __typename: labels(resource)[0], author: author, post: apoc.map.merge(properties(post), {__typename: labels(post)[0], author: properties(postAuthor)})} AS finalResource', + labels(resource)[0] = "Report", ' + MATCH (reportedResource)<-[:BELONGS_TO]-(resource)<-[filed:FILED]-(user) + RETURN {__typename: "FiledReport", reportId: resource.id, createdAt: filed.createdAt, reasonCategory: filed.reasonCategory, reasonDescription: filed.reasonDescription, resource: apoc.map.merge(properties(reportedResource), {__typename: labels(reportedResource)[0]})} AS finalResource' + ], + '', + { + resource: resource, + user: user + }) YIELD value + RETURN notification {.*, from: value.finalResource, to: properties(user)} +` + export default { Subscription: { notificationAdded: { @@ -42,97 +63,87 @@ export default { const offset = args.offset && typeof args.offset === 'number' ? `SKIP ${args.offset}` : '' const limit = args.first && typeof args.first === 'number' ? `LIMIT ${args.first}` : '' + const cypher = ` + // Wolle 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"]) + // $ {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 + // 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 + // RETURN notification {.*, from: finalResource, to: properties(user)} + // $ {orderByClause} + // $ {offset} $ {limit} + + 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"]) + ${whereClause} + ${cypherReturnNotificationsWithCollectedResourceData} + // Wolle WITH resource, notification, user + // MATCH (author:User)-[:WROTE]->(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(postAuthor:User) + // WITH resource, notification, user, author, post, postAuthor + // resource {.*, __typename: labels(resource)[0], author: author, post: apoc.map.merge(properties(post), {__typename: labels(post)[0], author: properties(postAuthor)})} AS finalResource', + // RETURN notification {.*, from: value.finalResource, to: properties(user)} + ${orderByClause} + ${offset} ${limit} + + // Wolle + // the UNION ALL with ORDER BY and SKIP, LIMIT is possible since Neo4j 4.0. See https://neo4j.com/docs/cypher-manual/4.0/clauses/call-subquery/#subquery-post-union + // refactor the following to the new CALL {} subquery + + // MATCH (author:User)-[:WROTE]->(post:Post)-[notification:NOTIFIED]->(user:User {id: $id}) + // WHERE NOT post.deleted AND NOT post.disabled + // $ {whereClause} + // WITH user, notification, post {.*, __typename: labels(post)[0], author: properties(author)} + // RETURN notification {.*, from: post, to: properties(user)} + + // UNION ALL + // MATCH (author:User)-[:WROTE]->(comment:Comment)-[:COMMENTS]->(post:Post)<-[:WROTE]-(postAuthor:User), + // (comment)-[notification:NOTIFIED]->(user:User {id: $id}) + // WHERE NOT comment.deleted AND NOT comment.disabled + // $ {whereClause} + // WITH user, notification, comment {.*, __typename: labels(comment)[0], author: properties(author), post: apoc.map.merge(properties(post), {__typename: labels(post)[0], author: properties(postAuthor)})} + // RETURN notification {.*, from: comment, to: properties(user)} + + // UNION ALL + // MATCH (reportedResource)<-[:BELONGS_TO]-(report)<-[file:FILED]-(user:User {id:$id}), + // (report:Report)-[notification:NOTIFIED]->(user) + // WHERE (reportedResource:User) OR (reportedResource:Post) OR (reportedResource:Comment) + // $ {whereClause} + // // Wolle - Here the three different case are not distinguished and therefore Post is not added to Comment and the authors are not added etc. + // WITH + // user, + // notification, + // { + // __typename: "FiledReport", + // createdAt: file.createdAt, + // reasonCategory: file.reasonCategory, + // reasonDescription: file.reasonDescription, + // reportId: report.id, + // resource: apoc.map.merge(properties(reportedResource), { + // __typename: labels(reportedResource)[0] + // }) + // } AS filedReport + // RETURN notification {.*, from: filedReport, to: properties(user)} + // $ {orderByClause} + // $ {offset} $ {limit} + ` + const readTxResultPromise = session.readTransaction(async transaction => { - const notificationsTransactionResponse = await transaction.run( - ` - // Wolle 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"]) - // ${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 - // 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 - // RETURN notification {.*, from: finalResource, to: properties(user)} - // ${orderByClause} - // ${offset} ${limit} - - 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"]) - ${whereClause} - CALL apoc.case( - [ - labels(resource)[0] = "Post", ' - MATCH (resource)<-[:WROTE]-(author:User) - RETURN resource {.*, __typename: labels(resource)[0], author: author} AS finalResource', - labels(resource)[0] = "Comment", ' - MATCH (author:User)-[:WROTE]->(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(postAuthor:User) - RETURN resource {.*, __typename: labels(resource)[0], author: author, post: apoc.map.merge(properties(post), {__typename: labels(post)[0], author: properties(postAuthor)})} AS finalResource', - labels(resource)[0] = "Report", ' - MATCH (reportedResource)<-[:BELONGS_TO]-(resource)<-[filed:FILED]-(user) - RETURN {__typename: "FiledReport", reportId: resource.id, createdAt: filed.createdAt, reasonCategory: filed.reasonCategory, reasonDescription: filed.reasonDescription, resource: apoc.map.merge(properties(reportedResource), {__typename: labels(reportedResource)[0]})} AS finalResource' - ], - '', - { - resource: resource, - user: user - }) YIELD value - RETURN notification {.*, from: value.finalResource, to: properties(user)} - ${orderByClause} - ${offset} ${limit} - - // Wolle - // the UNION ALL with ORDER BY and SKIP, LIMIT is possible since Neo4j 4.0. See https://neo4j.com/docs/cypher-manual/4.0/clauses/call-subquery/#subquery-post-union - // refactor the following to the new CALL {} subquery - // MATCH (author:User)-[:WROTE]->(post:Post)-[notification:NOTIFIED]->(user:User {id: $id}) - // WHERE NOT post.deleted AND NOT post.disabled - // ${whereClause} - // WITH user, notification, post {.*, __typename: labels(post)[0], author: properties(author)} - // RETURN notification {.*, from: post, to: properties(user)} - - // UNION ALL - // MATCH (author:User)-[:WROTE]->(comment:Comment)-[:COMMENTS]->(post:Post)<-[:WROTE]-(postAuthor:User), - // (comment)-[notification:NOTIFIED]->(user:User {id: $id}) - // WHERE NOT comment.deleted AND NOT comment.disabled - // ${whereClause} - // WITH user, notification, comment {.*, __typename: labels(comment)[0], author: properties(author), post: apoc.map.merge(properties(post), {__typename: labels(post)[0], author: properties(postAuthor)})} - // RETURN notification {.*, from: comment, to: properties(user)} - - // UNION ALL - // MATCH (reportedResource)<-[:BELONGS_TO]-(report)<-[file:FILED]-(user:User {id:$id}), - // (report:Report)-[notification:NOTIFIED]->(user) - // WHERE (reportedResource:User) OR (reportedResource:Post) OR (reportedResource:Comment) - // ${whereClause} - // // Wolle - Here the three different case are not distinguished and therefore Post is not added to Comment and the authors are not added etc. - // WITH - // user, - // notification, - // { - // __typename: "FiledReport", - // createdAt: file.createdAt, - // reasonCategory: file.reasonCategory, - // reasonDescription: file.reasonDescription, - // reportId: report.id, - // resource: apoc.map.merge(properties(reportedResource), { - // __typename: labels(reportedResource)[0] - // }) - // } AS filedReport - // RETURN notification {.*, from: filedReport, to: properties(user)} - // ${orderByClause} - // ${offset} ${limit} - `, - { id: currentUser.id }, - ) + const notificationsTransactionResponse = await transaction.run(cypher, { + id: currentUser.id, + }) log(notificationsTransactionResponse) - // Wolle return notificationsTransactionResponse.records.map(record => record.get('notification')) - const notification = notificationsTransactionResponse.records.map(record => record.get('notification')) - // Wolle console.log('notification: ', notification) - return notification + const notifications = notificationsTransactionResponse.records.map(record => + record.get('notification'), + ) + return notifications }) try { const notifications = await readTxResultPromise @@ -144,7 +155,7 @@ export default { }, Mutation: { - markAsRead: async (parent, args, context, resolveInfo) => { + markAsRead: async (_parent, args, context, _resolveInfo) => { const { user: currentUser } = context const session = context.driver.session() const writeTxResultPromise = session.writeTransaction(async transaction => { @@ -152,12 +163,8 @@ export default { ` MATCH (resource {id: $resourceId})-[notification:NOTIFIED {read: FALSE}]->(user:User {id: $id}) SET notification.read = TRUE - WITH user, notification, resource, - [(resource)<-[:WROTE]-(author:User) | author {.*}] AS authors, - [(resource)-[:COMMENTS]->(post:Post)<-[:WROTE]-(author:User) | post{.*, author: properties(author)} ] AS posts - WITH resource, user, notification, authors, posts, - resource {.*, __typename: labels(resource)[0], author: authors[0], post: posts[0]} AS finalResource - RETURN notification {.*, from: finalResource, to: properties(user)} + WITH resource, notification, user + ${cypherReturnNotificationsWithCollectedResourceData} `, { resourceId: args.id, @@ -170,8 +177,8 @@ export default { ) }) try { - const [notifications] = await writeTxResultPromise - return notifications + const [notification] = await writeTxResultPromise + return notification } finally { session.close() } diff --git a/backend/src/schema/resolvers/notifications.spec.js b/backend/src/schema/resolvers/notifications.spec.js index bb086a501..a4206dccb 100644 --- a/backend/src/schema/resolvers/notifications.spec.js +++ b/backend/src/schema/resolvers/notifications.spec.js @@ -344,8 +344,7 @@ describe('given some notifications', () => { resource: { __typename: 'Comment', id: 'c4', - content: - 'I am harassing content in a harassing comment to a bad post !!!', + content: 'I am harassing content in a harassing comment to a bad post !!!', }, }, }, @@ -431,8 +430,7 @@ describe('given some notifications', () => { resource: { __typename: 'Comment', id: 'c4', - content: - 'I am harassing content in a harassing comment to a bad post !!!', + content: 'I am harassing content in a harassing comment to a bad post !!!', }, }, }, @@ -533,8 +531,8 @@ describe('given some notifications', () => { ... on Comment { content } - ... on Report { - id + ... on FiledReport { + reportId } } } diff --git a/webapp/graphql/User.js b/webapp/graphql/User.js index 3cb26fd5a..e557f98e6 100644 --- a/webapp/graphql/User.js +++ b/webapp/graphql/User.js @@ -161,7 +161,7 @@ export const markAsReadMutation = i18n => { } } } - ... on Report { + ... on FiledReport { id } }