From a0b7d4200282d07267252ace2b03c7beeb7896f4 Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Thu, 10 Apr 2025 13:12:26 +0200 Subject: [PATCH] backend: Enhance recording service to support field filtering in getRecording and getAllRecordings methods --- .../src/controllers/recording.controller.ts | 4 +- backend/src/helpers/utils.helper.ts | 45 ++++++++++++------- .../recording-validator.middleware.ts | 5 ++- backend/src/routes/recording.routes.ts | 4 +- backend/src/services/recording.service.ts | 11 +++-- backend/src/services/room.service.ts | 22 ++------- 6 files changed, 49 insertions(+), 42 deletions(-) diff --git a/backend/src/controllers/recording.controller.ts b/backend/src/controllers/recording.controller.ts index 800b3cf..81f2726 100644 --- a/backend/src/controllers/recording.controller.ts +++ b/backend/src/controllers/recording.controller.ts @@ -70,10 +70,12 @@ export const getRecording = async (req: Request, res: Response) => { const logger = container.get(LoggerService); const recordingService = container.get(RecordingService); const recordingId = req.params.recordingId; + const fields = req.query.fields as string | undefined; + logger.info(`Getting recording ${recordingId}`); try { - const recordingInfo = await recordingService.getRecording(recordingId); + const recordingInfo = await recordingService.getRecording(recordingId, fields); return res.status(200).json(recordingInfo); } catch (error) { if (error instanceof OpenViduMeetError) { diff --git a/backend/src/helpers/utils.helper.ts b/backend/src/helpers/utils.helper.ts index 9211648..0147c51 100644 --- a/backend/src/helpers/utils.helper.ts +++ b/backend/src/helpers/utils.helper.ts @@ -1,18 +1,33 @@ +import { MeetRecordingInfo, MeetRoom } from '@typings-ce'; + export class UtilsHelper { - private constructor() { - // Prevent instantiation of this utility class + // Prevent instantiation of this utility class. + private constructor() {} + + /** + * Filters the fields of an object based on a list of keys. + * + * @param obj - The object to filter (it can be a MeetRoom or MeetRecordingInfo). + * @param fields - A comma-separated string or an array of field names to keep. + * @returns A new object containing only the specified keys. + */ + static filterObjectFields(obj: T, fields?: string | string[]): Partial { + // If no fields are provided, return the full object. + if (!fields || (typeof fields === 'string' && fields.trim().length === 0)) { + return obj; + } + + // Convert the string to an array if necessary. + const fieldsArray = Array.isArray(fields) ? fields : fields.split(',').map((f) => f.trim()); + + // Reduce the object by only including the specified keys. + return fieldsArray.reduce((acc, field) => { + if (Object.prototype.hasOwnProperty.call(obj, field)) { + // Use keyof T to properly type the field access + acc[field as keyof T] = obj[field as keyof T]; + } + + return acc; + }, {} as Partial); } - - static filterObjectFields = (obj: Record, fields: string[]): Record => { - return fields.reduce( - (acc, field) => { - if (Object.prototype.hasOwnProperty.call(obj, field)) { - acc[field] = obj[field]; - } - - return acc; - }, - {} as Record - ); - }; } diff --git a/backend/src/middlewares/request-validators/recording-validator.middleware.ts b/backend/src/middlewares/request-validators/recording-validator.middleware.ts index 3996c2b..473502b 100644 --- a/backend/src/middlewares/request-validators/recording-validator.middleware.ts +++ b/backend/src/middlewares/request-validators/recording-validator.middleware.ts @@ -52,7 +52,8 @@ const GetRecordingsFiltersSchema: z.ZodType = z.object({ .default(10), // status: z.string().optional(), roomId: z.string().optional(), - nextPageToken: z.string().optional() + nextPageToken: z.string().optional(), + fields: z.string().optional(), }); export const withValidStartRecordingRequest = (req: Request, res: Response, next: NextFunction) => { @@ -77,7 +78,7 @@ export const withValidRecordingId = (req: Request, res: Response, next: NextFunc next(); }; -export const withValidGetRecordingsRequest = (req: Request, res: Response, next: NextFunction) => { +export const withValidRecordingFiltersRequest = (req: Request, res: Response, next: NextFunction) => { const { success, error, data } = GetRecordingsFiltersSchema.safeParse(req.query); if (!success) { diff --git a/backend/src/routes/recording.routes.ts b/backend/src/routes/recording.routes.ts index ff32850..0e7d227 100644 --- a/backend/src/routes/recording.routes.ts +++ b/backend/src/routes/recording.routes.ts @@ -8,7 +8,7 @@ import { tokenAndRoleValidator, withRecordingEnabled, withCorrectPermissions, - withValidGetRecordingsRequest, + withValidRecordingFiltersRequest, withValidRecordingBulkDeleteRequest, withValidRecordingId, withValidStartRecordingRequest, @@ -35,7 +35,7 @@ recordingRouter.get( recordingRouter.get( '/', withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)), - withValidGetRecordingsRequest, + withValidRecordingFiltersRequest, recordingCtrl.getRecordings ); recordingRouter.delete( diff --git a/backend/src/services/recording.service.ts b/backend/src/services/recording.service.ts index 8eff057..94f6bb2 100644 --- a/backend/src/services/recording.service.ts +++ b/backend/src/services/recording.service.ts @@ -37,6 +37,7 @@ import { MeetLock } from '../helpers/redis.helper.js'; import { IScheduledTask, TaskSchedulerService } from './task-scheduler.service.js'; import { SystemEventService } from './system-event.service.js'; import { SystemEventType } from '../models/system-event.model.js'; +import { UtilsHelper } from '../helpers/utils.helper.js'; @injectable() export class RecordingService { @@ -228,10 +229,10 @@ export class RecordingService { * @param recordingId - The unique identifier of the recording. * @returns A promise that resolves to a MeetRecordingInfo object. */ - async getRecording(recordingId: string): Promise { + async getRecording(recordingId: string, fields?: string): Promise { const { recordingInfo } = await this.getMeetRecordingInfoFromMetadata(recordingId); - return recordingInfo; + return UtilsHelper.filterObjectFields(recordingInfo, fields) as MeetRecordingInfo; } /** @@ -245,7 +246,7 @@ export class RecordingService { * - `nextPageToken`: (Optional) A token to retrieve the next page of results, if available. * @throws Will throw an error if there is an issue retrieving the recordings. */ - async getAllRecordings({ maxItems, nextPageToken, roomId }: MeetRecordingFilters): Promise<{ + async getAllRecordings({ maxItems, nextPageToken, roomId, fields }: MeetRecordingFilters): Promise<{ recordings: MeetRecordingInfo[]; isTruncated: boolean; nextPageToken?: string; @@ -274,7 +275,9 @@ export class RecordingService { } }); - const recordings: MeetRecordingInfo[] = await Promise.all(promises); + let recordings: MeetRecordingInfo[] = await Promise.all(promises); + + recordings = recordings.map((rec) => UtilsHelper.filterObjectFields(rec, fields)) as MeetRecordingInfo[]; this.logger.info(`Retrieved ${recordings.length} recordings.`); // Return the paginated list of recordings diff --git a/backend/src/services/room.service.ts b/backend/src/services/room.service.ts index 9644d22..c58af66 100644 --- a/backend/src/services/room.service.ts +++ b/backend/src/services/room.service.ts @@ -89,7 +89,7 @@ export class RoomService { metadata: JSON.stringify({ createdBy: MEET_NAME_ID, roomOptions: MeetRoomHelper.toOpenViduOptions(meetRoom) - }), + }) //TODO: Uncomment this when bug in LiveKit is fixed // When it is defined, the room will be closed although there are participants // emptyTimeout: ms('20s') / 1000, @@ -129,13 +129,8 @@ export class RoomService { }> { const response = await this.storageService.getMeetRooms(maxItems, nextPageToken); - if (fields && fields.length > 0) { - const fieldsArray = Array.isArray(fields) ? fields : fields.split(',').map((f) => f.trim()); - const filteredRooms = response.rooms.map((room) => - UtilsHelper.filterObjectFields(room as unknown as Record, fieldsArray) - ); - response.rooms = filteredRooms as MeetRoom[]; - } + const filteredRooms = response.rooms.map((room) => UtilsHelper.filterObjectFields(room, fields)); + response.rooms = filteredRooms as MeetRoom[]; return response; } @@ -149,16 +144,7 @@ export class RoomService { async getMeetRoom(roomId: string, fields?: string): Promise { const meetRoom = await this.storageService.getMeetRoom(roomId); - if (fields && fields.length > 0) { - const fieldsArray = Array.isArray(fields) ? fields : fields.split(',').map((f) => f.trim()); - const filteredRoom = UtilsHelper.filterObjectFields( - meetRoom as unknown as Record, - fieldsArray - ); - return filteredRoom as MeetRoom; - } - - return meetRoom; + return UtilsHelper.filterObjectFields(meetRoom, fields) as MeetRoom; } /**