diff --git a/backend/src/middleware/notifications/notificationsMiddleware.js b/backend/src/middleware/notifications/notificationsMiddleware.js index a871605f7..e619fcee8 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.js @@ -51,6 +51,19 @@ const publishNotifications = async (context, promises) => { }) } + +const handleJoinGroup = async (resolve, root, args, context, resolveInfo) => { + const { groupId } = args + const user = await resolve(root, args, context, resolveInfo) + if (user) { + await publishNotifications( + context, + [notifyOwnersOfGroup(groupId, 'user_joined_group', context)] + ) + } + return user +} + const handleContentDataOfPost = async (resolve, root, args, context, resolveInfo) => { const idsOfUsers = extractMentionedUsers(args.content) const post = await resolve(root, args, context, resolveInfo) @@ -94,6 +107,36 @@ const postAuthorOfComment = async (commentId, { context }) => { } } +const notifyOwnersOfGroup = async (groupId, reason, context) => { + const cypher = ` + MATCH (group:Group { id: $groupId })<-[membership:MEMBER_OF]-(owner:User) + WHERE membership.role = 'owner' + WITH owner, group + MERGE (group)-[notification:NOTIFIED {reason: $reason}]->(owner) + WITH group, owner, notification + SET notification.read = FALSE + SET notification.createdAt = COALESCE(notification.createdAt, toString(datetime())) + SET notification.updatedAt = toString(datetime()) + RETURN notification {.*, from: group, to: properties(owner)} + ` + const session = context.driver.session() + const writeTxResultPromise = session.writeTransaction(async (transaction) => { + const notificationTransactionResponse = await transaction.run(cypher, { + groupId, + reason, + }) + return notificationTransactionResponse.records.map((record) => record.get('notification')) + }) + try { + const notifications = await writeTxResultPromise + return notifications + } catch (error) { + throw new Error(error) + } finally { + session.close() + } +} + const notifyUsersOfMention = async (label, id, idsOfUsers, reason, context) => { if (!(idsOfUsers && idsOfUsers.length)) return [] await validateNotifyUsers(label, reason) @@ -188,5 +231,6 @@ export default { UpdatePost: handleContentDataOfPost, CreateComment: handleContentDataOfComment, UpdateComment: handleContentDataOfComment, + JoinGroup: handleJoinGroup, }, } diff --git a/backend/src/middleware/notifications/notificationsMiddleware.spec.js b/backend/src/middleware/notifications/notificationsMiddleware.spec.js index a8a5d396b..42ff5a0c6 100644 --- a/backend/src/middleware/notifications/notificationsMiddleware.spec.js +++ b/backend/src/middleware/notifications/notificationsMiddleware.spec.js @@ -3,6 +3,14 @@ import { cleanDatabase } from '../../db/factories' import { createTestClient } from 'apollo-server-testing' import { getNeode, getDriver } from '../../db/neo4j' import createServer, { pubsub } from '../../server' +import { + createGroupMutation, + joinGroupMutation, + leaveGroupMutation, + changeGroupMemberRoleMutation, + removeUserFromGroupMutation, +} from '../../graphql/groups' + let server, query, mutate, notifiedUser, authenticatedUser let publishSpy @@ -102,6 +110,9 @@ describe('notifications', () => { id content } + ... on Group { + id + } } } } @@ -616,4 +627,72 @@ describe('notifications', () => { }) }) }) + + describe('group notifications', () => { + let groupOwner + let group + + beforeEach(async () => { + groupOwner = await neode.create( + 'User', + { + id: 'group-owner', + name: 'Group Owner', + slug: 'group-owner', + }, + { + email: 'owner@example.org', + password: '1234', + }, + ) + authenticatedUser = await groupOwner.toJson() + await mutate({ + mutation: createGroupMutation(), + variables: { + id: 'closed-group', + name: 'The Closed Group', + about: 'Will test the closed group!', + description: 'Some description' + Array(50).join('_'), + groupType: 'public', + actionRadius: 'regional', + categoryIds, + }, + }) + }) + + describe('user joins group', () => { + beforeEach(async () => { + authenticatedUser = await notifiedUser.toJson() + await mutate({ + mutation: joinGroupMutation(), + variables: { + groupId: 'closed-group', + userId: authenticatedUser.id, + }, + }) + authenticatedUser = await groupOwner.toJson() + }) + + it('works', async () => { + await expect(query({ + query: notificationQuery, + })).resolves.toMatchObject({ + data: { + notifications: [ + { + read: false, + reason: 'user_joined_group', + createdAt: expect.any(String), + from: { + __typename: 'Group', + id: 'closed-group', + } + }, + ], + }, + errors: undefined, + }) + }) + }) + }) }) diff --git a/backend/src/schema/types/enum/ReasonNotification.gql b/backend/src/schema/types/enum/ReasonNotification.gql index e870e01dc..e5a348398 100644 --- a/backend/src/schema/types/enum/ReasonNotification.gql +++ b/backend/src/schema/types/enum/ReasonNotification.gql @@ -2,4 +2,5 @@ enum ReasonNotification { mentioned_in_post mentioned_in_comment commented_on_post + user_joined_group } diff --git a/backend/src/schema/types/type/NOTIFIED.gql b/backend/src/schema/types/type/NOTIFIED.gql index 864cdea4d..ea02ee87e 100644 --- a/backend/src/schema/types/type/NOTIFIED.gql +++ b/backend/src/schema/types/type/NOTIFIED.gql @@ -8,7 +8,7 @@ type NOTIFIED { reason: NotificationReason } -union NotificationSource = Post | Comment +union NotificationSource = Post | Comment | Group enum NotificationOrdering { createdAt_asc @@ -21,6 +21,7 @@ enum NotificationReason { mentioned_in_post mentioned_in_comment commented_on_post + user_joined_group } type Query {