Prevents editing rooms with active meetings

Enhances room management by preventing modifications to rooms with active meetings.

Adds validation to backend to prevent updates to room configuration during an active meeting.

Improves frontend user experience by disabling the room editing option and adding a guard to redirect users away from the edit page.
This commit is contained in:
Carlos Santos 2025-11-11 13:10:13 +01:00
parent 449f9cbf25
commit d72149c97d
9 changed files with 106 additions and 7 deletions

View File

@ -0,0 +1,8 @@
description: Room not found
content:
application/json:
schema:
$ref: '../schemas/error.yaml'
example:
error: 'Room Error'
message: 'Room "room_123" has an active meeting'

View File

@ -56,10 +56,10 @@
description: |
Delete multiple OpenVidu Meet rooms at once with the specified room IDs.
If any of the rooms have active meetings or recordings,
If any of the rooms have active meetings or recordings,
deletion behavior is determined by the provided `withMeeting` and `withRecordings` deletion policies.
Depending on these policies, the rooms may be deleted/closed immediately, scheduled to be deleted/closed once the meetings end,
Depending on these policies, the rooms may be deleted/closed immediately, scheduled to be deleted/closed once the meetings end,
or the operation may fail if deletion is not permitted.
tags:
- OpenVidu Meet - Rooms
@ -120,10 +120,10 @@
description: |
Deletes the specified OpenVidu Meet room by its room ID.
If the room has an active meeting or existing recordings,
If the room has an active meeting or existing recordings,
deletion behavior is determined by the provided `withMeeting` and `withRecordings` deletion policies.
Depending on these policies, the room may be deleted/closed immediately, scheduled to be deleted/closed once the meeting ends,
Depending on these policies, the room may be deleted/closed immediately, scheduled to be deleted/closed once the meeting ends,
or the operation may fail if deletion is not permitted.
tags:
- OpenVidu Meet - Rooms
@ -204,6 +204,8 @@
$ref: '../components/responses/forbidden-error.yaml'
'404':
$ref: '../components/responses/error-room-not-found.yaml'
'409':
$ref: '../components/responses/error-room-active-meeting.yaml'
'422':
$ref: '../components/responses/validation-error.yaml'
'500':

View File

@ -205,6 +205,10 @@ export const errorRoomClosed = (roomId: string): OpenViduMeetError => {
return new OpenViduMeetError('Room Error', `Room '${roomId}' is closed and cannot be joined`, 409);
};
export const errorRoomActiveMeeting = (roomId: string): OpenViduMeetError => {
return new OpenViduMeetError('Room Error', `Room '${roomId}' has an active meeting`, 409);
};
export const errorInvalidRoomSecret = (roomId: string, secret: string): OpenViduMeetError => {
return new OpenViduMeetError('Room Error', `Secret '${secret}' is not recognized for room '${roomId}'`, 400);
};

View File

@ -25,6 +25,7 @@ import { validateRecordingTokenMetadata } from '../middlewares/index.js';
import {
errorDeletingRoom,
errorInvalidRoomSecret,
errorRoomActiveMeeting,
errorRoomNotFound,
internalError,
OpenViduMeetError
@ -139,6 +140,12 @@ export class RoomService {
*/
async updateMeetRoomConfig(roomId: string, config: MeetRoomConfig): Promise<MeetRoom> {
const room = await this.getMeetRoom(roomId);
if (room.status === MeetRoomStatus.ACTIVE_MEETING) {
// Reject config updates during active meetings
throw errorRoomActiveMeeting(roomId);
}
room.config = config;
await this.roomRepository.update(room);

View File

@ -5,10 +5,12 @@ import { FrontendEventService } from '../../../../src/services/index.js';
import {
createRoom,
deleteAllRooms,
disconnectFakeParticipants,
getRoom,
startTestServer,
updateRoomConfig
} from '../../../helpers/request-helpers.js';
import { RoomData, setupSingleRoom } from '../../../helpers/test-scenarios.js';
describe('Room API Tests', () => {
beforeAll(async () => {
@ -133,6 +135,42 @@ describe('Room API Tests', () => {
});
});
describe('Update Room Config with Active Meeting Tests', () => {
let roomData: RoomData;
afterEach(async () => {
await disconnectFakeParticipants();
});
it('should reject room config update when there is an active meeting', async () => {
// Create a room and start a meeting
roomData = await setupSingleRoom(true);
// Try to update room config
const newConfig = {
recording: {
enabled: false
},
chat: {
enabled: false
},
virtualBackground: {
enabled: false
},
e2ee: {
enabled: false
}
};
const response = await updateRoomConfig(roomData.room.roomId, newConfig);
// Should return 409 Conflict
expect(response.status).toBe(409);
expect(response.body.error).toBe('Room Error');
expect(response.body.message).toContain(`Room '${roomData.room.roomId}' has an active meeting`);
});
});
describe('Update Room Config Validation failures', () => {
it('should fail when config has incorrect structure', async () => {
const { roomId } = await createRoom({

View File

@ -323,8 +323,8 @@ export class RoomsListsComponent implements OnInit, OnChanges {
return room.status !== MeetRoomStatus.CLOSED;
}
canEditRoom(_room: MeetRoom): boolean {
return true;
canEditRoom(room: MeetRoom): boolean {
return room.status !== MeetRoomStatus.ACTIVE_MEETING;
}
// ===== UI HELPER METHODS =====

View File

@ -0,0 +1,37 @@
import { inject } from '@angular/core';
import { CanActivateFn, Router } from '@angular/router';
import { MeetRoomStatus } from '@openvidu-meet/typings';
import { RoomService } from '../services/room.service';
/**
* Guard that prevents editing a room when there's an active meeting.
* Redirects to /rooms if the room has an active meeting.
*/
export const checkRoomEditGuard: CanActivateFn = async (route) => {
const roomService = inject(RoomService);
const router = inject(Router);
const roomId = route.paramMap.get('roomId');
if (!roomId) {
console.warn('No roomId provided in route params');
router.navigate(['/rooms']);
return false;
}
try {
const room = await roomService.getRoom(roomId);
if (room.status === MeetRoomStatus.ACTIVE_MEETING) {
console.warn(`Cannot edit room ${roomId} - active meeting in progress`);
router.navigate(['/rooms']);
return false;
}
return true;
} catch (error) {
console.error(`Error checking room status for ${roomId}:`, error);
router.navigate(['/rooms']);
return false;
}
};

View File

@ -1,4 +1,5 @@
export * from './auth.guard';
export * from './check-room-edit.guard';
export * from './extract-query-params.guard';
export * from './remove-query-params.guard';
export * from './run-serially.guard';

View File

@ -2,6 +2,7 @@ import { Routes } from '@angular/router';
import {
checkParticipantRoleAndAuthGuard,
checkRecordingAuthGuard,
checkRoomEditGuard,
checkUserAuthenticatedGuard,
checkUserNotAuthenticatedGuard,
extractRecordingQueryParamsGuard,
@ -90,7 +91,8 @@ export const baseRoutes: Routes = [
},
{
path: 'rooms/:roomId/edit',
component: RoomWizardComponent
component: RoomWizardComponent,
canActivate: [checkRoomEditGuard]
},
{
path: 'recordings',