From f4567b14ff838a5f78e2f132d471d8ce4aa7da0a Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jul 2023 13:25:57 +0200 Subject: [PATCH 1/7] feat(backend): unread rooms query --- backend/src/schema/resolvers/rooms.ts | 21 +++++++++++++++++++++ backend/src/schema/types/type/Room.gql | 1 + 2 files changed, 22 insertions(+) diff --git a/backend/src/schema/resolvers/rooms.ts b/backend/src/schema/resolvers/rooms.ts index d5015a03b..3eda1440c 100644 --- a/backend/src/schema/resolvers/rooms.ts +++ b/backend/src/schema/resolvers/rooms.ts @@ -25,6 +25,27 @@ export default { } return resolved }, + UnreadRooms: async (object, params, context, resolveInfo) => { + const { + user: { id: currentUserId }, + } = context + const session = context.driver.session() + const readTxResultPromise = session.readTransaction(async (transaction) => { + const unreadRoomsCypher = ` + MATCH (:User { id: $currentUserId })-[:CHATS_IN]->(room:Room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(user:User) + WHERE NOT message.seen AND NOT user.id = $currentUserId + RETURN toString(COUNT(room)) AS count + ` + const unreadRoomsTxResponse = await transaction.run(unreadRoomsCypher, { currentUserId }) + return unreadRoomsTxResponse.records.map((record) => record.get('count')) + }) + try { + const count = await readTxResultPromise + return count + } finally { + session.close() + } + }, }, Mutation: { CreateRoom: async (_parent, params, context, _resolveInfo) => { diff --git a/backend/src/schema/types/type/Room.gql b/backend/src/schema/types/type/Room.gql index 2ce6556f6..55984fb5f 100644 --- a/backend/src/schema/types/type/Room.gql +++ b/backend/src/schema/types/type/Room.gql @@ -25,4 +25,5 @@ type Mutation { type Query { Room: [Room] + UnreadRooms: Int } From 09be4d3442e227bb7722cfc5fe0b4a3d9445a44c Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jul 2023 14:40:38 +0200 Subject: [PATCH 2/7] test unread rooms query --- backend/src/graphql/rooms.ts | 8 ++ .../src/middleware/permissionsMiddleware.ts | 1 + backend/src/schema/resolvers/rooms.spec.ts | 120 +++++++++++++++++- backend/src/schema/resolvers/rooms.ts | 6 +- 4 files changed, 128 insertions(+), 7 deletions(-) diff --git a/backend/src/graphql/rooms.ts b/backend/src/graphql/rooms.ts index 109bf1d55..cb511c4eb 100644 --- a/backend/src/graphql/rooms.ts +++ b/backend/src/graphql/rooms.ts @@ -30,3 +30,11 @@ export const roomQuery = () => { } ` } + +export const unreadRoomsQuery = () => { + return gql` + query { + UnreadRooms + } + ` +} diff --git a/backend/src/middleware/permissionsMiddleware.ts b/backend/src/middleware/permissionsMiddleware.ts index c07098a3c..f87f4b079 100644 --- a/backend/src/middleware/permissionsMiddleware.ts +++ b/backend/src/middleware/permissionsMiddleware.ts @@ -408,6 +408,7 @@ export default shield( getInviteCode: isAuthenticated, // and inviteRegistration Room: isAuthenticated, Message: isAuthenticated, + UnreadRooms: isAuthenticated, }, Mutation: { '*': deny, diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index 03c3d4456..6a8cb47de 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -1,7 +1,8 @@ import { createTestClient } from 'apollo-server-testing' import Factory, { cleanDatabase } from '../../db/factories' import { getNeode, getDriver } from '../../db/neo4j' -import { createRoomMutation, roomQuery } from '../../graphql/rooms' +import { createRoomMutation, roomQuery, unreadRoomsQuery } from '../../graphql/rooms' +import { createMessageMutation } from '../../graphql/messages' import createServer from '../../server' const driver = getDriver() @@ -29,11 +30,13 @@ beforeAll(async () => { }) afterAll(async () => { - await cleanDatabase() + // await cleanDatabase() driver.close() }) describe('Room', () => { + let roomId: string + beforeAll(async () => { ;[chattingUser, otherChattingUser, notChattingUser] = await Promise.all([ Factory.build('user', { @@ -68,8 +71,6 @@ describe('Room', () => { }) describe('authenticated', () => { - let roomId: string - beforeAll(async () => { authenticatedUser = await chattingUser.toJson() }) @@ -260,4 +261,115 @@ describe('Room', () => { }) }) }) + + describe('unread rooms query', () => { + describe('unauthenticated', () => { + it('throws authorization error', async () => { + authenticatedUser = null + await expect( + query({ + query: unreadRoomsQuery(), + }), + ).resolves.toMatchObject({ + errors: [{ message: 'Not Authorized!' }], + }) + }) + }) + + describe('authenticated', () => { + let otherRoomId: string + + beforeAll(async () => { + authenticatedUser = await chattingUser.toJson() + const result = await mutate({ + mutation: createRoomMutation(), + variables: { + userId: 'not-chatting-user', + }, + }) + otherRoomId = result.data.CreateRoom.roomId + await mutate({ + mutation: createMessageMutation(), + variables: { + roomId: otherRoomId, + content: 'Message to not chatting user', + }, + }) + await mutate({ + mutation: createMessageMutation(), + variables: { + roomId, + content: '1st message to other chatting user', + }, + }) + await mutate({ + mutation: createMessageMutation(), + variables: { + roomId, + content: '2nd message to other chatting user', + }, + }) + authenticatedUser = await otherChattingUser.toJson() + const result2 = await mutate({ + mutation: createRoomMutation(), + variables: { + userId: 'not-chatting-user', + }, + }) + otherRoomId = result2.data.CreateRoom.roomId + await mutate({ + mutation: createMessageMutation(), + variables: { + roomId: otherRoomId, + content: 'Other message to not chatting user', + }, + }) + }) + + describe('as chatting user', () => { + it('has 0 unread rooms', async () => { + authenticatedUser = await chattingUser.toJson() + await expect( + query({ + query: unreadRoomsQuery(), + }), + ).resolves.toMatchObject({ + data: { + UnreadRooms: 0, + }, + }) + }) + }) + + describe('as other chatting user', () => { + it('has 1 unread rooms', async () => { + authenticatedUser = await otherChattingUser.toJson() + await expect( + query({ + query: unreadRoomsQuery(), + }), + ).resolves.toMatchObject({ + data: { + UnreadRooms: 1, + }, + }) + }) + }) + + describe('as not chatting user', () => { + it('has 2 unread rooms', async () => { + authenticatedUser = await notChattingUser.toJson() + await expect( + query({ + query: unreadRoomsQuery(), + }), + ).resolves.toMatchObject({ + data: { + UnreadRooms: 2, + }, + }) + }) + }) + }) + }) }) diff --git a/backend/src/schema/resolvers/rooms.ts b/backend/src/schema/resolvers/rooms.ts index 3eda1440c..084a75eb0 100644 --- a/backend/src/schema/resolvers/rooms.ts +++ b/backend/src/schema/resolvers/rooms.ts @@ -33,11 +33,11 @@ export default { const readTxResultPromise = session.readTransaction(async (transaction) => { const unreadRoomsCypher = ` MATCH (:User { id: $currentUserId })-[:CHATS_IN]->(room:Room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(user:User) - WHERE NOT message.seen AND NOT user.id = $currentUserId - RETURN toString(COUNT(room)) AS count + WHERE NOT user.id = $currentUserId AND NOT message.seen + RETURN toString(COUNT(DISTINCT room)) AS count ` const unreadRoomsTxResponse = await transaction.run(unreadRoomsCypher, { currentUserId }) - return unreadRoomsTxResponse.records.map((record) => record.get('count')) + return unreadRoomsTxResponse.records.map((record) => record.get('count'))[0] }) try { const count = await readTxResultPromise From d7746e5904a44460404fa5ebf6d554ef42d17882 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jul 2023 14:44:41 +0200 Subject: [PATCH 3/7] clean db after all --- backend/src/schema/resolvers/rooms.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index 6a8cb47de..8c46794c7 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -30,7 +30,7 @@ beforeAll(async () => { }) afterAll(async () => { - // await cleanDatabase() + await cleanDatabase() driver.close() }) From c46d0064fcd33b175fa71e95aab747bef587aa04 Mon Sep 17 00:00:00 2001 From: Moriz Wahl Date: Fri, 14 Jul 2023 14:50:43 +0200 Subject: [PATCH 4/7] unread rooms query in chat notification --- .../ChatNotificationMenu.vue | 18 +++++++++++++++++- webapp/graphql/Rooms.js | 8 ++++++++ 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/webapp/components/ChatNotificationMenu/ChatNotificationMenu.vue b/webapp/components/ChatNotificationMenu/ChatNotificationMenu.vue index fcd93ee48..016410216 100644 --- a/webapp/components/ChatNotificationMenu/ChatNotificationMenu.vue +++ b/webapp/components/ChatNotificationMenu/ChatNotificationMenu.vue @@ -8,18 +8,34 @@ placement: 'bottom-start', }" > - + diff --git a/webapp/graphql/Rooms.js b/webapp/graphql/Rooms.js index e28702f77..c659ba85c 100644 --- a/webapp/graphql/Rooms.js +++ b/webapp/graphql/Rooms.js @@ -27,3 +27,11 @@ export const createRoom = () => gql` } } ` + +export const unreadRoomsQuery = () => { + return gql` + query { + UnreadRooms + } + ` +} From b6fe28e1c4fcbadedc7b8d5f64f17a5714e222f6 Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 17 Jul 2023 10:29:32 +0200 Subject: [PATCH 5/7] fix test, test pagination properly --- backend/src/schema/resolvers/rooms.spec.ts | 27 ++++++++++++++++++++-- 1 file changed, 25 insertions(+), 2 deletions(-) diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index 17277ab13..c4c46f00d 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -399,7 +399,7 @@ describe('Room', () => { }) it('returns the rooms paginated', async () => { - expect(await query({ query: roomQuery(), variables: { first: 2, offset: 0 } })).toMatchObject( + expect(await query({ query: roomQuery(), variables: { first: 3, offset: 0 } })).toMatchObject( { errors: undefined, data: { @@ -450,11 +450,34 @@ describe('Room', () => { }, ]), }, + { + id: expect.any(String), + roomId: expect.any(String), + roomName: 'Not Chatting User', + users: expect.arrayContaining([ + { + _id: 'chatting-user', + id: 'chatting-user', + name: 'Chatting User', + avatar: { + url: expect.any(String), + }, + }, + { + _id: 'not-chatting-user', + id: 'not-chatting-user', + name: 'Not Chatting User', + avatar: { + url: expect.any(String), + }, + }, + ]), + }, ], }, }, ) - expect(await query({ query: roomQuery(), variables: { first: 2, offset: 2 } })).toMatchObject( + expect(await query({ query: roomQuery(), variables: { first: 3, offset: 3 } })).toMatchObject( { errors: undefined, data: { From d75566a8ce3733efd709271d8f775e36eb51dbbe Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 17 Jul 2023 10:30:12 +0200 Subject: [PATCH 6/7] sender user --- backend/src/schema/resolvers/rooms.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backend/src/schema/resolvers/rooms.ts b/backend/src/schema/resolvers/rooms.ts index 084a75eb0..cbf5bcd63 100644 --- a/backend/src/schema/resolvers/rooms.ts +++ b/backend/src/schema/resolvers/rooms.ts @@ -32,8 +32,8 @@ export default { const session = context.driver.session() const readTxResultPromise = session.readTransaction(async (transaction) => { const unreadRoomsCypher = ` - MATCH (:User { id: $currentUserId })-[:CHATS_IN]->(room:Room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(user:User) - WHERE NOT user.id = $currentUserId AND NOT message.seen + MATCH (:User { id: $currentUserId })-[:CHATS_IN]->(room:Room)<-[:INSIDE]-(message:Message)<-[:CREATED]-(sender:User) + WHERE NOT sender.id = $currentUserId AND NOT message.seen RETURN toString(COUNT(DISTINCT room)) AS count ` const unreadRoomsTxResponse = await transaction.run(unreadRoomsCypher, { currentUserId }) From 882435e54c28af8ef1f609a846c2829014e5658e Mon Sep 17 00:00:00 2001 From: Ulf Gebhardt Date: Mon, 17 Jul 2023 11:40:07 +0200 Subject: [PATCH 7/7] lint fix --- backend/src/schema/resolvers/rooms.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index c4c46f00d..690572e43 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -374,7 +374,7 @@ describe('Room', () => { ).resolves.toMatchObject({ data: { UnreadRooms: 2, - } + }, }) }) })