mirror of
https://github.com/IT4Change/Ocelot-Social.git
synced 2025-12-13 07:45:56 +00:00
feat(backend): do not notify blocked or muted users (#8403)
* no more notifications of blocked or muted users in groups * do not receive notifications on mentions or observed posts from muted users
This commit is contained in:
parent
de4325cb50
commit
89b0fa7a51
@ -118,7 +118,7 @@ afterAll(async () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('notify group members of new posts in group', () => {
|
describe('notify group members of new posts in group', () => {
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
postAuthor = await Factory.build(
|
postAuthor = await Factory.build(
|
||||||
'user',
|
'user',
|
||||||
{
|
{
|
||||||
@ -193,8 +193,12 @@ describe('notify group members of new posts in group', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
afterEach(async () => {
|
||||||
|
await cleanDatabase()
|
||||||
|
})
|
||||||
|
|
||||||
describe('group owner posts in group', () => {
|
describe('group owner posts in group', () => {
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
authenticatedUser = await groupMember.toJson()
|
authenticatedUser = await groupMember.toJson()
|
||||||
await markAllAsRead()
|
await markAllAsRead()
|
||||||
@ -275,29 +279,15 @@ describe('notify group members of new posts in group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('group member mutes group', () => {
|
describe('group member mutes group', () => {
|
||||||
it('sets the muted status correctly', async () => {
|
beforeEach(async () => {
|
||||||
authenticatedUser = await groupMember.toJson()
|
authenticatedUser = await groupMember.toJson()
|
||||||
await expect(
|
await mutate({
|
||||||
mutate({
|
mutation: muteGroupMutation,
|
||||||
mutation: muteGroupMutation,
|
variables: {
|
||||||
variables: {
|
groupId: 'g-1',
|
||||||
groupId: 'g-1',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toMatchObject({
|
|
||||||
data: {
|
|
||||||
muteGroup: {
|
|
||||||
isMutedByMe: true,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
errors: undefined,
|
|
||||||
})
|
})
|
||||||
})
|
|
||||||
|
|
||||||
it('sends NO notification when another post is posted', async () => {
|
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
authenticatedUser = await groupMember.toJson()
|
|
||||||
await markAllAsRead()
|
|
||||||
authenticatedUser = await postAuthor.toJson()
|
authenticatedUser = await postAuthor.toJson()
|
||||||
await mutate({
|
await mutate({
|
||||||
mutation: createPostMutation,
|
mutation: createPostMutation,
|
||||||
@ -308,7 +298,9 @@ describe('notify group members of new posts in group', () => {
|
|||||||
groupId: 'g-1',
|
groupId: 'g-1',
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
authenticatedUser = await groupMember.toJson()
|
})
|
||||||
|
|
||||||
|
it('sends NO notification when another post is posted', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
query({
|
query({
|
||||||
query: notificationQuery,
|
query: notificationQuery,
|
||||||
@ -329,30 +321,18 @@ describe('notify group members of new posts in group', () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
describe('group member unmutes group again but disables email', () => {
|
describe('group member unmutes group again but disables email', () => {
|
||||||
beforeAll(async () => {
|
beforeEach(async () => {
|
||||||
|
authenticatedUser = await groupMember.toJson()
|
||||||
|
await mutate({
|
||||||
|
mutation: unmuteGroupMutation,
|
||||||
|
variables: {
|
||||||
|
groupId: 'g-1',
|
||||||
|
},
|
||||||
|
})
|
||||||
jest.clearAllMocks()
|
jest.clearAllMocks()
|
||||||
await groupMember.update({ emailNotificationsPostInGroup: false })
|
await groupMember.update({ emailNotificationsPostInGroup: false })
|
||||||
})
|
})
|
||||||
|
|
||||||
it('sets the muted status correctly', async () => {
|
|
||||||
authenticatedUser = await groupMember.toJson()
|
|
||||||
await expect(
|
|
||||||
mutate({
|
|
||||||
mutation: unmuteGroupMutation,
|
|
||||||
variables: {
|
|
||||||
groupId: 'g-1',
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
).resolves.toMatchObject({
|
|
||||||
data: {
|
|
||||||
unmuteGroup: {
|
|
||||||
isMutedByMe: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
errors: undefined,
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('sends notification when another post is posted', async () => {
|
it('sends notification when another post is posted', async () => {
|
||||||
authenticatedUser = await groupMember.toJson()
|
authenticatedUser = await groupMember.toJson()
|
||||||
await markAllAsRead()
|
await markAllAsRead()
|
||||||
@ -396,5 +376,85 @@ describe('notify group members of new posts in group', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('group member blocks author', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await groupMember.relateTo(postAuthor, 'blocked')
|
||||||
|
authenticatedUser = await groupMember.toJson()
|
||||||
|
await markAllAsRead()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
authenticatedUser = await postAuthor.toJson()
|
||||||
|
await mutate({
|
||||||
|
mutation: createPostMutation,
|
||||||
|
variables: {
|
||||||
|
id: 'post-1',
|
||||||
|
title: 'This is another post in the group',
|
||||||
|
content: 'This is the content of another post in the group',
|
||||||
|
groupId: 'g-1',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends no notification to the user', async () => {
|
||||||
|
authenticatedUser = await groupMember.toJson()
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
notifications: [],
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends NO email', () => {
|
||||||
|
expect(sendMailMock).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('group member mutes author', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await groupMember.relateTo(postAuthor, 'muted')
|
||||||
|
authenticatedUser = await groupMember.toJson()
|
||||||
|
await markAllAsRead()
|
||||||
|
jest.clearAllMocks()
|
||||||
|
authenticatedUser = await postAuthor.toJson()
|
||||||
|
await mutate({
|
||||||
|
mutation: createPostMutation,
|
||||||
|
variables: {
|
||||||
|
id: 'post-1',
|
||||||
|
title: 'This is another post in the group',
|
||||||
|
content: 'This is the content of another post in the group',
|
||||||
|
groupId: 'g-1',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends no notification to the user', async () => {
|
||||||
|
authenticatedUser = await groupMember.toJson()
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
notifications: [],
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends NO email', () => {
|
||||||
|
expect(sendMailMock).not.toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -294,6 +294,25 @@ describe('notifications', () => {
|
|||||||
).resolves.toEqual(expected)
|
).resolves.toEqual(expected)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('if I have muted the comment author', () => {
|
||||||
|
it('sends me no notification', async () => {
|
||||||
|
await notifiedUser.relateTo(commentAuthor, 'muted')
|
||||||
|
await createCommentOnPostAction()
|
||||||
|
const expected = expect.objectContaining({
|
||||||
|
data: { notifications: [] },
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('commenter is me', () => {
|
describe('commenter is me', () => {
|
||||||
@ -581,6 +600,48 @@ describe('notifications', () => {
|
|||||||
expect(pubsubSpy).not.toHaveBeenCalled()
|
expect(pubsubSpy).not.toHaveBeenCalled()
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('but the author of the post muted me', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await postAuthor.relateTo(notifiedUser, 'muted')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends me a notification', async () => {
|
||||||
|
await createPostAction()
|
||||||
|
const expected = expect.objectContaining({
|
||||||
|
data: {
|
||||||
|
notifications: [
|
||||||
|
{
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
from: {
|
||||||
|
__typename: 'Post',
|
||||||
|
content:
|
||||||
|
'Hey <a class="mention" data-mention-id="you" href="/profile/you/al-capone" target="_blank">@al-capone</a> how do you do?',
|
||||||
|
id: 'p47',
|
||||||
|
},
|
||||||
|
read: false,
|
||||||
|
reason: 'mentioned_in_post',
|
||||||
|
relatedUser: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toEqual(expected)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('publishes `NOTIFICATION_ADDED`', async () => {
|
||||||
|
await createPostAction()
|
||||||
|
expect(pubsubSpy).toHaveBeenCalled()
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('mentions me in a comment', () => {
|
describe('mentions me in a comment', () => {
|
||||||
@ -736,6 +797,72 @@ describe('notifications', () => {
|
|||||||
expect(pubsubSpy).toHaveBeenCalledTimes(1)
|
expect(pubsubSpy).toHaveBeenCalledTimes(1)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('but the author of the post muted me', () => {
|
||||||
|
beforeEach(async () => {
|
||||||
|
await postAuthor.relateTo(notifiedUser, 'muted')
|
||||||
|
commentContent =
|
||||||
|
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.'
|
||||||
|
commentAuthor = await neode.create(
|
||||||
|
'User',
|
||||||
|
{
|
||||||
|
id: 'commentAuthor',
|
||||||
|
name: 'Mrs Comment',
|
||||||
|
slug: 'mrs-comment',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
email: 'comment-author@example.org',
|
||||||
|
password: '1234',
|
||||||
|
},
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('sends me a notification', async () => {
|
||||||
|
await createCommentOnPostAction()
|
||||||
|
await expect(
|
||||||
|
query({
|
||||||
|
query: notificationQuery,
|
||||||
|
variables: {
|
||||||
|
read: false,
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
).resolves.toMatchObject({
|
||||||
|
data: {
|
||||||
|
notifications: [
|
||||||
|
{
|
||||||
|
createdAt: expect.any(String),
|
||||||
|
from: {
|
||||||
|
__typename: 'Comment',
|
||||||
|
content:
|
||||||
|
'One mention about me with <a data-mention-id="you" class="mention" href="/profile/you" target="_blank">@al-capone</a>.',
|
||||||
|
id: 'c47',
|
||||||
|
},
|
||||||
|
read: false,
|
||||||
|
reason: 'mentioned_in_comment',
|
||||||
|
relatedUser: null,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
errors: undefined,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
it('publishes `NOTIFICATION_ADDED` to authenticated user and me', async () => {
|
||||||
|
await createCommentOnPostAction()
|
||||||
|
expect(pubsubSpy).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
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
expect(pubsubSpy).toHaveBeenCalledTimes(2)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@ -245,6 +245,8 @@ const notifyGroupMembersOfNewPost = async (postId, groupId, context) => {
|
|||||||
MATCH (post)-[:IN]->(group:Group { id: $groupId })<-[membership:MEMBER_OF]-(user:User)
|
MATCH (post)-[:IN]->(group:Group { id: $groupId })<-[membership:MEMBER_OF]-(user:User)
|
||||||
WHERE NOT membership.role = 'pending'
|
WHERE NOT membership.role = 'pending'
|
||||||
AND NOT (user)-[:MUTED]->(group)
|
AND NOT (user)-[:MUTED]->(group)
|
||||||
|
AND NOT (user)-[:MUTED]->(author)
|
||||||
|
AND NOT (user)-[:BLOCKED]-(author)
|
||||||
AND NOT user.id = $userId
|
AND NOT user.id = $userId
|
||||||
WITH post, author, user
|
WITH post, author, user
|
||||||
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
MERGE (post)-[notification:NOTIFIED {reason: $reason}]->(user)
|
||||||
@ -360,7 +362,10 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
|||||||
case 'mentioned_in_post': {
|
case 'mentioned_in_post': {
|
||||||
mentionedCypher = `
|
mentionedCypher = `
|
||||||
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
MATCH (post: Post { id: $id })<-[:WROTE]-(author: User)
|
||||||
MATCH (user: User) WHERE user.id in $idsOfUsers AND NOT (user)-[:BLOCKED]-(author)
|
MATCH (user: User)
|
||||||
|
WHERE user.id in $idsOfUsers
|
||||||
|
AND NOT (user)-[:BLOCKED]-(author)
|
||||||
|
AND NOT (user)-[:MUTED]->(author)
|
||||||
OPTIONAL MATCH (post)-[:IN]->(group:Group)
|
OPTIONAL MATCH (post)-[:IN]->(group:Group)
|
||||||
OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user)
|
OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user)
|
||||||
WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
|
WITH post, author, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
|
||||||
@ -376,6 +381,8 @@ const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => {
|
|||||||
WHERE user.id in $idsOfUsers
|
WHERE user.id in $idsOfUsers
|
||||||
AND NOT (user)-[:BLOCKED]-(commenter)
|
AND NOT (user)-[:BLOCKED]-(commenter)
|
||||||
AND NOT (user)-[:BLOCKED]-(postAuthor)
|
AND NOT (user)-[:BLOCKED]-(postAuthor)
|
||||||
|
AND NOT (user)-[:MUTED]->(commenter)
|
||||||
|
AND NOT (user)-[:MUTED]->(postAuthor)
|
||||||
OPTIONAL MATCH (post)-[:IN]->(group:Group)
|
OPTIONAL MATCH (post)-[:IN]->(group:Group)
|
||||||
OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user)
|
OPTIONAL MATCH (group)<-[membership:MEMBER_OF]-(user)
|
||||||
WITH comment, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
|
WITH comment, user, group WHERE group IS NULL OR group.groupType = 'public' OR membership.role IN ['usual', 'admin', 'owner']
|
||||||
@ -422,7 +429,9 @@ const notifyUsersOfComment = async (label, commentId, reason, context) => {
|
|||||||
const notificationTransactionResponse = await transaction.run(
|
const notificationTransactionResponse = await transaction.run(
|
||||||
`
|
`
|
||||||
MATCH (observingUser:User)-[:OBSERVES { active: true }]->(post:Post)<-[:COMMENTS]-(comment:Comment { id: $commentId })<-[:WROTE]-(commenter:User)
|
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
|
WHERE NOT (observingUser)-[:BLOCKED]-(commenter)
|
||||||
|
AND NOT (observingUser)-[:MUTED]->(commenter)
|
||||||
|
AND NOT observingUser.id = $userId
|
||||||
WITH observingUser, post, comment, commenter
|
WITH observingUser, post, comment, commenter
|
||||||
MATCH (postAuthor:User)-[:WROTE]->(post)
|
MATCH (postAuthor:User)-[:WROTE]->(post)
|
||||||
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
|
MERGE (comment)-[notification:NOTIFIED {reason: $reason}]->(observingUser)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user