diff --git a/backend/src/graphql/rooms.ts b/backend/src/graphql/rooms.ts index 294b50641..b35a3e999 100644 --- a/backend/src/graphql/rooms.ts +++ b/backend/src/graphql/rooms.ts @@ -25,7 +25,7 @@ export const createRoomMutation = () => { export const roomQuery = () => { return gql` query Room($first: Int, $offset: Int, $id: ID) { - Room(first: $first, offset: $offset, id: $id, orderBy: createdAt_desc) { + Room(first: $first, offset: $offset, id: $id, orderBy: lastMessageAt_desc) { id roomId roomName diff --git a/backend/src/middleware/chatMiddleware.ts b/backend/src/middleware/chatMiddleware.ts index c28d6a70d..8ae252e13 100644 --- a/backend/src/middleware/chatMiddleware.ts +++ b/backend/src/middleware/chatMiddleware.ts @@ -54,4 +54,7 @@ export default { Mutation: { CreateRoom: roomProperties, }, + Subscription: { + chatMessageAdded: messageProperties, + }, } diff --git a/backend/src/schema/resolvers/messages.spec.ts b/backend/src/schema/resolvers/messages.spec.ts index 1679b0c34..83d9fdc6b 100644 --- a/backend/src/schema/resolvers/messages.spec.ts +++ b/backend/src/schema/resolvers/messages.spec.ts @@ -117,7 +117,7 @@ describe('Message', () => { }) describe('user chats in room', () => { - it('returns the message and publishes subscription', async () => { + it('returns the message and publishes subscriptions', async () => { await expect( mutate({ mutation: createMessageMutation(), @@ -146,6 +146,20 @@ describe('Message', () => { roomCountUpdated: '1', userId: 'other-chatting-user', }) + expect(pubsubSpy).toBeCalledWith('CHAT_MESSAGE_ADDED', { + chatMessageAdded: expect.objectContaining({ + id: expect.any(String), + content: 'Some nice message to other chatting user', + senderId: 'chatting-user', + username: 'Chatting User', + avatar: expect.any(String), + date: expect.any(String), + saved: true, + distributed: false, + seen: false, + }), + userId: 'other-chatting-user', + }) }) describe('room is updated as well', () => { diff --git a/backend/src/schema/resolvers/messages.ts b/backend/src/schema/resolvers/messages.ts index a908f3fd8..e473c2bb5 100644 --- a/backend/src/schema/resolvers/messages.ts +++ b/backend/src/schema/resolvers/messages.ts @@ -1,7 +1,9 @@ import { neo4jgraphql } from 'neo4j-graphql-js' import Resolver from './helpers/Resolver' + import { getUnreadRoomsCount } from './rooms' -import { pubsub, ROOM_COUNT_UPDATED } from '../../server' +import { pubsub, ROOM_COUNT_UPDATED, CHAT_MESSAGE_ADDED } from '../../server' +import { withFilter } from 'graphql-subscriptions' const setMessagesAsDistributed = async (undistributedMessagesIds, session) => { return session.writeTransaction(async (transaction) => { @@ -19,6 +21,16 @@ const setMessagesAsDistributed = async (undistributedMessagesIds, session) => { } export default { + Subscription: { + chatMessageAdded: { + subscribe: withFilter( + () => pubsub.asyncIterator(CHAT_MESSAGE_ADDED), + (payload, variables) => { + return payload.userId === variables.userId + }, + ), + }, + }, Query: { Message: async (object, params, context, resolveInfo) => { const { roomId } = params @@ -102,10 +114,14 @@ export default { const roomCountUpdated = await getUnreadRoomsCount(message.recipientId, session) // send subscriptions - await pubsub.publish(ROOM_COUNT_UPDATED, { + void pubsub.publish(ROOM_COUNT_UPDATED, { roomCountUpdated, userId: message.recipientId, }) + void pubsub.publish(CHAT_MESSAGE_ADDED, { + chatMessageAdded: message, + userId: message.recipientId, + }) } return message diff --git a/backend/src/schema/resolvers/rooms.spec.ts b/backend/src/schema/resolvers/rooms.spec.ts index ee291a6c9..2e26dc1e3 100644 --- a/backend/src/schema/resolvers/rooms.spec.ts +++ b/backend/src/schema/resolvers/rooms.spec.ts @@ -423,125 +423,147 @@ describe('Room', () => { }) it('returns the rooms paginated', async () => { - expect(await query({ query: roomQuery(), variables: { first: 3, offset: 0 } })).toMatchObject( - { - errors: undefined, - data: { - Room: [ - { + await expect( + query({ query: roomQuery(), variables: { first: 3, offset: 0 } }), + ).resolves.toMatchObject({ + errors: undefined, + data: { + Room: expect.arrayContaining([ + expect.objectContaining({ + id: expect.any(String), + roomId: expect.any(String), + roomName: 'Third Chatting User', + lastMessageAt: null, + unreadCount: 0, + lastMessage: null, + users: expect.arrayContaining([ + expect.objectContaining({ + _id: 'chatting-user', + id: 'chatting-user', + name: 'Chatting User', + avatar: { + url: expect.any(String), + }, + }), + expect.objectContaining({ + _id: 'third-chatting-user', + id: 'third-chatting-user', + name: 'Third Chatting User', + avatar: { + url: expect.any(String), + }, + }), + ]), + }), + expect.objectContaining({ + id: expect.any(String), + roomId: expect.any(String), + roomName: 'Second Chatting User', + lastMessageAt: null, + unreadCount: 0, + lastMessage: null, + users: expect.arrayContaining([ + expect.objectContaining({ + _id: 'chatting-user', + id: 'chatting-user', + name: 'Chatting User', + avatar: { + url: expect.any(String), + }, + }), + expect.objectContaining({ + _id: 'second-chatting-user', + id: 'second-chatting-user', + name: 'Second Chatting User', + avatar: { + url: expect.any(String), + }, + }), + ]), + }), + expect.objectContaining({ + id: expect.any(String), + roomId: expect.any(String), + roomName: 'Other Chatting User', + lastMessageAt: expect.any(String), + unreadCount: 0, + lastMessage: { + _id: expect.any(String), id: expect.any(String), - roomId: expect.any(String), - roomName: 'Third Chatting User', - users: expect.arrayContaining([ - { - _id: 'chatting-user', - id: 'chatting-user', - name: 'Chatting User', - avatar: { - url: expect.any(String), - }, - }, - { - _id: 'third-chatting-user', - id: 'third-chatting-user', - name: 'Third Chatting User', - avatar: { - url: expect.any(String), - }, - }, - ]), + content: '2nd message to other chatting user', + senderId: 'chatting-user', + username: 'Chatting User', + avatar: expect.any(String), + date: expect.any(String), + saved: true, + distributed: false, + seen: false, }, - { - id: expect.any(String), - roomId: expect.any(String), - roomName: 'Second Chatting User', - users: expect.arrayContaining([ - { - _id: 'chatting-user', - id: 'chatting-user', - name: 'Chatting User', - avatar: { - url: expect.any(String), - }, + users: expect.arrayContaining([ + expect.objectContaining({ + _id: 'chatting-user', + id: 'chatting-user', + name: 'Chatting User', + avatar: { + url: expect.any(String), }, - { - _id: 'second-chatting-user', - id: 'second-chatting-user', - name: 'Second Chatting User', - avatar: { - url: expect.any(String), - }, + }), + expect.objectContaining({ + _id: 'other-chatting-user', + id: 'other-chatting-user', + name: 'Other Chatting User', + avatar: { + url: expect.any(String), }, - ]), - }, - { - 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: 3, offset: 3 } })).toMatchObject( - { - errors: undefined, - data: { - Room: [ - { - id: expect.any(String), - roomId: expect.any(String), - roomName: 'Other Chatting User', - users: expect.arrayContaining([ - { - _id: 'chatting-user', - id: 'chatting-user', - name: 'Chatting User', - avatar: { - url: expect.any(String), - }, + }) + await expect( + query({ query: roomQuery(), variables: { first: 3, offset: 3 } }), + ).resolves.toMatchObject({ + errors: undefined, + data: { + Room: [ + expect.objectContaining({ + 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: 'other-chatting-user', - id: 'other-chatting-user', - name: 'Other Chatting User', - avatar: { - url: expect.any(String), - }, + }, + { + _id: 'not-chatting-user', + id: 'not-chatting-user', + name: 'Not Chatting User', + avatar: { + url: expect.any(String), }, - ]), - }, - ], - }, + }, + ]), + }), + ], }, - ) + }) }) }) describe('query single room', () => { let result: any = null + beforeAll(async () => { authenticatedUser = await chattingUser.toJson() result = await query({ query: roomQuery() }) }) + describe('as chatter of room', () => { it('returns the room', async () => { expect( @@ -556,34 +578,19 @@ describe('Room', () => { { id: expect.any(String), roomId: expect.any(String), - roomName: 'Third Chatting User', - users: expect.arrayContaining([ - { - _id: 'chatting-user', - id: 'chatting-user', - name: 'Chatting User', - avatar: { - url: expect.any(String), - }, - }, - { - _id: 'third-chatting-user', - id: 'third-chatting-user', - name: 'Third Chatting User', - avatar: { - url: expect.any(String), - }, - }, - ]), + roomName: result.data.Room[0].roomName, + users: expect.any(Array), }, ], }, }) }) + describe('as not chatter of room', () => { beforeAll(async () => { authenticatedUser = await notChattingUser.toJson() }) + it('returns no room', async () => { authenticatedUser = await notChattingUser.toJson() expect( diff --git a/backend/src/schema/types/type/Message.gql b/backend/src/schema/types/type/Message.gql index 764181dd9..71d175e1c 100644 --- a/backend/src/schema/types/type/Message.gql +++ b/backend/src/schema/types/type/Message.gql @@ -44,3 +44,7 @@ type Query { orderBy: [_MessageOrdering] ): [Message] } + +type Subscription { + chatMessageAdded(userId: ID!): Message +} diff --git a/backend/src/schema/types/type/Room.gql b/backend/src/schema/types/type/Room.gql index fdce6865b..0cf5b22c8 100644 --- a/backend/src/schema/types/type/Room.gql +++ b/backend/src/schema/types/type/Room.gql @@ -7,7 +7,7 @@ # TODO change this to last message date enum _RoomOrdering { - createdAt_desc + lastMessageAt_desc } type Room { diff --git a/backend/src/server.ts b/backend/src/server.ts index feceeb9eb..0522f5fc8 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -14,7 +14,7 @@ import bodyParser from 'body-parser' import { graphqlUploadExpress } from 'graphql-upload' export const NOTIFICATION_ADDED = 'NOTIFICATION_ADDED' -// export const CHAT_MESSAGE_ADDED = 'CHAT_MESSAGE_ADDED' +export const CHAT_MESSAGE_ADDED = 'CHAT_MESSAGE_ADDED' export const ROOM_COUNT_UPDATED = 'ROOM_COUNT_UPDATED' const { REDIS_DOMAIN, REDIS_PORT, REDIS_PASSWORD } = CONFIG let prodPubsub, devPubsub diff --git a/webapp/components/Chat/Chat.vue b/webapp/components/Chat/Chat.vue index 43994ef5d..3a059f64e 100644 --- a/webapp/components/Chat/Chat.vue +++ b/webapp/components/Chat/Chat.vue @@ -61,7 +61,12 @@