From 80ce1a3efdb6ef73fc50bbe23bcbf0dd7eac78e9 Mon Sep 17 00:00:00 2001 From: CSantosM <4a.santos@gmail.com> Date: Fri, 6 Feb 2026 16:47:07 +0100 Subject: [PATCH] backend: Improves data fetching efficiency Refactors repository methods to accept an array of fields instead of a comma-separated string, optimizing data retrieval and reducing unnecessary string manipulation. Also, modifies services and validators to use array of fields instead of strings. --- .../backend/src/middlewares/auth.middleware.ts | 2 +- .../backend/src/repositories/base.repository.ts | 4 ++++ .../src/repositories/recording.repository.ts | 12 ++++++++---- .../backend/src/repositories/room.repository.ts | 12 ++++++++---- meet-ce/backend/src/services/recording.service.ts | 5 +++-- .../backend/src/services/room-member.service.ts | 15 +++++++-------- 6 files changed, 31 insertions(+), 19 deletions(-) diff --git a/meet-ce/backend/src/middlewares/auth.middleware.ts b/meet-ce/backend/src/middlewares/auth.middleware.ts index b286ff0f..399ec979 100644 --- a/meet-ce/backend/src/middlewares/auth.middleware.ts +++ b/meet-ce/backend/src/middlewares/auth.middleware.ts @@ -214,7 +214,7 @@ export const roomMemberTokenValidator: AuthValidator = { } else { // If the token has no memberId (anonymous access), validate that room roles/anonymous haven't been updated const roomRepository = container.get(RoomRepository); - const room = await roomRepository.findByRoomId(roomId, 'rolesUpdatedAt'); + const room = await roomRepository.findByRoomId(roomId, ['rolesUpdatedAt']); // If room not found or roles/anonymous were updated after token issuance, invalidate token if (!room || iat < room.rolesUpdatedAt) { diff --git a/meet-ce/backend/src/repositories/base.repository.ts b/meet-ce/backend/src/repositories/base.repository.ts index d7cbc8af..33ea6e18 100644 --- a/meet-ce/backend/src/repositories/base.repository.ts +++ b/meet-ce/backend/src/repositories/base.repository.ts @@ -38,6 +38,8 @@ export abstract class BaseRepository { let query = this.model.findOne(filter); if (fields) { + //!FIXME: This transform should be optimized to avoid unnecessary string manipulation + const fieldSelection = fields .split(',') .map((field) => field.trim()) @@ -127,6 +129,8 @@ export abstract class BaseRepository { // Apply field selection if specified if (fields) { + // !FIXME: This transform should be optimized to avoid unnecessary string manipulation. + // !The argument method should ideally accept an array of fields instead of a comma-separated string to avoid this overhead. // Convert comma-separated string to space-separated format for MongoDB select() const fieldSelection = fields .split(',') diff --git a/meet-ce/backend/src/repositories/recording.repository.ts b/meet-ce/backend/src/repositories/recording.repository.ts index b28c9863..ced77a65 100644 --- a/meet-ce/backend/src/repositories/recording.repository.ts +++ b/meet-ce/backend/src/repositories/recording.repository.ts @@ -1,7 +1,8 @@ -import { MeetRecordingFilters, MeetRecordingInfo, MeetRecordingStatus } from '@openvidu-meet/typings'; +import { MeetRecordingInfo, MeetRecordingStatus } from '@openvidu-meet/typings'; import { inject, injectable } from 'inversify'; import { uid as secureUid } from 'uid/secure'; import { MeetRecordingDocument, MeetRecordingModel } from '../models/mongoose-schemas/recording.schema.js'; +import { MeetRecordingField, MeetRecordingFilters } from '../models/recording-request.js'; import { LoggerService } from '../services/logger.service.js'; import { BaseRepository } from './base.repository.js'; @@ -76,8 +77,10 @@ export class RecordingRepository { - const document = await this.findOne({ recordingId }, fields); + async findByRecordingId(recordingId: string, fields?: MeetRecordingField[]): Promise { + //!FIXME: This transform should be removed because the findOne method should accept an array of fields instead of a comma-separated string, to avoid unnecessary string manipulation + const fieldsString = fields ? fields.join(',') : undefined; + const document = await this.findOne({ recordingId }, fieldsString); return document ? this.toDomain(document) : null; } @@ -149,7 +152,8 @@ export class RecordingRepository extends BaseRepos * @param fields - Comma-separated list of fields to include in the result * @returns The room or null if not found */ - async findByRoomId(roomId: string, fields?: string): Promise { - const document = await this.findOne({ roomId }, fields); + async findByRoomId(roomId: string, fields?: MeetRoomField[]): Promise { + //!FIXME: This transform should be removed once the controller is updated to pass the fields as an array of MeetRoomField instead of a comma-separated string. + const fieldsString = fields ? fields.join(',') : undefined; + const document = await this.findOne({ roomId }, fieldsString); return document ? this.enrichRoomWithBaseUrls(document) : null; } @@ -146,7 +149,8 @@ export class RoomRepository extends BaseRepos sortField, sortOrder }, - fields + //! FIXME: This transform should be removed because the findMany method should accept an array of fields instead of a comma-separated string, to avoid unnecessary string manipulation + fields?.join(',') ); return { diff --git a/meet-ce/backend/src/services/recording.service.ts b/meet-ce/backend/src/services/recording.service.ts index 9b313fee..4feec604 100644 --- a/meet-ce/backend/src/services/recording.service.ts +++ b/meet-ce/backend/src/services/recording.service.ts @@ -1,7 +1,6 @@ import { MeetRecordingEncodingOptions, MeetRecordingEncodingPreset, - MeetRecordingFilters, MeetRecordingInfo, MeetRecordingLayout, MeetRecordingStatus, @@ -35,6 +34,7 @@ import { isErrorRecordingNotFound, OpenViduMeetError } from '../models/error.model.js'; +import { MeetRecordingField, MeetRecordingFilters } from '../models/recording-request.js'; import { RecordingRepository } from '../repositories/recording.repository.js'; import { DistributedEventService } from './distributed-event.service.js'; import { FrontendEventService } from './frontend-event.service.js'; @@ -453,9 +453,10 @@ export class RecordingService { /** * Retrieves the recording information for a given recording ID. * @param recordingId - The unique identifier of the recording. + * @param fields - Array of {@link MeetRecordingField} to include in the response * @returns A promise that resolves to a MeetRecordingInfo object. */ - async getRecording(recordingId: string, fields?: string): Promise { + async getRecording(recordingId: string, fields?: MeetRecordingField[]): Promise { const recordingInfo = await this.recordingRepository.findByRecordingId(recordingId, fields); if (!recordingInfo) { diff --git a/meet-ce/backend/src/services/room-member.service.ts b/meet-ce/backend/src/services/room-member.service.ts index 91a35c90..44d5032b 100644 --- a/meet-ce/backend/src/services/room-member.service.ts +++ b/meet-ce/backend/src/services/room-member.service.ts @@ -109,7 +109,7 @@ export class RoomMemberService { } // Compute effective permissions - const room = await this.roomService.getMeetRoom(roomId); + const room = await this.roomService.getMeetRoom(roomId, { fields: ['roles'] }); const effectivePermissions = this.computeEffectivePermissions(room.roles, baseRole, customPermissions); const now = Date.now(); @@ -154,7 +154,7 @@ export class RoomMemberService { */ async isRoomMember(roomId: string, memberId: string): Promise { // Verify room exists first - await this.roomService.getMeetRoom(roomId); + await this.roomService.getMeetRoom(roomId, { fields: ['roomId'] }); const member = await this.roomMemberRepository.findByRoomAndMemberId(roomId, memberId); return !!member; } @@ -219,7 +219,7 @@ export class RoomMemberService { } // Recompute effective permissions - const room = await this.roomService.getMeetRoom(roomId); + const room = await this.roomService.getMeetRoom(roomId, { fields: ['roles'] }); member.effectivePermissions = this.computeEffectivePermissions( room.roles, member.baseRole, @@ -420,7 +420,7 @@ 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); + const room = await this.roomService.getMeetRoom(roomId, { fields: ['roles', 'anonymous'] }); // Check that anonymous access is enabled for the role if (!room.anonymous[baseRole].enabled) { @@ -492,7 +492,7 @@ export class RoomMemberService { userId?: string ): Promise { // Check that room is open - const room = await this.roomService.getMeetRoom(roomId); + const room = await this.roomService.getMeetRoom(roomId, { fields: ['status', 'config'] }); if (room.status === MeetRoomStatus.CLOSED) { throw errorRoomClosed(roomId); @@ -611,7 +611,7 @@ 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); + const room = await this.roomService.getMeetRoom(roomId, { fields: ['roomId', 'anonymous'] }); const { moderatorSecret, speakerSecret } = MeetRoomHelper.extractSecretsFromRoom(room); switch (secret) { @@ -780,8 +780,7 @@ export class RoomMemberService { newRole: MeetRoomMemberRole ): Promise { try { - const meetRoom = await this.roomService.getMeetRoom(roomId); - + const meetRoom = await this.roomService.getMeetRoom(roomId, { fields: ['roles', 'anonymous'] }); const participant = await this.getParticipantFromMeeting(roomId, participantIdentity); const metadata: MeetRoomMemberTokenMetadata = this.tokenService.parseRoomMemberTokenMetadata( participant.metadata