diff --git a/backend/src/middlewares/request-validators/room-validator.middleware.ts b/backend/src/middlewares/request-validators/room-validator.middleware.ts index cdfceb4..30e9453 100644 --- a/backend/src/middlewares/request-validators/room-validator.middleware.ts +++ b/backend/src/middlewares/request-validators/room-validator.middleware.ts @@ -2,6 +2,9 @@ import { MeetChatPreferences, MeetRecordingAccess, MeetRecordingPreferences, + MeetRoomAutoDeletionPolicy, + MeetRoomDeletionPolicyWithMeeting, + MeetRoomDeletionPolicyWithRecordings, MeetRoomFilters, MeetRoomOptions, MeetRoomPreferences, @@ -55,17 +58,6 @@ export const nonEmptySanitizedRoomId = (fieldName: string) => message: `${fieldName} cannot be empty after sanitization` }); -const validForceQueryParam = () => - z - .preprocess((val) => { - if (typeof val === 'string') { - return val.toLowerCase() === 'true'; - } - - return val; - }, z.boolean()) - .default(false); - const RecordingAccessSchema: z.ZodType = z.enum([ MeetRecordingAccess.ADMIN, MeetRecordingAccess.ADMIN_MODERATOR, @@ -102,6 +94,23 @@ const RoomPreferencesSchema: z.ZodType = z.object({ virtualBackgroundPreferences: VirtualBackgroundPreferencesSchema }); +const RoomDeletionPolicyWithMeetingSchema: z.ZodType = z.enum([ + MeetRoomDeletionPolicyWithMeeting.FORCE, + MeetRoomDeletionPolicyWithMeeting.WHEN_MEETING_ENDS, + MeetRoomDeletionPolicyWithMeeting.FAIL +]); + +const RoomDeletionPolicyWithRecordingsSchema: z.ZodType = z.enum([ + MeetRoomDeletionPolicyWithRecordings.FORCE, + MeetRoomDeletionPolicyWithRecordings.CLOSE, + MeetRoomDeletionPolicyWithRecordings.FAIL +]); + +const RoomAutoDeletionPolicySchema: z.ZodType = z.object({ + withMeeting: RoomDeletionPolicyWithMeetingSchema, + withRecordings: RoomDeletionPolicyWithRecordingsSchema +}); + const RoomRequestOptionsSchema: z.ZodType = z.object({ roomName: z .string() @@ -117,6 +126,10 @@ const RoomRequestOptionsSchema: z.ZodType = z.object({ `autoDeletionDate must be at least ${INTERNAL_CONFIG.MIN_FUTURE_TIME_FOR_ROOM_AUTODELETION_DATE} in the future` ) .optional(), + autoDeletionPolicy: RoomAutoDeletionPolicySchema.optional().default({ + withMeeting: MeetRoomDeletionPolicyWithMeeting.WHEN_MEETING_ENDS, + withRecordings: MeetRoomDeletionPolicyWithRecordings.CLOSE + }), preferences: RoomPreferencesSchema.optional().default({ recordingPreferences: { enabled: true, allowAccessTo: MeetRecordingAccess.ADMIN_MODERATOR_SPEAKER }, chatPreferences: { enabled: true }, @@ -146,6 +159,11 @@ const GetRoomFiltersSchema: z.ZodType = z.object({ fields: z.string().optional() }); +const DeleteRoomQueryParamsSchema = z.object({ + withMeeting: RoomDeletionPolicyWithMeetingSchema.optional().default(MeetRoomDeletionPolicyWithMeeting.FAIL), + withRecordings: RoomDeletionPolicyWithRecordingsSchema.optional().default(MeetRoomDeletionPolicyWithRecordings.FAIL) +}); + const BulkDeleteRoomsSchema = z.object({ roomIds: z.preprocess( (arg) => { @@ -181,7 +199,8 @@ const BulkDeleteRoomsSchema = z.object({ message: 'At least one valid roomId is required after sanitization' }) ), - force: validForceQueryParam() + withMeeting: RoomDeletionPolicyWithMeetingSchema.optional().default(MeetRoomDeletionPolicyWithMeeting.FAIL), + withRecordings: RoomDeletionPolicyWithRecordingsSchema.optional().default(MeetRoomDeletionPolicyWithRecordings.FAIL) }); const UpdateRoomPreferencesSchema = z.object({ @@ -250,18 +269,6 @@ export const withValidRoomId = (req: Request, res: Response, next: NextFunction) next(); }; -export const withValidRoomBulkDeleteRequest = (req: Request, res: Response, next: NextFunction) => { - const { success, error, data } = BulkDeleteRoomsSchema.safeParse(req.query); - - if (!success) { - return rejectUnprocessableRequest(res, error); - } - - req.query.roomIds = data.roomIds as any; - req.query.force = data.force ? 'true' : 'false'; - next(); -}; - export const withValidRoomDeleteRequest = (req: Request, res: Response, next: NextFunction) => { const roomIdResult = nonEmptySanitizedRoomId('roomId').safeParse(req.params.roomId); @@ -271,13 +278,24 @@ export const withValidRoomDeleteRequest = (req: Request, res: Response, next: Ne req.params.roomId = roomIdResult.data; - const forceResult = validForceQueryParam().safeParse(req.query.force); + const queryParamsResult = DeleteRoomQueryParamsSchema.safeParse(req.query); - if (!forceResult.success) { - return rejectUnprocessableRequest(res, forceResult.error); + if (!queryParamsResult.success) { + return rejectUnprocessableRequest(res, queryParamsResult.error); } - req.query.force = forceResult.data ? 'true' : 'false'; + req.query = queryParamsResult.data; + next(); +}; + +export const withValidRoomBulkDeleteRequest = (req: Request, res: Response, next: NextFunction) => { + const { success, error, data } = BulkDeleteRoomsSchema.safeParse(req.query); + + if (!success) { + return rejectUnprocessableRequest(res, error); + } + + req.query = data; next(); }; diff --git a/backend/src/services/room.service.ts b/backend/src/services/room.service.ts index 31afe42..3280aaa 100644 --- a/backend/src/services/room.service.ts +++ b/backend/src/services/room.service.ts @@ -1,9 +1,11 @@ import { + MeetingEndAction, MeetRecordingAccess, MeetRoom, MeetRoomFilters, MeetRoomOptions, MeetRoomPreferences, + MeetRoomStatus, ParticipantRole, RecordingPermissions } from '@typings-ce'; @@ -15,6 +17,7 @@ import { uid } from 'uid/single'; import INTERNAL_CONFIG from '../config/internal-config.js'; import { MEET_NAME_ID } from '../environment.js'; import { MeetRoomHelper, UtilsHelper } from '../helpers/index.js'; +import { validateRecordingTokenMetadata } from '../middlewares/index.js'; import { errorInvalidRoomSecret, errorRoomMetadataNotFound, @@ -23,15 +26,14 @@ import { } from '../models/error.model.js'; import { DistributedEventService, + FrontendEventService, IScheduledTask, LiveKitService, LoggerService, MeetStorageService, TaskSchedulerService, - TokenService, - FrontendEventService + TokenService } from './index.js'; -import { validateRecordingTokenMetadata } from '../middlewares/index.js'; /** * Service for managing OpenVidu Meet rooms. @@ -70,7 +72,7 @@ export class RoomService { * */ async createMeetRoom(baseUrl: string, roomOptions: MeetRoomOptions): Promise { - const { roomName, autoDeletionDate, preferences } = roomOptions; + const { roomName, autoDeletionDate, autoDeletionPolicy, preferences } = roomOptions; const roomIdPrefix = roomName!.replace(/\s+/g, ''); // Remove all spaces const roomId = `${roomIdPrefix}-${uid(15)}`; // Generate a unique room ID based on the room name @@ -80,13 +82,15 @@ export class RoomService { creationDate: Date.now(), // maxParticipants, autoDeletionDate, - preferences, + autoDeletionPolicy, + preferences: preferences!, moderatorUrl: `${baseUrl}/room/${roomId}?secret=${secureUid(10)}`, - speakerUrl: `${baseUrl}/room/${roomId}?secret=${secureUid(10)}` + speakerUrl: `${baseUrl}/room/${roomId}?secret=${secureUid(10)}`, + status: MeetRoomStatus.OPEN, + meetingEndAction: MeetingEndAction.NONE }; await this.storageService.saveMeetRoom(meetRoom); - return meetRoom; }