From 2572fd3960e64f55fb38ff39079e371ad28ba89f Mon Sep 17 00:00:00 2001 From: juancarmore Date: Thu, 26 Feb 2026 18:37:48 +0100 Subject: [PATCH] backend: update room retrieval to use selective fields for efficiency --- .../src/controllers/meeting.controller.ts | 4 +-- .../backend/src/helpers/recording.helper.ts | 2 +- meet-ce/backend/src/helpers/room.helper.ts | 11 ++++--- .../src/middlewares/recording.middleware.ts | 4 +-- .../backend/src/services/recording.service.ts | 22 ++++++------- .../src/services/room-member.service.ts | 32 +++++++++---------- meet-ce/backend/src/services/room.service.ts | 14 ++++---- .../backend/tests/helpers/test-scenarios.ts | 2 +- 8 files changed, 45 insertions(+), 46 deletions(-) diff --git a/meet-ce/backend/src/controllers/meeting.controller.ts b/meet-ce/backend/src/controllers/meeting.controller.ts index 1690b78c..3c200483 100644 --- a/meet-ce/backend/src/controllers/meeting.controller.ts +++ b/meet-ce/backend/src/controllers/meeting.controller.ts @@ -15,7 +15,7 @@ export const endMeeting = async (req: Request, res: Response) => { // Check if the room exists try { - await roomService.getMeetRoom(roomId); + await roomService.getMeetRoom(roomId, ['roomId']); } catch (error) { return handleError(res, error, `getting room '${roomId}'`); } @@ -53,7 +53,7 @@ export const kickParticipantFromMeeting = async (req: Request, res: Response) => // Check if the room exists try { - await roomService.getMeetRoom(roomId); + await roomService.getMeetRoom(roomId, ['roomId']); } catch (error) { return handleError(res, error, `getting room '${roomId}'`); } diff --git a/meet-ce/backend/src/helpers/recording.helper.ts b/meet-ce/backend/src/helpers/recording.helper.ts index fa91a720..f387dcb5 100644 --- a/meet-ce/backend/src/helpers/recording.helper.ts +++ b/meet-ce/backend/src/helpers/recording.helper.ts @@ -45,7 +45,7 @@ export class RecordingHelper { const layout = RecordingHelper.extractRecordingLayout(egressInfo); const encoding = RecordingHelper.extractRecordingEncoding(egressInfo); const roomService = container.get(RoomService); - const { roomName } = await roomService.getMeetRoom(roomId); + const { roomName } = await roomService.getMeetRoom(roomId, ['roomName']); return { recordingId, diff --git a/meet-ce/backend/src/helpers/room.helper.ts b/meet-ce/backend/src/helpers/room.helper.ts index 4747016b..b4a52e4f 100644 --- a/meet-ce/backend/src/helpers/room.helper.ts +++ b/meet-ce/backend/src/helpers/room.helper.ts @@ -2,6 +2,7 @@ import { MEET_ROOM_EXTRA_FIELDS, MEET_ROOM_FIELDS, MeetRoom, + MeetRoomAnonymous, MeetRoomExtraField, MeetRoomField, MeetRoomMemberPermissions, @@ -89,19 +90,19 @@ export class MeetRoomHelper { } /** - * Extracts speaker and moderator secrets from a MeetRoom object's URLs. + * Extracts speaker and moderator secrets from MeetRoom anonymous access URLs. * * This method parses the 'secret' query parameter from both speaker and moderator * anonymous access URLs associated with the meeting room. * - * @param room - The MeetRoom object containing speakerUrl and moderatorUrl properties + * @param room - The anonymous access configuration of the MeetRoom from which to extract secrets. * @returns An object containing the extracted secrets with the following properties: * - speakerSecret: The secret extracted from the speaker anonymous access URL * - moderatorSecret: The secret extracted from the moderator anonymous access URL */ - static extractSecretsFromRoom(room: MeetRoom): { speakerSecret: string; moderatorSecret: string } { - const speakerUrl = room.anonymous.speaker.accessUrl; - const moderatorUrl = room.anonymous.moderator.accessUrl; + static extractSecretsFromRoom(anonymous: MeetRoomAnonymous): { speakerSecret: string; moderatorSecret: string } { + const speakerUrl = anonymous.speaker.accessUrl; + const moderatorUrl = anonymous.moderator.accessUrl; const parsedSpeakerUrl = new URL(speakerUrl); const speakerSecret = parsedSpeakerUrl.searchParams.get('secret') || ''; diff --git a/meet-ce/backend/src/middlewares/recording.middleware.ts b/meet-ce/backend/src/middlewares/recording.middleware.ts index 1144ec08..db8e1822 100644 --- a/meet-ce/backend/src/middlewares/recording.middleware.ts +++ b/meet-ce/backend/src/middlewares/recording.middleware.ts @@ -28,9 +28,9 @@ export const withRecordingEnabled = async (req: Request, res: Response, next: Ne try { const { roomId } = req.body as { roomId: string }; - const room = await roomService.getMeetRoom(roomId!); + const { config } = await roomService.getMeetRoom(roomId!, ['config']); - if (!room.config.recording.enabled) { + if (!config.recording.enabled) { logger.debug(`Recording is disabled for room '${roomId}'`); const error = errorRecordingDisabled(roomId!); return rejectRequestFromMeetError(res, error); diff --git a/meet-ce/backend/src/services/recording.service.ts b/meet-ce/backend/src/services/recording.service.ts index ff7e6516..878e988d 100644 --- a/meet-ce/backend/src/services/recording.service.ts +++ b/meet-ce/backend/src/services/recording.service.ts @@ -1,4 +1,5 @@ import { + MeetRecordingConfig, MeetRecordingEncodingOptions, MeetRecordingEncodingPreset, MeetRecordingField, @@ -6,8 +7,6 @@ import { MeetRecordingInfo, MeetRecordingLayout, MeetRecordingStatus, - MeetRoom, - MeetRoomConfig, MeetRoomMemberPermissions } from '@openvidu-meet/typings'; import { inject, injectable } from 'inversify'; @@ -86,7 +85,7 @@ export class RecordingService { if (!acquiredLock) throw errorRecordingAlreadyStarted(roomId); - const room = await this.validateRoomForStartRecording(roomId); + const roomRecordingConfig = await this.validateRoomForStartRecording(roomId); // Manually send the recording signal to OpenVidu Components for avoiding missing event if timeout occurs // and the egress_started webhook is not received. @@ -131,7 +130,7 @@ export class RecordingService { // Promise that starts the recording process const startRecordingPromise = (async (): Promise => { try { - const options = this.generateCompositeOptionsFromRequest(room.config, configOverride); + const options = this.generateCompositeOptionsFromRequest(roomRecordingConfig, configOverride); const output = this.generateFileOutputFromRequest(roomId); const egressInfo = await this.livekitService.startRoomComposite(roomId, output, options); @@ -592,18 +591,18 @@ export class RecordingService { * Validates that a room exists and has participants before starting a recording. * * @param roomId - * @returns The MeetRoom object if validation passes. + * @returns The MeetRecordingConfig object if validation passes. * @throws Will throw an error if the room does not exist or has no participants. */ - protected async validateRoomForStartRecording(roomId: string): Promise { + protected async validateRoomForStartRecording(roomId: string): Promise { const roomService = await this.getRoomService(); - const room = await roomService.getMeetRoom(roomId); + const { config } = await roomService.getMeetRoom(roomId, ['config']); const hasParticipants = await this.livekitService.roomHasParticipants(roomId); if (!hasParticipants) throw errorRoomHasNoParticipants(roomId); - return room; + return config.recording; } /** @@ -738,21 +737,20 @@ export class RecordingService { } /** - * Generates composite options for recording based on the provided room configuration. + * Generates composite options for recording based on the provided room recording configuration. * If configOverride is provided, its values will take precedence over room configuration. * - * @param roomConfig The room configuration + * @param roomRecordingConfig The recording configuration defined for the room * @param configOverride Optional configuration override from the request * @returns The generated RoomCompositeOptions object. */ protected generateCompositeOptionsFromRequest( - roomConfig: MeetRoomConfig, + roomRecordingConfig: MeetRecordingConfig, configOverride?: { layout?: MeetRecordingLayout; encoding?: MeetRecordingEncodingPreset | MeetRecordingEncodingOptions; } ): RoomCompositeOptions { - const roomRecordingConfig = roomConfig.recording; const layout = configOverride?.layout ?? roomRecordingConfig.layout; const encoding = configOverride?.encoding ?? roomRecordingConfig.encoding; const encodingOptions = EncodingConverter.toLivekit(encoding); diff --git a/meet-ce/backend/src/services/room-member.service.ts b/meet-ce/backend/src/services/room-member.service.ts index ebfa6485..1719bad2 100644 --- a/meet-ce/backend/src/services/room-member.service.ts +++ b/meet-ce/backend/src/services/room-member.service.ts @@ -109,8 +109,8 @@ export class RoomMemberService { } // Compute effective permissions - const room = await this.roomService.getMeetRoom(roomId, ['roles']); - const effectivePermissions = this.computeEffectivePermissions(room.roles, baseRole, customPermissions); + const { roles } = await this.roomService.getMeetRoom(roomId, ['roles']); + const effectivePermissions = this.computeEffectivePermissions(roles, baseRole, customPermissions); const now = Date.now(); const roomMember = { @@ -219,9 +219,9 @@ export class RoomMemberService { } // Recompute effective permissions - const room = await this.roomService.getMeetRoom(roomId, ['roles']); + const { roles } = await this.roomService.getMeetRoom(roomId, ['roles']); member.effectivePermissions = this.computeEffectivePermissions( - room.roles, + roles, member.baseRole, member.customPermissions ); @@ -419,14 +419,14 @@ export class RoomMemberService { } else { // If secret matches anonymous access URL secret, assign role and permissions based on it baseRole = await this.getRoomMemberRoleBySecret(roomId, secret); - const room = await this.roomService.getMeetRoom(roomId, ['roles', 'anonymous']); + const { roles, anonymous } = await this.roomService.getMeetRoom(roomId, ['roles', 'anonymous']); // Check that anonymous access is enabled for the role - if (!room.anonymous[baseRole].enabled) { + if (!anonymous[baseRole].enabled) { throw errorAnonymousAccessDisabled(roomId, baseRole); } - effectivePermissions = room.roles[baseRole].permissions; + effectivePermissions = roles[baseRole].permissions; } } else { // Case 2: Authenticated user @@ -491,9 +491,9 @@ export class RoomMemberService { userId?: string ): Promise { // Check that room is open - const room = await this.roomService.getMeetRoom(roomId, ['status', 'config']); + const { status, config } = await this.roomService.getMeetRoom(roomId, ['status', 'config']); - if (room.status === MeetRoomStatus.CLOSED) { + if (status === MeetRoomStatus.CLOSED) { throw errorRoomClosed(roomId); } @@ -557,7 +557,7 @@ export class RoomMemberService { customPermissions, effectivePermissions }; - const roomWithCaptions = room.config.captions.enabled; + const roomWithCaptions = config.captions.enabled; // Generate token with participant name return this.tokenService.generateRoomMemberToken({ @@ -610,8 +610,8 @@ export class RoomMemberService { * @throws Error if the provided secret doesn't match any of the room's secrets (unauthorized) */ protected async getRoomMemberRoleBySecret(roomId: string, secret: string): Promise { - const room = await this.roomService.getMeetRoom(roomId, ['roomId', 'anonymous']); - const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room); + const { anonymous } = await this.roomService.getMeetRoom(roomId, ['anonymous']); + const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(anonymous); switch (secret) { case moderatorSecret: @@ -619,7 +619,7 @@ export class RoomMemberService { case speakerSecret: return MeetRoomMemberRole.SPEAKER; default: - throw errorInvalidRoomSecret(room.roomId, secret); + throw errorInvalidRoomSecret(roomId, secret); } } @@ -779,7 +779,7 @@ export class RoomMemberService { newRole: MeetRoomMemberRole ): Promise { try { - const meetRoom = await this.roomService.getMeetRoom(roomId, ['roles', 'anonymous']); + const { roles, anonymous } = await this.roomService.getMeetRoom(roomId, ['roles', 'anonymous']); const participant = await this.getParticipantFromMeeting(roomId, participantIdentity); const metadata: MeetRoomMemberTokenMetadata = this.tokenService.parseRoomMemberTokenMetadata( participant.metadata @@ -788,11 +788,11 @@ export class RoomMemberService { // Update role and permissions in metadata metadata.baseRole = newRole; metadata.customPermissions = undefined; - metadata.effectivePermissions = meetRoom.roles[newRole].permissions; + metadata.effectivePermissions = roles[newRole].permissions; await this.livekitService.updateParticipantMetadata(roomId, participantIdentity, JSON.stringify(metadata)); - const { speakerSecret, moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(meetRoom); + const { speakerSecret, moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(anonymous); const secret = newRole === MeetRoomMemberRole.MODERATOR ? moderatorSecret : speakerSecret; await this.frontendEventService.sendParticipantRoleUpdatedSignal( roomId, diff --git a/meet-ce/backend/src/services/room.service.ts b/meet-ce/backend/src/services/room.service.ts index 0bef335e..b4f8d444 100644 --- a/meet-ce/backend/src/services/room.service.ts +++ b/meet-ce/backend/src/services/room.service.ts @@ -313,7 +313,7 @@ export class RoomService { * @returns A Promise that resolves to true if the room exists, false otherwise */ async meetRoomExists(roomId: string): Promise { - const meetRoom = await this.roomRepository.findByRoomId(roomId); + const meetRoom = await this.roomRepository.findByRoomId(roomId, ['roomId']); return !!meetRoom; } @@ -857,8 +857,8 @@ export class RoomService { * @throws Error if room not found */ async isRoomOwner(roomId: string, userId: string): Promise { - const room = await this.getMeetRoom(roomId, ['owner']); - return room.owner === userId; + const { owner } = await this.getMeetRoom(roomId, ['owner']); + return owner === userId; } /** @@ -870,8 +870,8 @@ export class RoomService { * @throws Error if room not found */ async isValidRoomSecret(roomId: string, secret: string): Promise { - const room = await this.getMeetRoom(roomId, ['anonymous']); - const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room); + const { anonymous } = await this.getMeetRoom(roomId, ['anonymous']); + const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(anonymous); return secret === moderatorSecret || secret === speakerSecret; } @@ -934,7 +934,7 @@ export class RoomService { */ async canUserAccessRoom(roomId: string, user: MeetUser): Promise { // Verify room exists first (throws 404 if not found) - const room = await this.getMeetRoom(roomId, ['owner']); + const { owner } = await this.getMeetRoom(roomId, ['owner']); if (user.role === MeetUserRole.ADMIN) { // Admins can access all rooms @@ -942,7 +942,7 @@ export class RoomService { } // Users can access rooms they own or are members of - const isOwner = room.owner === user.userId; + const isOwner = owner === user.userId; if (isOwner) { return true; diff --git a/meet-ce/backend/tests/helpers/test-scenarios.ts b/meet-ce/backend/tests/helpers/test-scenarios.ts index bb30d2a2..a16c1d44 100644 --- a/meet-ce/backend/tests/helpers/test-scenarios.ts +++ b/meet-ce/backend/tests/helpers/test-scenarios.ts @@ -51,7 +51,7 @@ export const setupSingleRoom = async ( }); // Extract the room secrets and generate room member tokens - const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room); + const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room.anonymous); const [moderatorToken, speakerToken] = await Promise.all([ generateRoomMemberToken(room.roomId, { secret: moderatorSecret, joinMeeting: false }), generateRoomMemberToken(room.roomId, { secret: speakerSecret, joinMeeting: false })