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.
This commit is contained in:
CSantosM 2026-02-06 16:47:07 +01:00
parent d4a87f8a45
commit 80ce1a3efd
6 changed files with 31 additions and 19 deletions

View File

@ -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) {

View File

@ -38,6 +38,8 @@ export abstract class BaseRepository<TDomain, TDocument extends Document> {
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<TDomain, TDocument extends Document> {
// 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(',')

View File

@ -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<TRecording extends MeetRecordingInfo = MeetReco
* @param fields - Comma-separated list of fields to include in the result
* @returns The recording (without access secrets), or null if not found
*/
async findByRecordingId(recordingId: string, fields?: string): Promise<TRecording | null> {
const document = await this.findOne({ recordingId }, fields);
async findByRecordingId(recordingId: string, fields?: MeetRecordingField[]): Promise<TRecording | null> {
//!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<TRecording extends MeetRecordingInfo = MeetReco
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 {

View File

@ -1,6 +1,7 @@
import { MeetRoom, MeetRoomFilters, MeetRoomStatus } from '@openvidu-meet/typings';
import { MeetRoom, MeetRoomStatus } from '@openvidu-meet/typings';
import { inject, injectable } from 'inversify';
import { MeetRoomDocument, MeetRoomModel } from '../models/mongoose-schemas/room.schema.js';
import { MeetRoomField, MeetRoomFilters } from '../models/room-request.js';
import { LoggerService } from '../services/logger.service.js';
import { getBasePath } from '../utils/html-injection.utils.js';
import { getBaseUrl } from '../utils/url.utils.js';
@ -64,8 +65,10 @@ export class RoomRepository<TRoom extends MeetRoom = MeetRoom> 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<TRoom | null> {
const document = await this.findOne({ roomId }, fields);
async findByRoomId(roomId: string, fields?: MeetRoomField[]): Promise<TRoom | null> {
//!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<TRoom extends MeetRoom = MeetRoom> 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 {

View File

@ -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<MeetRecordingInfo> {
async getRecording(recordingId: string, fields?: MeetRecordingField[]): Promise<MeetRecordingInfo> {
const recordingInfo = await this.recordingRepository.findByRecordingId(recordingId, fields);
if (!recordingInfo) {

View File

@ -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<boolean> {
// 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<string> {
// 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<MeetRoomMemberRole> {
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<void> {
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