backend: Enhance room management logic
This commit is contained in:
parent
c598530918
commit
4ff00aad96
@ -3,7 +3,7 @@ import { Request, Response } from 'express';
|
||||
import { LoggerService } from '../services/logger.service.js';
|
||||
import { OpenViduMeetError } from '../models/error.model.js';
|
||||
import { RoomService } from '../services/room.service.js';
|
||||
import { MeetRoomOptions } from '@typings-ce';
|
||||
import { MeetRoomFilters, MeetRoomOptions } from '@typings-ce';
|
||||
|
||||
export const createRoom = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
@ -24,20 +24,15 @@ export const createRoom = async (req: Request, res: Response) => {
|
||||
|
||||
export const getRooms = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const fields = req.query.fields as string[] | undefined;
|
||||
const roomService = container.get(RoomService);
|
||||
const queryParams = req.query as unknown as MeetRoomFilters;
|
||||
|
||||
logger.verbose('Getting all rooms');
|
||||
|
||||
try {
|
||||
logger.verbose('Getting rooms');
|
||||
const response = await roomService.getAllMeetRooms(queryParams);
|
||||
|
||||
const roomService = container.get(RoomService);
|
||||
const rooms = await roomService.listOpenViduRooms();
|
||||
|
||||
if (fields && fields.length > 0) {
|
||||
const filteredRooms = rooms.map((room) => filterObjectFields(room, fields));
|
||||
return res.status(200).json(filteredRooms);
|
||||
}
|
||||
|
||||
return res.status(200).json(rooms);
|
||||
return res.status(200).json(response);
|
||||
} catch (error) {
|
||||
logger.error('Error getting rooms');
|
||||
handleError(res, error);
|
||||
@ -48,18 +43,13 @@ export const getRoom = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
|
||||
const { roomId } = req.params;
|
||||
const fields = req.query.fields as string[] | undefined;
|
||||
const fields = req.query.fields as string | undefined;
|
||||
|
||||
try {
|
||||
logger.verbose(`Getting room with id '${roomId}'`);
|
||||
|
||||
const roomService = container.get(RoomService);
|
||||
const room = await roomService.getMeetRoom(roomId);
|
||||
|
||||
if (fields && fields.length > 0) {
|
||||
const filteredRoom = filterObjectFields(room, fields);
|
||||
return res.status(200).json(filteredRoom);
|
||||
}
|
||||
const room = await roomService.getMeetRoom(roomId, fields);
|
||||
|
||||
return res.status(200).json(room);
|
||||
} catch (error) {
|
||||
@ -68,28 +58,38 @@ export const getRoom = async (req: Request, res: Response) => {
|
||||
}
|
||||
};
|
||||
|
||||
export const deleteRooms = async (req: Request, res: Response) => {
|
||||
export const deleteRoom = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const roomService = container.get(RoomService);
|
||||
|
||||
const { roomId } = req.params;
|
||||
const { roomIds } = req.body;
|
||||
|
||||
const roomsToDelete = roomId ? [roomId] : roomIds;
|
||||
|
||||
// TODO: Validate roomIds with ZOD
|
||||
if (!Array.isArray(roomsToDelete) || roomsToDelete.length === 0) {
|
||||
return res.status(400).json({ error: 'roomIds must be a non-empty array' });
|
||||
}
|
||||
|
||||
try {
|
||||
logger.verbose(`Deleting rooms: ${roomsToDelete.join(', ')}`);
|
||||
logger.verbose(`Deleting room: ${roomId}`);
|
||||
|
||||
await roomService.deleteRooms(roomsToDelete);
|
||||
logger.info(`Rooms deleted: ${roomsToDelete.join(', ')}`);
|
||||
return res.status(200).json({ message: 'Rooms deleted', deletedRooms: roomsToDelete });
|
||||
await roomService.bulkDeleteRooms([roomId]);
|
||||
logger.info(`Room deleted: ${roomId}`);
|
||||
return res.status(204).json();
|
||||
} catch (error) {
|
||||
logger.error(`Error deleting rooms: ${roomsToDelete.join(', ')}`);
|
||||
logger.error(`Error deleting room: ${roomId}`);
|
||||
handleError(res, error);
|
||||
}
|
||||
};
|
||||
|
||||
export const bulkDeleteRooms = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const roomService = container.get(RoomService);
|
||||
const { roomIds } = req.query;
|
||||
|
||||
logger.info(`Deleting rooms: ${roomIds}`);
|
||||
|
||||
try {
|
||||
const roomIdsArray = (roomIds as string).split(',');
|
||||
await roomService.bulkDeleteRooms(roomIdsArray);
|
||||
|
||||
return res.status(204).send();
|
||||
} catch (error) {
|
||||
logger.error(`Error deleting rooms: ${error}`);
|
||||
handleError(res, error);
|
||||
}
|
||||
};
|
||||
@ -114,41 +114,19 @@ export const getParticipantRole = async (req: Request, res: Response) => {
|
||||
|
||||
export const updateRoomPreferences = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const roomService = container.get(RoomService);
|
||||
const roomPreferences = req.body;
|
||||
const { roomId } = req.params;
|
||||
|
||||
logger.verbose(`Updating room preferences: ${JSON.stringify(req.body)}`);
|
||||
// const { roomName, roomPreferences } = req.body;
|
||||
logger.verbose(`Updating room preferences`);
|
||||
|
||||
// try {
|
||||
// const preferenceService = container.get(GlobalPreferencesService);
|
||||
// preferenceService.validateRoomPreferences(roomPreferences);
|
||||
|
||||
// const savedPreferences = await preferenceService.updateOpenViduRoomPreferences(roomName, roomPreferences);
|
||||
|
||||
// return res
|
||||
// .status(200)
|
||||
// .json({ message: 'Room preferences updated successfully.', preferences: savedPreferences });
|
||||
// } catch (error) {
|
||||
// if (error instanceof OpenViduCallError) {
|
||||
// logger.error(`Error saving room preferences: ${error.message}`);
|
||||
// return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
// }
|
||||
|
||||
// logger.error('Error saving room preferences:' + error);
|
||||
// return res.status(500).json({ message: 'Error saving room preferences', error });
|
||||
// }
|
||||
};
|
||||
|
||||
const filterObjectFields = (obj: Record<string, any>, fields: string[]): Record<string, any> => {
|
||||
return fields.reduce(
|
||||
(acc, field) => {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, field)) {
|
||||
acc[field] = obj[field];
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, any>
|
||||
);
|
||||
try {
|
||||
const room = await roomService.updateMeetRoomPreferences(roomId, roomPreferences);
|
||||
return res.status(200).json(room);
|
||||
} catch (error) {
|
||||
logger.error(`Error saving room preferences: ${error}`);
|
||||
handleError(res, error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (res: Response, error: OpenViduMeetError | unknown) => {
|
||||
|
||||
18
backend/src/helpers/utils.helper.ts
Normal file
18
backend/src/helpers/utils.helper.ts
Normal file
@ -0,0 +1,18 @@
|
||||
export class UtilsHelper {
|
||||
private constructor() {
|
||||
// Prevent instantiation of this utility class
|
||||
}
|
||||
|
||||
static filterObjectFields = (obj: Record<string, unknown>, fields: string[]): Record<string, any> => {
|
||||
return fields.reduce(
|
||||
(acc, field) => {
|
||||
if (Object.prototype.hasOwnProperty.call(obj, field)) {
|
||||
acc[field] = obj[field];
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{} as Record<string, unknown>
|
||||
);
|
||||
};
|
||||
}
|
||||
@ -3,11 +3,28 @@ import {
|
||||
MeetRoomOptions,
|
||||
MeetRecordingPreferences,
|
||||
MeetRoomPreferences,
|
||||
MeetVirtualBackgroundPreferences
|
||||
MeetVirtualBackgroundPreferences,
|
||||
MeetRoomFilters
|
||||
} from '@typings-ce';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
import { z } from 'zod';
|
||||
|
||||
const sanitizeId = (val: string): string => {
|
||||
return val
|
||||
.trim() // Remove leading and trailing spaces
|
||||
.replace(/\s+/g, '-') // Replace spaces with hyphens
|
||||
.replace(/[^a-zA-Z0-9_-]/g, ''); // Remove special characters (allow alphanumeric, hyphens and underscores)
|
||||
};
|
||||
|
||||
const nonEmptySanitizedString = (fieldName: string) =>
|
||||
z
|
||||
.string()
|
||||
.min(1, { message: `${fieldName} is required and cannot be empty` })
|
||||
.transform(sanitizeId)
|
||||
.refine((data) => data !== '', {
|
||||
message: `${fieldName} cannot be empty after sanitization`
|
||||
});
|
||||
|
||||
const RecordingPreferencesSchema: z.ZodType<MeetRecordingPreferences> = z.object({
|
||||
enabled: z.boolean()
|
||||
});
|
||||
@ -61,6 +78,33 @@ const GetParticipantRoleSchema = z.object({
|
||||
secret: z.string()
|
||||
});
|
||||
|
||||
const GetRoomFiltersSchema: z.ZodType<MeetRoomFilters> = z.object({
|
||||
maxItems: z.coerce
|
||||
.number()
|
||||
.int()
|
||||
.transform((val) => (val > 100 ? 100 : val))
|
||||
.default(10),
|
||||
nextPageToken: z.string().optional(),
|
||||
fields: z.string().optional()
|
||||
});
|
||||
|
||||
const BulkDeleteRoomsSchema = z.object({
|
||||
roomIds: z.preprocess(
|
||||
(arg) => {
|
||||
if (typeof arg === 'string') {
|
||||
// If the argument is a string, it is expected to be a comma-separated list of recording IDs.
|
||||
return arg
|
||||
.split(',')
|
||||
.map((s) => s.trim())
|
||||
.filter((s) => s !== '');
|
||||
}
|
||||
|
||||
return arg;
|
||||
},
|
||||
z.array(nonEmptySanitizedString('recordingId')).default([])
|
||||
)
|
||||
});
|
||||
|
||||
export const withValidRoomOptions = (req: Request, res: Response, next: NextFunction) => {
|
||||
const { success, error, data } = RoomRequestOptionsSchema.safeParse(req.body);
|
||||
|
||||
@ -72,14 +116,40 @@ export const withValidRoomOptions = (req: Request, res: Response, next: NextFunc
|
||||
next();
|
||||
};
|
||||
|
||||
export const validateGetRoomQueryParams = (req: Request, res: Response, next: NextFunction) => {
|
||||
const fieldsQuery = req.query.fields as string | undefined;
|
||||
export const withValidRoomFiltersRequest = (req: Request, res: Response, next: NextFunction) => {
|
||||
const { success, error, data } = GetRoomFiltersSchema.safeParse(req.query);
|
||||
|
||||
if (fieldsQuery) {
|
||||
const fields = fieldsQuery.split(',').map((f) => f.trim());
|
||||
req.query.fields = fields;
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
}
|
||||
|
||||
req.query = {
|
||||
...data,
|
||||
maxItems: data.maxItems?.toString()
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
export const withValidRoomPreferences = (req: Request, res: Response, next: NextFunction) => {
|
||||
const { success, error, data } = RoomPreferencesSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
next();
|
||||
};
|
||||
|
||||
export const withValidRoomBulkDeleteRequest = (req: Request, res: Response, next: NextFunction) => {
|
||||
const { success, error, data } = BulkDeleteRoomsSchema.safeParse(req.query);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
}
|
||||
|
||||
req.query.roomIds = data.roomIds.join(',');
|
||||
next();
|
||||
};
|
||||
|
||||
@ -87,16 +157,7 @@ export const validateGetParticipantRoleRequest = (req: Request, res: Response, n
|
||||
const { success, error, data } = GetParticipantRoleSchema.safeParse(req.query);
|
||||
|
||||
if (!success) {
|
||||
const errors = error.errors.map((error) => ({
|
||||
field: error.path.join('.'),
|
||||
message: error.message
|
||||
}));
|
||||
|
||||
return res.status(422).json({
|
||||
error: 'Unprocessable Entity',
|
||||
message: 'Invalid request query',
|
||||
details: errors
|
||||
});
|
||||
return rejectRequest(res, error);
|
||||
}
|
||||
|
||||
req.query = data;
|
||||
|
||||
@ -3,7 +3,8 @@ export const enum RedisKeyPrefix {
|
||||
}
|
||||
|
||||
export const enum RedisKeyName {
|
||||
GLOBAL_PREFERENCES = `${RedisKeyPrefix.BASE}global_preferences`
|
||||
GLOBAL_PREFERENCES = `${RedisKeyPrefix.BASE}global_preferences`,
|
||||
ROOM = `${RedisKeyPrefix.BASE}room:`,
|
||||
}
|
||||
|
||||
export const enum RedisLockPrefix {
|
||||
|
||||
@ -7,10 +7,12 @@ import {
|
||||
apiKeyValidator,
|
||||
participantTokenValidator,
|
||||
validateGetParticipantRoleRequest,
|
||||
validateGetRoomQueryParams,
|
||||
withValidRoomFiltersRequest,
|
||||
withValidRoomOptions,
|
||||
configureCreateRoomAuth,
|
||||
configureRoomAuthorization
|
||||
configureRoomAuthorization,
|
||||
withValidRoomPreferences,
|
||||
withValidRoomBulkDeleteRequest
|
||||
} from '../middlewares/index.js';
|
||||
|
||||
import { UserRole } from '@typings-ce';
|
||||
@ -21,31 +23,37 @@ roomRouter.use(bodyParser.json());
|
||||
|
||||
// Room Routes
|
||||
roomRouter.post('/', configureCreateRoomAuth, withValidRoomOptions, roomCtrl.createRoom);
|
||||
|
||||
|
||||
|
||||
|
||||
roomRouter.get(
|
||||
'/',
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||
validateGetRoomQueryParams,
|
||||
withValidRoomFiltersRequest,
|
||||
roomCtrl.getRooms
|
||||
);
|
||||
roomRouter.delete(
|
||||
'/',
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)),
|
||||
withValidRoomBulkDeleteRequest,
|
||||
roomCtrl.bulkDeleteRooms
|
||||
);
|
||||
roomRouter.get(
|
||||
'/:roomId',
|
||||
withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), participantTokenValidator),
|
||||
configureRoomAuthorization,
|
||||
validateGetRoomQueryParams,
|
||||
roomCtrl.getRoom
|
||||
);
|
||||
roomRouter.delete('/:roomId', withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)), roomCtrl.deleteRooms);
|
||||
|
||||
// Room preferences
|
||||
roomRouter.put('/', withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)), roomCtrl.updateRoomPreferences);
|
||||
roomRouter.delete('/:roomId', withAuth(apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)), roomCtrl.deleteRoom);
|
||||
|
||||
// Internal room routes
|
||||
export const internalRoomRouter = Router();
|
||||
internalRoomRouter.use(bodyParser.urlencoded({ extended: true }));
|
||||
internalRoomRouter.use(bodyParser.json());
|
||||
|
||||
// Room preferences
|
||||
internalRoomRouter.put(
|
||||
'/:roomId',
|
||||
withAuth(tokenAndRoleValidator(UserRole.ADMIN)),
|
||||
withValidRoomPreferences,
|
||||
roomCtrl.updateRoomPreferences
|
||||
);
|
||||
|
||||
internalRoomRouter.get('/:roomId/participant-role', validateGetParticipantRoleRequest, roomCtrl.getParticipantRole);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user