mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
refactor(backend): comment on observed post notification (#8311)
* all users that observe a post are notified when the post is commented, except of the author of the comment, or users that blocked the commenter * test to illustrate the behavior of notifications for observed posts
This commit is contained in:
parent
1e6a74b8ce
commit
0835057cc7
@ -238,7 +238,6 @@ describe('notifications', () => {
|
||||
})
|
||||
|
||||
it('sends me no notification', async () => {
|
||||
await notifiedUser.relateTo(commentAuthor, 'blocked')
|
||||
await createCommentOnPostAction()
|
||||
const expected = expect.objectContaining({
|
||||
data: { notifications: [] },
|
||||
|
||||
@ -108,13 +108,19 @@ const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo
|
||||
|
||||
const handleContentDataOfComment = async (resolve, root, args, context, resolveInfo) => {
|
||||
const { content } = args
|
||||
let idsOfUsers = extractMentionedUsers(content)
|
||||
let idsOfMentionedUsers = extractMentionedUsers(content)
|
||||
const comment = await resolve(root, args, context, resolveInfo)
|
||||
const [postAuthor] = await postAuthorOfComment(comment.id, { context })
|
||||
idsOfUsers = idsOfUsers.filter((id) => id !== postAuthor.id)
|
||||
idsOfMentionedUsers = idsOfMentionedUsers.filter((id) => id !== postAuthor.id)
|
||||
await publishNotifications(context, [
|
||||
notifyUsersOfMention('Comment', comment.id, idsOfUsers, 'mentioned_in_comment', context),
|
||||
notifyUsersOfComment('Comment', comment.id, postAuthor.id, 'commented_on_post', context),
|
||||
notifyUsersOfMention(
|
||||
'Comment',
|
||||
comment.id,
|
||||
idsOfMentionedUsers,
|
||||
'mentioned_in_comment',
|
||||
context,
|
||||
),
|
||||
notifyUsersOfComment('Comment', comment.id, 'commented_on_post', context),
|
||||
])
|
||||
return comment
|
||||
}
|
||||
@ -269,29 +275,34 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
||||
}
|
||||
}
|
||||
|
||||
const notifyUsersOfComment = async (label, commentId, postAuthorId, reason, context) => {
|
||||
if (context.user.id === postAuthorId) return []
|
||||
const notifyUsersOfComment = async (label, commentId, reason, context) => {
|
||||
await validateNotifyUsers(label, reason)
|
||||
const session = context.driver.session()
|
||||
const writeTxResultPromise = await session.writeTransaction(async (transaction) => {
|
||||
const notificationTransactionResponse = 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)
|
||||
MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
||||
WHERE NOT (observingUser)-[:BLOCKED]-(commenter) AND NOT observingUser.id = $userId
|
||||
WITH observingUser, post, comment, commenter
|
||||
MATCH (postAuthor:User)-[:WROTE]->(post)
|
||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
|
||||
SET notification.read = FALSE
|
||||
SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime()))
|
||||
SET notification.updatedAt = toString(datetime())
|
||||
WITH notification, postAuthor, post, commenter,
|
||||
WITH notification, observingUser, post, commenter, postAuthor,
|
||||
comment {.*, __typename: labels(comment)[0], author: properties(commenter), post: post {.*, author: properties(postAuthor) } } AS finalResource
|
||||
RETURN notification {
|
||||
.*,
|
||||
from: finalResource,
|
||||
to: properties(postAuthor),
|
||||
to: properties(observingUser),
|
||||
relatedUser: properties(commenter)
|
||||
}
|
||||
`,
|
||||
{ commentId, postAuthorId, reason },
|
||||
{
|
||||
commentId,
|
||||
reason,
|
||||
userId: context.user.id,
|
||||
},
|
||||
)
|
||||
return notificationTransactionResponse.records.map((record) => record.get('notification'))
|
||||
})
|
||||
|
||||
377
backend/src/middleware/notifications/observing-posts.spec.ts
Normal file
377
backend/src/middleware/notifications/observing-posts.spec.ts
Normal file
@ -0,0 +1,377 @@
|
||||
import gql from 'graphql-tag'
|
||||
import { cleanDatabase } from '../../db/factories'
|
||||
import { getNeode, getDriver } from '../../db/neo4j'
|
||||
import createServer from '../../server'
|
||||
import { createTestClient } from 'apollo-server-testing'
|
||||
|
||||
import CONFIG from '../../config'
|
||||
|
||||
CONFIG.CATEGORIES_ACTIVE = false
|
||||
|
||||
let server, query, mutate, authenticatedUser
|
||||
|
||||
let postAuthor, firstCommenter, secondCommenter
|
||||
|
||||
const driver = getDriver()
|
||||
const neode = getNeode()
|
||||
|
||||
const createPostMutation = gql`
|
||||
mutation ($id: ID, $title: String!, $content: String!) {
|
||||
CreatePost(id: $id, title: $title, content: $content) {
|
||||
id
|
||||
title
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const createCommentMutation = gql`
|
||||
mutation ($id: ID, $postId: ID!, $content: String!) {
|
||||
CreateComment(id: $id, postId: $postId, content: $content) {
|
||||
id
|
||||
content
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const notificationQuery = gql`
|
||||
query ($read: Boolean) {
|
||||
notifications(read: $read, orderBy: updatedAt_desc) {
|
||||
read
|
||||
reason
|
||||
createdAt
|
||||
relatedUser {
|
||||
id
|
||||
}
|
||||
from {
|
||||
__typename
|
||||
... on Post {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Comment {
|
||||
id
|
||||
content
|
||||
}
|
||||
... on Group {
|
||||
id
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
const toggleObservePostMutation = gql`
|
||||
mutation ($id: ID!, $value: Boolean!) {
|
||||
toggleObservePost(id: $id, value: $value) {
|
||||
isObservedByMe
|
||||
observingUsersCount
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
beforeAll(async () => {
|
||||
await cleanDatabase()
|
||||
|
||||
const createServerResult = createServer({
|
||||
context: () => {
|
||||
return {
|
||||
user: authenticatedUser,
|
||||
neode,
|
||||
driver,
|
||||
cypherParams: {
|
||||
currentUserId: authenticatedUser ? authenticatedUser.id : null,
|
||||
},
|
||||
}
|
||||
},
|
||||
})
|
||||
server = createServerResult.server
|
||||
const createTestClientResult = createTestClient(server)
|
||||
query = createTestClientResult.query
|
||||
mutate = createTestClientResult.mutate
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
await cleanDatabase()
|
||||
driver.close()
|
||||
})
|
||||
|
||||
describe('notifications for users that observe a post', () => {
|
||||
beforeAll(async () => {
|
||||
postAuthor = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'post-author',
|
||||
name: 'Post Author',
|
||||
slug: 'post-author',
|
||||
},
|
||||
{
|
||||
email: 'test@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
firstCommenter = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'first-commenter',
|
||||
name: 'First Commenter',
|
||||
slug: 'first-commenter',
|
||||
},
|
||||
{
|
||||
email: 'test2@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
secondCommenter = await neode.create(
|
||||
'User',
|
||||
{
|
||||
id: 'second-commenter',
|
||||
name: 'Second Commenter',
|
||||
slug: 'second-commenter',
|
||||
},
|
||||
{
|
||||
email: 'test3@example.org',
|
||||
password: '1234',
|
||||
},
|
||||
)
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createPostMutation,
|
||||
variables: {
|
||||
id: 'post',
|
||||
title: 'This is the post',
|
||||
content: 'This is the content of the post',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
describe('first comment on the post', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post',
|
||||
id: 'c-1',
|
||||
content: 'first comment of first commenter',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the commenter', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the author', async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-1',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
describe('second comment on post', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await secondCommenter.toJson()
|
||||
await mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post',
|
||||
id: 'c-2',
|
||||
content: 'first comment of second commenter',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends NO notification to the commenter', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to the author', async () => {
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-1',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to first commenter', async () => {
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe('first commenter unfollows the post and post author comments post', () => {
|
||||
beforeAll(async () => {
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await mutate({
|
||||
mutation: toggleObservePostMutation,
|
||||
variables: {
|
||||
id: 'post',
|
||||
value: false,
|
||||
},
|
||||
})
|
||||
|
||||
authenticatedUser = await postAuthor.toJson()
|
||||
await mutate({
|
||||
mutation: createCommentMutation,
|
||||
variables: {
|
||||
postId: 'post',
|
||||
id: 'c-3',
|
||||
content: 'first comment of post author',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
it('sends no new notification to the post author', async () => {
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-1',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends no new notification to first commenter', async () => {
|
||||
authenticatedUser = await firstCommenter.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-2',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
|
||||
it('sends notification to second commenter', async () => {
|
||||
authenticatedUser = await secondCommenter.toJson()
|
||||
await expect(
|
||||
query({
|
||||
query: notificationQuery,
|
||||
}),
|
||||
).resolves.toMatchObject({
|
||||
data: {
|
||||
notifications: [
|
||||
{
|
||||
from: {
|
||||
__typename: 'Comment',
|
||||
id: 'c-3',
|
||||
},
|
||||
read: false,
|
||||
reason: 'commented_on_post',
|
||||
},
|
||||
],
|
||||
},
|
||||
errors: undefined,
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
Loading…
x
Reference in New Issue
Block a user