backend: enhance room deletion logic with new policies for meetings and recordings
This commit is contained in:
parent
632d36a470
commit
4802f48ba6
@ -61,10 +61,10 @@ export const registerDependencies = () => {
|
||||
|
||||
container.bind(FrontendEventService).toSelf().inSingletonScope();
|
||||
container.bind(LiveKitService).toSelf().inSingletonScope();
|
||||
container.bind(RecordingService).toSelf().inSingletonScope();
|
||||
container.bind(RoomService).toSelf().inSingletonScope();
|
||||
container.bind(ParticipantNameService).toSelf().inSingletonScope();
|
||||
container.bind(ParticipantService).toSelf().inSingletonScope();
|
||||
container.bind(RecordingService).toSelf().inSingletonScope();
|
||||
container.bind(OpenViduWebhookService).toSelf().inSingletonScope();
|
||||
container.bind(LivekitWebhookService).toSelf().inSingletonScope();
|
||||
};
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
import { MeetRoomFilters, MeetRoomOptions, MeetRoomRoleAndPermissions, ParticipantRole } from '@typings-ce';
|
||||
import {
|
||||
MeetRoomDeletionPolicyWithMeeting,
|
||||
MeetRoomDeletionPolicyWithRecordings,
|
||||
MeetRoomDeletionSuccessCode,
|
||||
MeetRoomFilters,
|
||||
MeetRoomOptions,
|
||||
MeetRoomRoleAndPermissions,
|
||||
ParticipantRole
|
||||
} from '@typings-ce';
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
@ -63,21 +71,26 @@ export const deleteRoom = async (req: Request, res: Response) => {
|
||||
const roomService = container.get(RoomService);
|
||||
|
||||
const { roomId } = req.params;
|
||||
const { force } = req.query;
|
||||
const forceDelete = force === 'true';
|
||||
const { withMeeting, withRecordings } = req.query as {
|
||||
withMeeting: MeetRoomDeletionPolicyWithMeeting;
|
||||
withRecordings: MeetRoomDeletionPolicyWithRecordings;
|
||||
};
|
||||
|
||||
try {
|
||||
logger.verbose(`Deleting room '${roomId}'`);
|
||||
const response = await roomService.deleteMeetRoom(roomId, withMeeting, withRecordings);
|
||||
|
||||
const { deleted } = await roomService.bulkDeleteRooms([roomId], forceDelete);
|
||||
// Determine the status code based on the success code
|
||||
// If the room action is scheduled, return 202. Otherwise, return 200.
|
||||
const scheduledSuccessCodes = [
|
||||
MeetRoomDeletionSuccessCode.ROOM_WITH_ACTIVE_MEETING_SCHEDULED_TO_BE_DELETED,
|
||||
MeetRoomDeletionSuccessCode.ROOM_WITH_ACTIVE_MEETING_SCHEDULED_TO_BE_CLOSED,
|
||||
MeetRoomDeletionSuccessCode.ROOM_WITH_ACTIVE_MEETING_AND_RECORDINGS_SCHEDULED_TO_BE_DELETED
|
||||
];
|
||||
const statusCode = scheduledSuccessCodes.includes(response.successCode) ? 202 : 200;
|
||||
|
||||
if (deleted.length > 0) {
|
||||
// Room was deleted
|
||||
return res.status(204).send();
|
||||
}
|
||||
|
||||
// Room was marked as deleted
|
||||
return res.status(202).json({ message: `Room '${roomId}' marked for deletion` });
|
||||
logger.info(response.message);
|
||||
return res.status(statusCode).json(response);
|
||||
} catch (error) {
|
||||
handleError(res, error, `deleting room '${roomId}'`);
|
||||
}
|
||||
@ -86,34 +99,30 @@ export const deleteRoom = async (req: Request, res: Response) => {
|
||||
export const bulkDeleteRooms = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const roomService = container.get(RoomService);
|
||||
const { roomIds, force } = req.query;
|
||||
const forceDelete = force === 'true';
|
||||
logger.verbose(`Deleting rooms: ${roomIds}`);
|
||||
|
||||
const { roomIds, withMeeting, withRecordings } = req.query as {
|
||||
roomIds: string[];
|
||||
withMeeting: MeetRoomDeletionPolicyWithMeeting;
|
||||
withRecordings: MeetRoomDeletionPolicyWithRecordings;
|
||||
};
|
||||
|
||||
try {
|
||||
const roomIdsArray = roomIds as string[];
|
||||
logger.verbose(`Deleting rooms: ${roomIds}`);
|
||||
const { successful, failed } = await roomService.bulkDeleteMeetRooms(roomIds, withMeeting, withRecordings);
|
||||
|
||||
const { deleted, markedForDeletion } = await roomService.bulkDeleteRooms(roomIdsArray, forceDelete);
|
||||
logger.info(
|
||||
`Bulk delete operation - Successfully processed rooms: ${successful.length}, failed to process: ${failed.length}`
|
||||
);
|
||||
|
||||
logger.info(`Deleted rooms: ${deleted.length}, marked for deletion: ${markedForDeletion.length}`);
|
||||
|
||||
// All rooms were deleted
|
||||
if (deleted.length > 0 && markedForDeletion.length === 0) {
|
||||
return res.sendStatus(204);
|
||||
if (failed.length === 0) {
|
||||
// All rooms were successfully processed
|
||||
return res.status(200).json({ message: 'All rooms successfully processed for deletion', successful });
|
||||
} else {
|
||||
// Some rooms failed to process
|
||||
return res
|
||||
.status(400)
|
||||
.json({ message: `${failed.length} room(s) failed to process while deleting`, successful, failed });
|
||||
}
|
||||
|
||||
// All room were marked for deletion
|
||||
if (deleted.length === 0 && markedForDeletion.length > 0) {
|
||||
const message =
|
||||
markedForDeletion.length === 1
|
||||
? `Room '${markedForDeletion[0]}' marked for deletion`
|
||||
: `Rooms '${markedForDeletion.join(', ')}' marked for deletion`;
|
||||
|
||||
return res.status(202).json({ message });
|
||||
}
|
||||
|
||||
// Mixed result (some rooms deleted, some marked for deletion)
|
||||
return res.status(200).json({ deleted, markedForDeletion });
|
||||
} catch (error) {
|
||||
handleError(res, error, `deleting rooms`);
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
import { MeetRoomDeletionErrorCode } from '@typings-ce';
|
||||
import { Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { container } from '../config/index.js';
|
||||
import { LoggerService } from '../services/index.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
type StatusError = 400 | 401 | 402 | 403 | 404 | 409 | 415 | 416 | 422 | 500 | 503;
|
||||
export class OpenViduMeetError extends Error {
|
||||
@ -220,6 +221,10 @@ export const errorInvalidRoomSecret = (roomId: string, secret: string): OpenVidu
|
||||
return new OpenViduMeetError('Room Error', `Secret '${secret}' is not recognized for room '${roomId}'`, 400);
|
||||
};
|
||||
|
||||
export const errorDeletingRoom = (errorCode: MeetRoomDeletionErrorCode, message: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError(errorCode, message, 409);
|
||||
};
|
||||
|
||||
// Participant errors
|
||||
|
||||
export const errorParticipantNotFound = (participantIdentity: string, roomId: string): OpenViduMeetError => {
|
||||
|
||||
@ -12,9 +12,9 @@ export * from './auth.service.js';
|
||||
|
||||
export * from './livekit.service.js';
|
||||
export * from './frontend-event.service.js';
|
||||
export * from './recording.service.js';
|
||||
export * from './room.service.js';
|
||||
export * from './participant-name.service.js';
|
||||
export * from './participant.service.js';
|
||||
export * from './recording.service.js';
|
||||
export * from './openvidu-webhook.service.js';
|
||||
export * from './livekit-webhook.service.js';
|
||||
|
||||
@ -31,7 +31,6 @@ import {
|
||||
MeetStorageService,
|
||||
MutexService,
|
||||
RedisLock,
|
||||
RoomService,
|
||||
TaskSchedulerService
|
||||
} from './index.js';
|
||||
|
||||
@ -39,7 +38,6 @@ import {
|
||||
export class RecordingService {
|
||||
constructor(
|
||||
@inject(LiveKitService) protected livekitService: LiveKitService,
|
||||
@inject(RoomService) protected roomService: RoomService,
|
||||
@inject(MutexService) protected mutexService: MutexService,
|
||||
@inject(TaskSchedulerService) protected taskSchedulerService: TaskSchedulerService,
|
||||
@inject(DistributedEventService) protected systemEventService: DistributedEventService,
|
||||
@ -552,6 +550,22 @@ export class RecordingService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to check if a room has recordings
|
||||
*
|
||||
* @param roomId - The ID of the room to check
|
||||
* @returns A promise that resolves to true if the room has recordings, false otherwise
|
||||
*/
|
||||
async hasRoomRecordings(roomId: string): Promise<boolean> {
|
||||
try {
|
||||
const response = await this.storageService.getAllRecordings(roomId, 1);
|
||||
return response.recordings.length > 0;
|
||||
} catch (error) {
|
||||
this.logger.warn(`Error checking recordings for room '${roomId}': ${error}`);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async getRecordingAsStream(
|
||||
recordingId: string,
|
||||
rangeHeader?: string
|
||||
@ -584,7 +598,7 @@ export class RecordingService {
|
||||
}
|
||||
|
||||
protected async validateRoomForStartRecording(roomId: string): Promise<void> {
|
||||
const room = await this.roomService.getMeetRoom(roomId);
|
||||
const room = await this.storageService.getMeetRoom(roomId);
|
||||
|
||||
if (!room) throw errorRoomNotFound(roomId);
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@ -68,3 +68,23 @@ export type MeetRoomFilters = {
|
||||
roomName?: string;
|
||||
fields?: string;
|
||||
};
|
||||
|
||||
export const enum MeetRoomDeletionSuccessCode {
|
||||
ROOM_DELETED = 'room_deleted',
|
||||
ROOM_WITH_ACTIVE_MEETING_DELETED = 'room_with_active_meeting_deleted',
|
||||
ROOM_WITH_ACTIVE_MEETING_SCHEDULED_TO_BE_DELETED = 'room_with_active_meeting_scheduled_to_be_deleted',
|
||||
ROOM_AND_RECORDINGS_DELETED = 'room_and_recordings_deleted',
|
||||
ROOM_CLOSED = 'room_closed',
|
||||
ROOM_WITH_ACTIVE_MEETING_AND_RECORDINGS_DELETED = 'room_with_active_meeting_and_recordings_deleted',
|
||||
ROOM_WITH_ACTIVE_MEETING_CLOSED = 'room_with_active_meeting_closed',
|
||||
ROOM_WITH_ACTIVE_MEETING_AND_RECORDINGS_SCHEDULED_TO_BE_DELETED = 'room_with_active_meeting_and_recordings_scheduled_to_be_deleted',
|
||||
ROOM_WITH_ACTIVE_MEETING_SCHEDULED_TO_BE_CLOSED = 'room_with_active_meeting_scheduled_to_be_closed'
|
||||
}
|
||||
|
||||
export const enum MeetRoomDeletionErrorCode {
|
||||
ROOM_HAS_ACTIVE_MEETING = 'room_has_active_meeting',
|
||||
ROOM_HAS_RECORDINGS = 'room_has_recordings',
|
||||
ROOM_WITH_ACTIVE_MEETING_HAS_RECORDINGS = 'room_with_active_meeting_has_recordings',
|
||||
ROOM_WITH_ACTIVE_MEETING_HAS_RECORDINGS_CANNOT_SCHEDULE_DELETION = 'room_with_active_meeting_has_recordings_cannot_schedule_deletion',
|
||||
ROOM_WITH_RECORDINGS_HAS_ACTIVE_MEETING = 'room_with_recordings_has_active_meeting'
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user