From 190075055abd1bd0f98d7c24602da8f37f76f4a2 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 20 May 2026 11:25:14 +0200 Subject: [PATCH 1/6] use id for leave call --- src/modules/call/call.controller.ts | 10 +++++++--- src/modules/call/call.usecase.ts | 2 +- .../call/infrastructure/room-user.repository.ts | 9 +-------- src/modules/call/services/call.service.ts | 9 ++++++++- src/modules/call/services/room.service.ts | 17 ++--------------- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/src/modules/call/call.controller.ts b/src/modules/call/call.controller.ts index 4b44e90..6f16ac8 100644 --- a/src/modules/call/call.controller.ts +++ b/src/modules/call/call.controller.ts @@ -175,10 +175,14 @@ export class CallController { @ApiInternalServerErrorResponse({ description: 'Internal server error' }) leaveCall( @Param('id') roomId: string, - @User() user: UserTokenData['payload'], @Body() leaveCallDto?: LeaveCallDto, ): Promise { - const { uuid } = user || {}; - return this.callUseCase.leaveCall(roomId, uuid || leaveCallDto?.userId); + if (leaveCallDto?.userId) { + this.logger.warn( + `Attempt to create leave call without ID for user: ${leaveCallDto}`, + ); + throw new BadRequestException('The user id is needed to leave the call'); + } + return this.callUseCase.leaveCall(roomId, leaveCallDto?.userId); } } diff --git a/src/modules/call/call.usecase.ts b/src/modules/call/call.usecase.ts index 54d88a7..5cc46b8 100644 --- a/src/modules/call/call.usecase.ts +++ b/src/modules/call/call.usecase.ts @@ -182,7 +182,7 @@ export class CallUseCase { return { token: callTokenData, room: roomId, - userId: roomUser.userId, + userId: roomUser.id, appId: this.configService.get('jitsi.appId'), }; } diff --git a/src/modules/call/infrastructure/room-user.repository.ts b/src/modules/call/infrastructure/room-user.repository.ts index b7b90fe..7e7708e 100644 --- a/src/modules/call/infrastructure/room-user.repository.ts +++ b/src/modules/call/infrastructure/room-user.repository.ts @@ -82,7 +82,7 @@ export class SequelizeRoomUserRepository { } async deleteByUserIdAndRoomId(userId: string, roomId: string): Promise { - await this.roomUserModel.destroy({ where: { userId, roomId } }); + await this.roomUserModel.destroy({ where: { id: userId, roomId } }); } async findByParticipantIdAndRoomId( @@ -95,13 +95,6 @@ export class SequelizeRoomUserRepository { return roomUser ? RoomUser.build(roomUser) : null; } - async deleteByParticipantIdAndRoomId( - participantId: string, - roomId: string, - ): Promise { - await this.roomUserModel.destroy({ where: { participantId, roomId } }); - } - async destroyParticipantWithOlderTimestamp( roomUserId: string, participantId: string, diff --git a/src/modules/call/services/call.service.ts b/src/modules/call/services/call.service.ts index 3184e9f..6fdcf3b 100644 --- a/src/modules/call/services/call.service.ts +++ b/src/modules/call/services/call.service.ts @@ -48,6 +48,13 @@ export class CallService { private async getMeetFeatureConfigForUser( userUuid: string, ): Promise { + const isProduction = this.configService.get('isProduction') + if (!isProduction) { + return { + enabled: true, + paxPerCall: 10, + }; + } const userFeatures = await this.paymentService .getUserTier(userUuid) .catch((err) => { @@ -64,7 +71,7 @@ export class CallService { } async createCall(user: User | UserTokenData['payload']) { - const meetFeatures = await this.getMeetFeatureConfigForUser(user.uuid); + const meetFeatures = await this.getMeetFeatureConfigForUser(user.uuid); if (!meetFeatures.enabled) throw new UnauthorizedException( diff --git a/src/modules/call/services/room.service.ts b/src/modules/call/services/room.service.ts index 4a819bb..2d2209d 100644 --- a/src/modules/call/services/room.service.ts +++ b/src/modules/call/services/room.service.ts @@ -157,22 +157,10 @@ export class RoomService { { transaction, lock: true }, ); - if (existingUser) { - if (existingUser.participantId) { + if (existingUser?.participantId) { oldParticipantId = existingUser.participantId; - } - - await this.roomUserRepository.update( - existingUser.id, - { - participantId: null, - joinedAt: null, - }, - transaction, - ); + } - roomUser = existingUser; - } else { roomUser = await this.roomUserRepository.create( { roomId, @@ -183,7 +171,6 @@ export class RoomService { }, transaction, ); - } }); return { roomUser, oldParticipantId }; From a50f9893a42efc53e8d005e4bf2891c2b0e17c0a Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 20 May 2026 14:35:01 +0200 Subject: [PATCH 2/6] fix typo --- src/modules/call/call.controller.ts | 2 +- src/modules/call/services/call.service.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/modules/call/call.controller.ts b/src/modules/call/call.controller.ts index 6f16ac8..a967c2c 100644 --- a/src/modules/call/call.controller.ts +++ b/src/modules/call/call.controller.ts @@ -179,7 +179,7 @@ export class CallController { ): Promise { if (leaveCallDto?.userId) { this.logger.warn( - `Attempt to create leave call without ID for user: ${leaveCallDto}`, + `Attempt to leave call without user ID: ${leaveCallDto}`, ); throw new BadRequestException('The user id is needed to leave the call'); } diff --git a/src/modules/call/services/call.service.ts b/src/modules/call/services/call.service.ts index 6fdcf3b..c78e5a1 100644 --- a/src/modules/call/services/call.service.ts +++ b/src/modules/call/services/call.service.ts @@ -71,7 +71,7 @@ export class CallService { } async createCall(user: User | UserTokenData['payload']) { - const meetFeatures = await this.getMeetFeatureConfigForUser(user.uuid); + const meetFeatures = await this.getMeetFeatureConfigForUser(user.uuid); if (!meetFeatures.enabled) throw new UnauthorizedException( From e19fc5c2dac5a6bc10a92b2218f0e2274b50d850 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 20 May 2026 14:57:19 +0200 Subject: [PATCH 3/6] fix tests --- src/modules/call/call.usecase.spec.ts | 6 +++--- .../call/infrastructure/room-user.repository.spec.ts | 2 +- src/modules/call/infrastructure/room-user.repository.ts | 7 +++++++ 3 files changed, 11 insertions(+), 4 deletions(-) diff --git a/src/modules/call/call.usecase.spec.ts b/src/modules/call/call.usecase.spec.ts index cf31b28..ea3c821 100644 --- a/src/modules/call/call.usecase.spec.ts +++ b/src/modules/call/call.usecase.spec.ts @@ -246,7 +246,7 @@ describe('CallUseCase', () => { expect(result).toEqual({ token: 'test-jwt-token', room: roomId, - userId, + userId: autoGeneratedUUID, appId: 'jitsi-app-id', }); }); @@ -418,7 +418,7 @@ describe('CallUseCase', () => { expect(result).toEqual({ token: 'test-jwt-token', room: roomId, - userId, + userId: autoGeneratedUUID, appId: 'jitsi-app-id', }); }); @@ -490,7 +490,7 @@ describe('CallUseCase', () => { anonymous: false, }, ); - expect(result.userId).toEqual(roomUserMock.userId); + expect(result.userId).toEqual(roomUserMock.id); }); }); diff --git a/src/modules/call/infrastructure/room-user.repository.spec.ts b/src/modules/call/infrastructure/room-user.repository.spec.ts index 7d742a5..55974e0 100644 --- a/src/modules/call/infrastructure/room-user.repository.spec.ts +++ b/src/modules/call/infrastructure/room-user.repository.spec.ts @@ -225,7 +225,7 @@ describe('SequelizeRoomUserRepository', () => { expect(deleteRoomUserSpy).toHaveBeenCalledWith({ where: { - userId: 'test-user-id', + id: 'test-user-id', roomId: 'test-room-id', }, }); diff --git a/src/modules/call/infrastructure/room-user.repository.ts b/src/modules/call/infrastructure/room-user.repository.ts index 7e7708e..d0a2653 100644 --- a/src/modules/call/infrastructure/room-user.repository.ts +++ b/src/modules/call/infrastructure/room-user.repository.ts @@ -95,6 +95,13 @@ export class SequelizeRoomUserRepository { return roomUser ? RoomUser.build(roomUser) : null; } + async deleteByParticipantIdAndRoomId( + participantId: string, + roomId: string, + ): Promise { + await this.roomUserModel.destroy({ where: { participantId, roomId } }); + } + async destroyParticipantWithOlderTimestamp( roomUserId: string, participantId: string, From 93f79a03a692489a2f32e551ecaa1198fb6c8952 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 20 May 2026 15:20:02 +0200 Subject: [PATCH 4/6] fix if, missing negation --- src/modules/call/call.controller.spec.ts | 30 ++++++++++-------------- src/modules/call/call.controller.ts | 8 +++---- 2 files changed, 16 insertions(+), 22 deletions(-) diff --git a/src/modules/call/call.controller.spec.ts b/src/modules/call/call.controller.spec.ts index fcb4561..3c1ac66 100644 --- a/src/modules/call/call.controller.spec.ts +++ b/src/modules/call/call.controller.spec.ts @@ -316,11 +316,10 @@ describe('Testing Call Endpoints', () => { describe('Leaving a call', () => { it('When leaving a call for authenticated user, then it should leave successfully', async () => { callUseCase.leaveCall.mockResolvedValue(); + const leaveCallDto = new LeaveCallDto(); + leaveCallDto.userId = mockUserPayload.uuid; - const result = await callController.leaveCall( - mockRoomId, - mockUserPayload, - ); + const result = await callController.leaveCall(mockRoomId, leaveCallDto); expect(result).toBeUndefined(); expect(callUseCase.leaveCall).toHaveBeenCalledWith( @@ -336,7 +335,7 @@ describe('Testing Call Endpoints', () => { callUseCase.leaveCall.mockResolvedValue(); - await callController.leaveCall(mockRoomId, null, leaveCallDto); + await callController.leaveCall(mockRoomId, leaveCallDto); expect(callUseCase.leaveCall).toHaveBeenCalledWith( mockRoomId, @@ -350,16 +349,11 @@ describe('Testing Call Endpoints', () => { callUseCase.leaveCall.mockResolvedValue(); - const userToken = createMockUserToken(); - await callController.leaveCall( - mockRoomId, - userToken.payload, - leaveCallDto, - ); + await callController.leaveCall(mockRoomId, leaveCallDto); expect(callUseCase.leaveCall).toHaveBeenCalledWith( mockRoomId, - userToken.payload.uuid, + 'anonymous-user-id', ); }); @@ -367,20 +361,22 @@ describe('Testing Call Endpoints', () => { const emptyDto = new LeaveCallDto(); callUseCase.leaveCall.mockResolvedValue(); - await callController.leaveCall(mockRoomId, null, emptyDto); - - expect(callUseCase.leaveCall).toHaveBeenCalledWith(mockRoomId, undefined); + await expect( + callController.leaveCall(mockRoomId, emptyDto), + ).rejects.toThrow(BadRequestException); }); it('When room is not found, then it should propagate NotFoundException', async () => { - const userToken = createMockUserToken(); + const anonymousUserId = 'anonymous-user-id'; + const leaveCallDto = new LeaveCallDto(); + leaveCallDto.userId = anonymousUserId; callUseCase.leaveCall.mockRejectedValue( new NotFoundException('Specified room not found'), ); await expect( - callController.leaveCall(mockRoomId, userToken.payload), + callController.leaveCall(mockRoomId, leaveCallDto), ).rejects.toThrow(NotFoundException); }); diff --git a/src/modules/call/call.controller.ts b/src/modules/call/call.controller.ts index a967c2c..7773978 100644 --- a/src/modules/call/call.controller.ts +++ b/src/modules/call/call.controller.ts @@ -173,14 +173,12 @@ export class CallController { }) @ApiNotFoundResponse({ description: 'Call/Room not found' }) @ApiInternalServerErrorResponse({ description: 'Internal server error' }) - leaveCall( + async leaveCall( @Param('id') roomId: string, @Body() leaveCallDto?: LeaveCallDto, ): Promise { - if (leaveCallDto?.userId) { - this.logger.warn( - `Attempt to leave call without user ID: ${leaveCallDto}`, - ); + if (!leaveCallDto?.userId) { + this.logger.warn('Attempt to leave call without user ID:', leaveCallDto); throw new BadRequestException('The user id is needed to leave the call'); } return this.callUseCase.leaveCall(roomId, leaveCallDto?.userId); From 1193e2a8c3e9d0d7444247ef0d73e928d5f4daab Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 20 May 2026 15:48:06 +0200 Subject: [PATCH 5/6] add test for dev config --- src/modules/call/services/call.service.spec.ts | 17 +++++++++++++++++ src/modules/call/services/call.service.ts | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/modules/call/services/call.service.spec.ts b/src/modules/call/services/call.service.spec.ts index fcbec6d..6e48b0f 100644 --- a/src/modules/call/services/call.service.spec.ts +++ b/src/modules/call/services/call.service.spec.ts @@ -86,4 +86,21 @@ describe('Call service', () => { expect(paymentService.getUserTier).toHaveBeenCalledWith(userPayload.uuid); }); + + it('When the user has meet enabled and isDevelopment, then a call should be created without calling payment service', async () => { + jest.spyOn(configService, 'get').mockReturnValueOnce(false); + const userPayload = mockUserPayload; + (uuid.v4 as jest.Mock).mockReturnValue('test-room-id'); + (jwt.sign as jest.Mock).mockReturnValue('test-jitsi-token'); + + const result = await callService.createCall(userPayload); + + expect(result).toEqual({ + appId: 'jitsi-app-id', + room: 'test-room-id', + paxPerCall: 10, + }); + + expect(paymentService.getUserTier).not.toHaveBeenCalled(); + }); }); diff --git a/src/modules/call/services/call.service.ts b/src/modules/call/services/call.service.ts index c78e5a1..ce7c368 100644 --- a/src/modules/call/services/call.service.ts +++ b/src/modules/call/services/call.service.ts @@ -48,7 +48,7 @@ export class CallService { private async getMeetFeatureConfigForUser( userUuid: string, ): Promise { - const isProduction = this.configService.get('isProduction') + const isProduction = this.configService.get('isProduction'); if (!isProduction) { return { enabled: true, From 5dcdfa25846bc6ea46e1f5ce1067e2bc31e66d65 Mon Sep 17 00:00:00 2001 From: tamarafinogina Date: Wed, 20 May 2026 15:52:09 +0200 Subject: [PATCH 6/6] remove ? after the check --- src/modules/call/call.controller.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modules/call/call.controller.ts b/src/modules/call/call.controller.ts index 7773978..32c77e6 100644 --- a/src/modules/call/call.controller.ts +++ b/src/modules/call/call.controller.ts @@ -181,6 +181,6 @@ export class CallController { this.logger.warn('Attempt to leave call without user ID:', leaveCallDto); throw new BadRequestException('The user id is needed to leave the call'); } - return this.callUseCase.leaveCall(roomId, leaveCallDto?.userId); + return this.callUseCase.leaveCall(roomId, leaveCallDto.userId); } }