Refactor notificationsMiddleware

- Use transaction functions
- extract validations into validationsMiddleware
- break notifyUsers into notifyUsersOfMention/notifyUsersOfComment
This commit is contained in:
mattwr18 2019-12-06 14:48:32 +01:00
parent 132951c525
commit da150e0b27
3 changed files with 88 additions and 83 deletions

View File

@ -1,4 +1,29 @@
import extractMentionedUsers from './mentions/extractMentionedUsers'
import { validateNotifyUsers } from '../validation/validationMiddleware'
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
const idsOfUsers = extractMentionedUsers(args.content)
const post = await resolve(root, args, context, resolveInfo)
if (post && idsOfUsers && idsOfUsers.length)
await notifyUsersOfMention('Post', post.id, idsOfUsers, 'mentioned_in_post', context)
return post
}
const handleCreateComment = async (resolve, root, args, context, resolveInfo) => {
const comment = await resolve(root, args, context, resolveInfo)
if (comment) return handleContentDataOfComment(resolve, root, args, context, resolveInfo)
}
const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
const { content, id: commentId } = args
let idsOfUsers = extractMentionedUsers(content)
const [postAuthor] = await postAuthorOfComment(commentId, { context })
idsOfUsers = idsOfUsers.filter(id => id !== postAuthor.id)
if (idsOfUsers && idsOfUsers.length)
await notifyUsersOfMention('Comment', commentId, idsOfUsers, 'mentioned_in_comment', context)
if (context.user.id !== postAuthor.id)
await notifyUsersOfComment('Comment', commentId, postAuthor.id, 'commented_on_post', context)
}
const postAuthorOfComment = async (commentId, { context }) => {
const session = context.driver.session()
@ -19,105 +44,74 @@ const postAuthorOfComment = async (commentId, { context }) => {
}
}
const notifyUsers = async (label, id, idsOfUsers, reason, context) => {
if (!idsOfUsers.length) return
// Checked here, because it does not go through GraphQL checks at all in this file.
const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
if (!reasonsAllowed.includes(reason)) {
throw new Error('Notification reason is not allowed!')
}
if (
(label === 'Post' && reason !== 'mentioned_in_post') ||
(label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason))
) {
throw new Error('Notification does not fit the reason!')
}
let cypher
const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
await validateNotifyUsers(label, reason)
let mentionedCypher
switch (reason) {
case 'mentioned_in_post': {
cypher = `
mentionedCypher = `
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
MATCH (user: User)
WHERE user.id in $idsOfUsers
AND NOT (user)<-[:BLOCKED]-(author)
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
SET notification.read = FALSE
SET (
CASE
WHEN notification.createdAt IS NULL
THEN notification END ).createdAt = toString(datetime())
SET notification.updatedAt = toString(datetime())
`
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)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
SET notification.read = FALSE
SET (
CASE
WHEN notification.createdAt IS NULL
THEN notification END ).createdAt = toString(datetime())
SET notification.updatedAt = toString(datetime())
`
break
}
case 'commented_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)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
SET notification.read = FALSE
SET (
CASE
WHEN notification.createdAt IS NULL
THEN notification END ).createdAt = toString(datetime())
SET notification.updatedAt = toString(datetime())
mentionedCypher = `
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)
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(user)
`
break
}
}
mentionedCypher += `
SET notification.read = FALSE
SET (
CASE
WHEN notification.createdAt IS NULL
THEN notification END ).createdAt = toString(datetime())
SET notification.updatedAt = toString(datetime())
`
const session = context.driver.session()
try {
await session.writeTransaction(transaction => {
return transaction.run(cypher, { id, idsOfUsers, reason })
return transaction.run(mentionedCypher, { id, idsOfUsers, reason })
})
} finally {
session.close()
}
}
const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => {
const idsOfUsers = extractMentionedUsers(args.content)
const post = await resolve(root, args, context, resolveInfo)
if (post) await notifyUsers('Post', post.id, idsOfUsers, 'mentioned_in_post', context)
return post
}
const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => {
await validateNotifyUsers(label, reason)
const session = context.driver.session()
const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
const { content, id: commentId } = args
let idsOfUsers = extractMentionedUsers(content)
const [postAuthor] = await postAuthorOfComment(commentId, { context })
idsOfUsers = idsOfUsers.filter(id => id !== postAuthor.id)
if (idsOfUsers && idsOfUsers.length)
await notifyUsers('Comment', commentId, idsOfUsers, 'mentioned_in_comment', context)
if (context.user.id !== postAuthor.id)
await notifyUsers('Comment', commentId, [postAuthor.id], 'commented_on_post', context)
}
const handleCreateComment = async (resolve, root, args, context, resolveInfo) => {
const comment = await resolve(root, args, context, resolveInfo)
if (comment) return handleContentDataOfComment(resolve, root, args, context, resolveInfo)
try {
return session.writeTransaction(async transaction => {
await transaction.run(
`
MATCH (postAuthor:User {id: $postAuthorId})-[:WROTE]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
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.updatedAt = toString(datetime())
`,
{ commentId, postAuthorId, reason },
)
})
} finally {
session.close()
}
}
export default {

View File

@ -35,7 +35,8 @@ const createCommentMutation = gql`
}
`
beforeAll(() => {
beforeAll(async () => {
await factory.cleanDatabase()
const createServerResult = createServer({
context: () => {
return {
@ -169,7 +170,6 @@ describe('notifications', () => {
],
},
})
const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,
@ -186,7 +186,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: { notifications: [] },
})
const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,
@ -210,7 +210,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: { notifications: [] },
})
const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,
@ -261,7 +261,7 @@ describe('notifications', () => {
],
},
})
const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,
@ -405,7 +405,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: { notifications: [] },
})
const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,
@ -463,7 +463,7 @@ describe('notifications', () => {
],
},
})
const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,
@ -497,7 +497,7 @@ describe('notifications', () => {
],
},
})
const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,
@ -528,7 +528,7 @@ describe('notifications', () => {
const expected = expect.objectContaining({
data: { notifications: [] },
})
const { query } = createTestClient(server)
await expect(
query({
query: notificationQuery,

View File

@ -115,6 +115,17 @@ const validateReview = async (resolve, root, args, context, info) => {
return resolve(root, args, context, info)
}
export const validateNotifyUsers = async (label, reason) => {
const reasonsAllowed = ['mentioned_in_post', 'mentioned_in_comment', 'commented_on_post']
if (!reasonsAllowed.includes(reason)) throw new Error('Notification reason is not allowed!')
if (
(label === 'Post' && reason !== 'mentioned_in_post') ||
(label === 'Comment' && !['mentioned_in_comment', 'commented_on_post'].includes(reason))
) {
throw new Error('Notification does not fit the reason!')
}
}
export default {
Mutation: {
CreateComment: validateCreateComment,