diff --git a/apps/server/src/infra/etherpad-client/etherpad-client.adapter.spec.ts b/apps/server/src/infra/etherpad-client/etherpad-client.adapter.spec.ts index 31d9c25ca02..d730c6c26d5 100644 --- a/apps/server/src/infra/etherpad-client/etherpad-client.adapter.spec.ts +++ b/apps/server/src/infra/etherpad-client/etherpad-client.adapter.spec.ts @@ -968,7 +968,7 @@ describe(EtherpadClientAdapter.name, () => { return groupId; }; - it('should call deletePadUsingGET with correct params', async () => { + it('should call deleteGroupUsingGET with correct params', async () => { const groupId = setup(); await service.deleteGroup(groupId); @@ -977,12 +977,12 @@ describe(EtherpadClientAdapter.name, () => { }); }); - describe('when deleteGroupUsingPOST returns etherpad error code', () => { + describe('when deleteGroupUsingPOST returns valid etherpad error', () => { const setup = () => { const groupId = 'groupId'; const response = createMock>({ data: { - code: EtherpadResponseCode.BAD_REQUEST, + code: EtherpadResponseCode.INTERNAL_ERROR, data: {}, }, }); @@ -995,11 +995,34 @@ describe(EtherpadClientAdapter.name, () => { it('should throw EtherpadErrorLoggableException', async () => { const groupId = setup(); - const exception = new EtherpadErrorLoggableException(EtherpadErrorType.BAD_REQUEST, { padId: groupId }, {}); + const exception = new EtherpadErrorLoggableException(EtherpadErrorType.INTERNAL_ERROR, { padId: groupId }, {}); await expect(service.deleteGroup(groupId)).rejects.toThrowError(exception); }); }); + describe('when deleteGroupUsingPOST returns invalid etherpad error', () => { + const setup = () => { + const groupId = 'groupId'; + const response = createMock>({ + data: { + code: EtherpadResponseCode.BAD_REQUEST, + message: 'sessionID does not exist', + data: {}, + }, + }); + + groupApi.deleteGroupUsingPOST.mockResolvedValue(response); + + return groupId; + }; + + it('should return successfull', async () => { + const groupId = setup(); + + await service.deleteGroup(groupId); + }); + }); + describe('when deleteGroupUsingPOST returns error', () => { const setup = () => { const groupId = 'padId'; diff --git a/apps/server/src/infra/etherpad-client/etherpad-client.adapter.ts b/apps/server/src/infra/etherpad-client/etherpad-client.adapter.ts index 404a544a0ae..3b87606ddbc 100644 --- a/apps/server/src/infra/etherpad-client/etherpad-client.adapter.ts +++ b/apps/server/src/infra/etherpad-client/etherpad-client.adapter.ts @@ -3,6 +3,8 @@ import { TypeGuard } from '@shared/common'; import { EntityId } from '@shared/domain/types'; import { AxiosResponse } from 'axios'; import { + AuthorApi, + GroupApi, InlineResponse200, InlineResponse2001, InlineResponse20013, @@ -10,8 +12,9 @@ import { InlineResponse2003, InlineResponse2004, InlineResponse2006, + PadApi, + SessionApi, } from './etherpad-api-client'; -import { AuthorApi, GroupApi, PadApi, SessionApi } from './etherpad-api-client/api'; import { AuthorId, EtherpadErrorType, @@ -23,6 +26,7 @@ import { Session, SessionId, } from './interface'; +import { EtherpadErrorLoggableException } from './loggable'; import { EtherpadResponseMapper } from './mappers'; @Injectable() @@ -225,7 +229,29 @@ export class EtherpadClientAdapter { public async deleteGroup(groupId: GroupId): Promise { const response = await this.tryDeleteGroup(groupId); - this.handleEtherpadResponse(response, { groupId }); + + try { + this.handleEtherpadResponse(response, { groupId }); + } catch (error) { + this.throwIfValidError(error); + } + } + + private throwIfValidError(error: unknown): void { + // If the session does not exist, we can ignore the error. See https://github.com/ether/etherpad-lite/issues/5798 + if (this.isSessionDoesNotExistError(error)) { + return; + } + + throw error; + } + + private isSessionDoesNotExistError(error: unknown): boolean { + return !!( + error instanceof EtherpadErrorLoggableException && + error.getLogMessage().type === EtherpadErrorType.BAD_REQUEST && + error?.cause?.toString().includes('sessionID does not exist') + ); } private async tryDeleteGroup(groupId: string): Promise> { diff --git a/apps/server/src/modules/board/gateway/api-test/board-collaboration.gateway.spec.ts b/apps/server/src/modules/board/gateway/api-test/board-collaboration.gateway.spec.ts index 53a15082226..206579c57fc 100644 --- a/apps/server/src/modules/board/gateway/api-test/board-collaboration.gateway.spec.ts +++ b/apps/server/src/modules/board/gateway/api-test/board-collaboration.gateway.spec.ts @@ -1,4 +1,4 @@ -import { EntityManager, ObjectId } from '@mikro-orm/mongodb'; +import { EntityManager } from '@mikro-orm/mongodb'; import { INestApplication } from '@nestjs/common'; import { Test } from '@nestjs/testing'; @@ -22,6 +22,7 @@ import { BoardCollaborationGateway } from '../board-collaboration.gateway'; describe(BoardCollaborationGateway.name, () => { let app: INestApplication; let ioClient: Socket; + let unauthorizedIoClient: Socket; let em: EntityManager; beforeAll(async () => { @@ -43,17 +44,20 @@ describe(BoardCollaborationGateway.name, () => { afterAll(async () => { ioClient.disconnect(); + unauthorizedIoClient.disconnect(); await app.close(); }); const setup = async () => { await cleanupCollections(em); const user = userFactory.buildWithId(); + const unauthorizedUser = userFactory.buildWithId(); const course = courseFactory.build({ teachers: [user] }); - await em.persistAndFlush([user, course]); + await em.persistAndFlush([user, unauthorizedUser, course]); ioClient = await getSocketApiClient(app, user); + unauthorizedIoClient = await getSocketApiClient(app, unauthorizedUser); const columnBoardNode = columnBoardNodeFactory.buildWithId({ context: { id: course.id, type: BoardExternalReferenceType.Course }, @@ -69,7 +73,7 @@ describe(BoardCollaborationGateway.name, () => { em.clear(); - return { user, columnBoardNode, columnNode, columnNode2, cardNodes, elementNodes }; + return { columnBoardNode, columnNode, columnNode2, cardNodes, elementNodes }; }; describe('validation errors', () => { @@ -98,15 +102,14 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when column does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const columnId = new ObjectId().toHexString(); + const { columnNode } = await setup(); - ioClient.emit('create-card-request', { columnId }); - const failure = await waitForEvent(ioClient, 'create-card-failure'); + unauthorizedIoClient.emit('create-card-request', { columnId: columnNode.id }); + const failure = await waitForEvent(unauthorizedIoClient, 'create-card-failure'); - expect(failure).toEqual({ columnId }); + expect(failure).toEqual({ columnId: columnNode.id }); }); }); }); @@ -124,13 +127,13 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when board does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const boardId = new ObjectId().toHexString(); + const { columnBoardNode } = await setup(); + const boardId = columnBoardNode.id; - ioClient.emit('fetch-board-request', { boardId }); - const failure = await waitForEvent(ioClient, 'fetch-board-failure'); + unauthorizedIoClient.emit('fetch-board-request', { boardId }); + const failure = await waitForEvent(unauthorizedIoClient, 'fetch-board-failure'); expect(failure).toEqual({ boardId }); }); @@ -180,22 +183,22 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when trying to move a non existing card', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - const { columnNode } = await setup(); + const { columnNode, cardNodes } = await setup(); const moveCardProps = { - cardId: new ObjectId().toHexString(), + cardId: cardNodes[0].id, oldIndex: 0, newIndex: 1, fromColumnId: columnNode.id, fromColumnIndex: 0, - toColumnId: columnNode.id, toColumnIndex: 0, + toColumnId: columnNode.id, }; - ioClient.emit('move-card-request', moveCardProps); - const failure = await waitForEvent(ioClient, 'move-card-failure'); + unauthorizedIoClient.emit('move-card-request', moveCardProps); + const failure = await waitForEvent(unauthorizedIoClient, 'move-card-failure'); expect(failure).toEqual(moveCardProps); }); @@ -214,13 +217,13 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when column does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const updateColumnProps = { columnId: new ObjectId().toHexString(), newTitle: 'new title' }; + const { columnNode } = await setup(); + const updateColumnProps = { columnId: columnNode.id, newTitle: 'new title' }; - ioClient.emit('update-column-title-request', updateColumnProps); - const failure = await waitForEvent(ioClient, 'update-column-title-failure'); + unauthorizedIoClient.emit('update-column-title-request', updateColumnProps); + const failure = await waitForEvent(unauthorizedIoClient, 'update-column-title-failure'); expect(failure).toEqual(updateColumnProps); }); @@ -240,15 +243,15 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when board does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const payload = { boardId: new ObjectId().toHexString() }; + const { columnBoardNode } = await setup(); + const boardId = columnBoardNode.id; - ioClient.emit('delete-board-request', payload); - const failure = await waitForEvent(ioClient, 'delete-board-failure'); + unauthorizedIoClient.emit('delete-board-request', { boardId }); + const failure = await waitForEvent(unauthorizedIoClient, 'delete-board-failure'); - expect(failure).toEqual(payload); + expect(failure).toEqual({ boardId }); }); }); }); @@ -266,16 +269,16 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when board does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const payload = { boardId: new ObjectId().toHexString(), newTitle: 'new title' }; + const { columnBoardNode } = await setup(); + const boardId = columnBoardNode.id; - ioClient.emit('update-board-title-request', payload); - const failure = await waitForEvent(ioClient, 'update-board-title-failure'); + unauthorizedIoClient.emit('update-board-title-request', { boardId, newTitle: 'new title' }); + const failure = await waitForEvent(unauthorizedIoClient, 'update-board-title-failure'); expect(failure).toBeDefined(); - expect(failure).toEqual(payload); + expect(failure).toEqual({ boardId, newTitle: 'new title' }); }); }); }); @@ -293,15 +296,15 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when board does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const payload = { boardId: new ObjectId().toHexString() }; + const { columnBoardNode } = await setup(); + const boardId = columnBoardNode.id; - ioClient.emit('create-column-request', payload); - const failure = await waitForEvent(ioClient, 'create-column-failure'); + unauthorizedIoClient.emit('create-column-request', { boardId }); + const failure = await waitForEvent(unauthorizedIoClient, 'create-column-failure'); - expect(failure).toEqual(payload); + expect(failure).toEqual({ boardId }); }); }); }); @@ -319,13 +322,13 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when board does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const boardId = new ObjectId().toHexString(); + const { columnBoardNode } = await setup(); + const boardId = columnBoardNode.id; - ioClient.emit('update-board-visibility-request', { boardId, isVisible: false }); - const failure = await waitForEvent(ioClient, 'update-board-visibility-failure'); + unauthorizedIoClient.emit('update-board-visibility-request', { boardId, isVisible: false }); + const failure = await waitForEvent(unauthorizedIoClient, 'update-board-visibility-failure'); expect(failure).toEqual({ boardId, isVisible: false }); }); @@ -345,13 +348,13 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when column does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const columnId = new ObjectId().toHexString(); + const { columnNode } = await setup(); + const columnId = columnNode.id; - ioClient.emit('delete-column-request', { columnId }); - const failure = await waitForEvent(ioClient, 'delete-column-failure'); + unauthorizedIoClient.emit('delete-column-request', { columnId }); + const failure = await waitForEvent(unauthorizedIoClient, 'delete-column-failure'); expect(failure).toEqual({ columnId }); }); @@ -379,21 +382,21 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when column does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - const { columnBoardNode } = await setup(); + const { columnBoardNode, columnNode } = await setup(); const payload = { targetBoardId: columnBoardNode.id, columnMove: { addedIndex: 1, removedIndex: 0, - columnId: new ObjectId().toHexString(), + columnId: columnNode.id, }, }; - ioClient.emit('move-column-request', payload); - const failure = await waitForEvent(ioClient, 'move-column-failure'); + unauthorizedIoClient.emit('move-column-request', payload); + const failure = await waitForEvent(unauthorizedIoClient, 'move-column-failure'); expect(failure).toEqual(payload); }); @@ -413,13 +416,13 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when card does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const payload = { cardId: new ObjectId().toHexString(), newTitle: 'new title' }; + const { cardNodes } = await setup(); + const payload = { cardId: cardNodes[0].id, newTitle: 'new title' }; - ioClient.emit('update-card-title-request', payload); - const failure = await waitForEvent(ioClient, 'update-card-title-failure'); + unauthorizedIoClient.emit('update-card-title-request', payload); + const failure = await waitForEvent(unauthorizedIoClient, 'update-card-title-failure'); expect(failure).toEqual(payload); }); @@ -440,15 +443,16 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when card does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const payload = { cardId: new ObjectId().toHexString(), newHeight: 200 }; + const { cardNodes } = await setup(); + const cardId = cardNodes[0].id; + const newHeight = 200; - ioClient.emit('update-card-height-request', payload); - const failure = await waitForEvent(ioClient, 'update-card-height-failure'); + unauthorizedIoClient.emit('update-card-height-request', { cardId, newHeight }); + const failure = await waitForEvent(unauthorizedIoClient, 'update-card-height-failure'); - expect(failure).toEqual(payload); + expect(failure).toEqual({ cardId, newHeight }); }); }); }); @@ -466,15 +470,30 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when card does not exist', () => { + describe('when user is not authorized', () => { + it('should not return any cards', async () => { + const { cardNodes } = await setup(); + const cardIds = cardNodes.map((card) => card.id); + + unauthorizedIoClient.emit('fetch-card-request', { cardIds }); + + const success = (await waitForEvent(unauthorizedIoClient, 'fetch-card-success')) as { + cards: { title: string }[]; + }; + + expect(success.cards.length).toEqual(0); + }); + }); + + describe('when an error is thrown', () => { it('should answer with failure', async () => { - await setup(); - const payload = { cardIds: [new ObjectId().toHexString()] }; + const { cardNodes, columnNode } = await setup(); - ioClient.emit('fetch-card-request', payload); + // passing a column id instead of a card id to force an error + ioClient.emit('fetch-card-request', { cardIds: [cardNodes[0].id, columnNode.id] }); const failure = await waitForEvent(ioClient, 'fetch-card-failure'); - expect(failure).toEqual(payload); + expect(failure).toBeDefined(); }); }); }); @@ -492,15 +511,15 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when card does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const payload = { cardId: new ObjectId().toHexString() }; + const { cardNodes } = await setup(); + const cardId = cardNodes[0].id; - ioClient.emit('delete-card-request', payload); - const failure = await waitForEvent(ioClient, 'delete-card-failure'); + unauthorizedIoClient.emit('delete-card-request', { cardId }); + const failure = await waitForEvent(unauthorizedIoClient, 'delete-card-failure'); - expect(failure).toEqual(payload); + expect(failure).toEqual({ cardId }); }); }); }); @@ -519,17 +538,15 @@ describe(BoardCollaborationGateway.name, () => { expect(Object.keys(success)).toEqual(expect.arrayContaining(['cardId', 'newElement'])); }); - describe('when card does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); - const cardId = new ObjectId().toHexString(); - - const payload = { cardId, type: ContentElementType.RICH_TEXT }; + const { cardNodes } = await setup(); + const cardId = cardNodes[1].id; - ioClient.emit('create-element-request', payload); - const failure = await waitForEvent(ioClient, 'create-element-failure'); + unauthorizedIoClient.emit('create-element-request', { cardId, type: ContentElementType.RICH_TEXT }); + const failure = await waitForEvent(unauthorizedIoClient, 'create-element-failure'); - expect(failure).toEqual(payload); + expect(failure).toEqual({ cardId, type: ContentElementType.RICH_TEXT }); }); }); }); @@ -548,15 +565,16 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when element does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - const { cardNodes } = await setup(); - const payload = { cardId: cardNodes[0].id, elementId: new ObjectId().toHexString() }; + const { cardNodes, elementNodes } = await setup(); + const cardId = cardNodes[0].id; + const elementId = elementNodes[0].id; - ioClient.emit('delete-element-request', payload); - const failure = await waitForEvent(ioClient, 'delete-element-failure'); + unauthorizedIoClient.emit('delete-element-request', { cardId, elementId }); + const failure = await waitForEvent(unauthorizedIoClient, 'delete-element-failure'); - expect(failure).toEqual(payload); + expect(failure).toEqual({ cardId, elementId }); }); }); }); @@ -582,19 +600,21 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when element does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - await setup(); + const { elementNodes } = await setup(); + const elementId = elementNodes[0].id; + const payload = { - elementId: new ObjectId().toHexString(), + elementId, data: { type: ContentElementType.RICH_TEXT, content: { text: 'some new text', inputFormat: InputFormat.PLAIN_TEXT }, }, }; - ioClient.emit('update-element-request', payload); - const failure = await waitForEvent(ioClient, 'update-element-failure'); + unauthorizedIoClient.emit('update-element-request', payload); + const failure = await waitForEvent(unauthorizedIoClient, 'update-element-failure'); expect(failure).toEqual(payload); }); @@ -614,13 +634,13 @@ describe(BoardCollaborationGateway.name, () => { }); }); - describe('when element does not exist', () => { + describe('when user is not authorized', () => { it('should answer with failure', async () => { - const { cardNodes } = await setup(); - const payload = { elementId: new ObjectId().toHexString(), toCardId: cardNodes[0].id, toPosition: 2 }; + const { cardNodes, elementNodes } = await setup(); + const payload = { elementId: elementNodes[0].id, toCardId: cardNodes[0].id, toPosition: 2 }; - ioClient.emit('move-element-request', payload); - const failure = await waitForEvent(ioClient, 'move-element-failure'); + unauthorizedIoClient.emit('move-element-request', payload); + const failure = await waitForEvent(unauthorizedIoClient, 'move-element-failure'); expect(failure).toEqual(payload); }); diff --git a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts index edcf4fd6c8e..dac4ed834fb 100644 --- a/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts +++ b/apps/server/src/modules/board/gateway/board-collaboration.gateway.ts @@ -51,111 +51,111 @@ export class BoardCollaborationGateway { @SubscribeMessage('delete-board-request') @UseRequestContext() async deleteBoard(socket: Socket, data: DeleteBoardMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.boardId, action: 'delete-board' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.boardId); - const { userId } = this.getCurrentUser(socket); await this.boardUc.deleteBoard(userId, data.boardId); - await emitter.emitToClientAndRoom('delete-board-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('delete-board-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('update-board-title-request') @UseRequestContext() async updateBoardTitle(socket: Socket, data: UpdateBoardTitleMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.boardId, action: 'update-board-title' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.boardId); - const { userId } = this.getCurrentUser(socket); await this.boardUc.updateBoardTitle(userId, data.boardId, data.newTitle); - await emitter.emitToClientAndRoom('update-board-title-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('update-board-title-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('update-card-title-request') @UseRequestContext() async updateCardTitle(socket: Socket, data: UpdateCardTitleMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.cardId, action: 'update-card-title' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.cardId); - const { userId } = this.getCurrentUser(socket); await this.cardUc.updateCardTitle(userId, data.cardId, data.newTitle); - await emitter.emitToClientAndRoom('update-card-title-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('update-card-title-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('update-card-height-request') @UseRequestContext() async updateCardHeight(socket: Socket, data: UpdateCardHeightMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.cardId, action: 'update-card-height' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.cardId); - const { userId } = this.getCurrentUser(socket); await this.cardUc.updateCardHeight(userId, data.cardId, data.newHeight); - await emitter.emitToClientAndRoom('update-card-height-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('update-card-height-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('delete-card-request') @UseRequestContext() async deleteCard(socket: Socket, data: DeleteCardMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.cardId, action: 'delete-card' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.cardId); - const { userId } = this.getCurrentUser(socket); await this.cardUc.deleteCard(userId, data.cardId); - await emitter.emitToClientAndRoom('delete-card-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('delete-card-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('create-card-request') @UseRequestContext() async createCard(socket: Socket, data: CreateCardMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.columnId, action: 'create-card' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.columnId); - const { userId } = this.getCurrentUser(socket); const card = await this.columnUc.createCard(userId, data.columnId); const responsePayload = { ...data, newCard: card.getProps(), }; - await emitter.emitToClientAndRoom('create-card-success', responsePayload); + await emitter.emitToClientAndRoom(responsePayload); } catch (err) { - socket.emit('create-card-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('create-column-request') @UseRequestContext() async createColumn(socket: Socket, data: CreateColumnMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.boardId, action: 'create-column' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.boardId); - const { userId } = this.getCurrentUser(socket); const column = await this.boardUc.createColumn(userId, data.boardId); + const newColumn = ColumnResponseMapper.mapToResponse(column); const responsePayload = { ...data, newColumn, }; - - await emitter.emitToClientAndRoom('create-column-success', responsePayload); + await emitter.emitToClientAndRoom(responsePayload); // payload needs to be returned to allow the client to do sequential operation // of createColumn and move the card into that column return responsePayload; } catch (err) { - socket.emit('create-column-failure', data); + emitter.emitFailure(data); return {}; } } @@ -163,186 +163,178 @@ export class BoardCollaborationGateway { @SubscribeMessage('fetch-board-request') @UseRequestContext() async fetchBoard(socket: Socket, data: FetchBoardMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.boardId, action: 'fetch-board' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.boardId); - const { userId } = this.getCurrentUser(socket); - const board = await this.boardUc.findBoard(userId, data.boardId); const responsePayload = BoardResponseMapper.mapToResponse(board); - await emitter.emitToClient('fetch-board-success', responsePayload); + await emitter.emitToClient(responsePayload); } catch (err) { - socket.emit('fetch-board-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('move-card-request') @UseRequestContext() async moveCard(socket: Socket, data: MoveCardMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.cardId, action: 'move-card' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.cardId); - const { userId } = this.getCurrentUser(socket); - await this.columnUc.moveCard(userId, data.cardId, data.toColumnId, data.newIndex); - await emitter.emitToClientAndRoom('move-card-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('move-card-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('move-column-request') @UseRequestContext() async moveColumn(socket: Socket, data: MoveColumnMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.targetBoardId, action: 'move-column' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.targetBoardId); - const { userId } = this.getCurrentUser(socket); - await this.boardUc.moveColumn(userId, data.columnMove.columnId, data.targetBoardId, data.columnMove.addedIndex); - await emitter.emitToClientAndRoom('move-column-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('move-column-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('update-column-title-request') @UseRequestContext() async updateColumnTitle(socket: Socket, data: UpdateColumnTitleMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.columnId, action: 'update-column-title' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.columnId); - const { userId } = this.getCurrentUser(socket); - await this.columnUc.updateColumnTitle(userId, data.columnId, data.newTitle); - await emitter.emitToClientAndRoom('update-column-title-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('update-column-title-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('update-board-visibility-request') @UseRequestContext() async updateBoardVisibility(socket: Socket, data: UpdateBoardVisibilityMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.boardId, action: 'update-board-visibility' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.boardId); - const { userId } = this.getCurrentUser(socket); - await this.boardUc.updateVisibility(userId, data.boardId, data.isVisible); - await emitter.emitToClientAndRoom('update-board-visibility-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('update-board-visibility-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('delete-column-request') @UseRequestContext() async deleteColumn(socket: Socket, data: DeleteColumnMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.columnId, action: 'delete-column' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.columnId); - const { userId } = this.getCurrentUser(socket); - await this.columnUc.deleteColumn(userId, data.columnId); - await emitter.emitToClientAndRoom('delete-column-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('delete-column-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('fetch-card-request') @UseRequestContext() async fetchCards(socket: Socket, data: FetchCardsMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.cardIds[0], action: 'fetch-card' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.cardIds[0]); - const { userId } = this.getCurrentUser(socket); - const cards = await this.cardUc.findCards(userId, data.cardIds); const cardResponses = cards.map((card) => CardResponseMapper.mapToResponse(card)); - await emitter.emitToClient('fetch-card-success', { cards: cardResponses, isOwnAction: false }); + await emitter.emitToClient({ cards: cardResponses, isOwnAction: false }); } catch (err) { - socket.emit('fetch-card-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('create-element-request') @UseRequestContext() async createElement(socket: Socket, data: CreateContentElementMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.cardId, action: 'create-element' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.cardId); - const { userId } = this.getCurrentUser(socket); - const element = await this.cardUc.createElement(userId, data.cardId, data.type, data.toPosition); const responsePayload = { ...data, newElement: ContentElementResponseFactory.mapToResponse(element), }; - await emitter.emitToClientAndRoom('create-element-success', responsePayload); + await emitter.emitToClientAndRoom(responsePayload); } catch (err) { - socket.emit('create-element-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('update-element-request') @UseRequestContext() async updateElement(socket: Socket, data: UpdateContentElementMessageParams) { + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.elementId, action: 'update-element' }); + const { userId } = this.getCurrentUser(socket); try { - const emitter = await this.buildBoardSocketEmitter(socket, data.elementId); - const { userId } = this.getCurrentUser(socket); - await this.elementUc.updateElement(userId, data.elementId, data.data.content); - await emitter.emitToClientAndRoom('update-element-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('update-element-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('delete-element-request') @UseRequestContext() async deleteElement(socket: Socket, data: DeleteContentElementMessageParams) { - try { - const emitter = await this.buildBoardSocketEmitter(socket, data.elementId); - const { userId } = this.getCurrentUser(socket); + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.elementId, action: 'delete-element' }); + const { userId } = this.getCurrentUser(socket); + try { await this.elementUc.deleteElement(userId, data.elementId); - - await emitter.emitToClientAndRoom('delete-element-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('delete-element-failure', data); + emitter.emitFailure(data); } } @SubscribeMessage('move-element-request') @UseRequestContext() async moveElement(socket: Socket, data: MoveContentElementMessageParams) { - try { - const emitter = await this.buildBoardSocketEmitter(socket, data.elementId); - const { userId } = this.getCurrentUser(socket); + const emitter = await this.buildBoardSocketEmitter({ socket, id: data.elementId, action: 'move-element' }); + const { userId } = this.getCurrentUser(socket); + try { await this.cardUc.moveElement(userId, data.elementId, data.toCardId, data.toPosition); - - await emitter.emitToClientAndRoom('move-element-success', data); + await emitter.emitToClientAndRoom(data); } catch (err) { - socket.emit('move-element-failure', data); + emitter.emitFailure(data); } } - private async buildBoardSocketEmitter(client: Socket, id: string) { + private async buildBoardSocketEmitter({ socket, id, action }: { socket: Socket; id: string; action: string }) { const rootId = await this.getRootIdForId(id); const room = `board_${rootId}`; return { - async emitToClient(event: string, data: object) { - await client.join(room); - client.emit(event, { ...data, isOwnAction: true }); + async emitToClient(data: object) { + await socket.join(room); + socket.emit(`${action}-success`, { ...data, isOwnAction: true }); + }, + async emitToClientAndRoom(data: object) { + await socket.join(room); + socket.to(room).emit(`${action}-success`, { ...data, isOwnAction: false }); + socket.emit(`${action}-success`, { ...data, isOwnAction: true }); }, - async emitToClientAndRoom(event: string, data: object) { - await client.join(room); - client.to(room).emit(event, { ...data, isOwnAction: false }); - client.emit(event, { ...data, isOwnAction: true }); + emitFailure(data: object) { + socket.emit(`${action}-failure`, data); }, }; }