backend: Improve error handling and logging, and refactor code
This commit is contained in:
parent
963db44b55
commit
8357a54597
@ -3,6 +3,15 @@ import { ClaimGrants } from 'livekit-server-sdk';
|
||||
import { container } from '../config/index.js';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
import { MEET_ACCESS_TOKEN_EXPIRATION, MEET_REFRESH_TOKEN_EXPIRATION } from '../environment.js';
|
||||
import {
|
||||
errorInvalidCredentials,
|
||||
errorInvalidRefreshToken,
|
||||
errorInvalidTokenSubject,
|
||||
errorRefreshTokenNotPresent,
|
||||
errorUnauthorized,
|
||||
handleError,
|
||||
rejectRequestFromMeetError
|
||||
} from '../models/error.model.js';
|
||||
import { AuthService, LoggerService, TokenService, UserService } from '../services/index.js';
|
||||
import { getCookieOptions } from '../utils/cookie-utils.js';
|
||||
|
||||
@ -16,7 +25,8 @@ export const login = async (req: Request, res: Response) => {
|
||||
|
||||
if (!user) {
|
||||
logger.warn('Login failed');
|
||||
return res.status(404).json({ message: 'Login failed. Invalid username or password' });
|
||||
const error = errorInvalidCredentials();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -36,8 +46,7 @@ export const login = async (req: Request, res: Response) => {
|
||||
logger.info(`Login succeeded for user '${username}'`);
|
||||
return res.status(200).json({ message: 'Login succeeded' });
|
||||
} catch (error) {
|
||||
logger.error('Error generating token' + error);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
handleError(res, error, 'generating token');
|
||||
}
|
||||
};
|
||||
|
||||
@ -56,7 +65,8 @@ export const refreshToken = async (req: Request, res: Response) => {
|
||||
|
||||
if (!refreshToken) {
|
||||
logger.warn('No refresh token provided');
|
||||
return res.status(400).json({ message: 'No refresh token provided' });
|
||||
const error = errorRefreshTokenNotPresent();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
const tokenService = container.get(TokenService);
|
||||
@ -65,8 +75,9 @@ export const refreshToken = async (req: Request, res: Response) => {
|
||||
try {
|
||||
payload = await tokenService.verifyToken(refreshToken);
|
||||
} catch (error) {
|
||||
logger.error('Error verifying refresh token' + error);
|
||||
return res.status(400).json({ message: 'Invalid refresh token' });
|
||||
logger.error('Error verifying refresh token:', error);
|
||||
const meetError = errorInvalidRefreshToken();
|
||||
return rejectRequestFromMeetError(res, meetError);
|
||||
}
|
||||
|
||||
const username = payload.sub;
|
||||
@ -75,7 +86,8 @@ export const refreshToken = async (req: Request, res: Response) => {
|
||||
|
||||
if (!user) {
|
||||
logger.warn('Invalid refresh token subject');
|
||||
return res.status(403).json({ message: 'Invalid refresh token subject' });
|
||||
const error = errorInvalidTokenSubject();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
try {
|
||||
@ -88,8 +100,7 @@ export const refreshToken = async (req: Request, res: Response) => {
|
||||
logger.info(`Token refreshed for user ${username}`);
|
||||
return res.status(200).json({ message: 'Token refreshed' });
|
||||
} catch (error) {
|
||||
logger.error('Error refreshing token' + error);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
handleError(res, error, 'refreshing token');
|
||||
}
|
||||
};
|
||||
|
||||
@ -97,7 +108,8 @@ export const getProfile = (req: Request, res: Response) => {
|
||||
const user = req.session?.user;
|
||||
|
||||
if (!user) {
|
||||
return res.status(401).json({ message: 'Unauthorized' });
|
||||
const error = errorUnauthorized();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
return res.status(200).json(user);
|
||||
|
||||
@ -1,13 +1,12 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { errorProFeature, rejectRequestFromMeetError } from '../../models/error.model.js';
|
||||
|
||||
export const updateAppearancePreferences = async (req: Request, res: Response) => {
|
||||
return res
|
||||
.status(402)
|
||||
.json({ message: 'Storing appearance preference is a PRO feature. Please, Updrade to OpenVidu PRO' });
|
||||
export const updateAppearancePreferences = async (_req: Request, res: Response) => {
|
||||
const error = errorProFeature('update appearance preferences');
|
||||
rejectRequestFromMeetError(res, error);
|
||||
};
|
||||
|
||||
export const getAppearancePreferences = async (req: Request, res: Response) => {
|
||||
return res
|
||||
.status(402)
|
||||
.json({ message: 'Getting appearance preference is a PRO feature. Please, Updrade to OpenVidu PRO' });
|
||||
export const getAppearancePreferences = async (_req: Request, res: Response) => {
|
||||
const error = errorProFeature('get appearance preferences');
|
||||
rejectRequestFromMeetError(res, error);
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { SecurityPreferencesDTO, UpdateSecurityPreferencesDTO } from '@typings-ce';
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../../config/index.js';
|
||||
import { OpenViduMeetError } from '../../models/error.model.js';
|
||||
import { handleError } from '../../models/error.model.js';
|
||||
import { LoggerService, MeetStorageService } from '../../services/index.js';
|
||||
|
||||
export const updateSecurityPreferences = async (req: Request, res: Response) => {
|
||||
@ -30,13 +30,7 @@ export const updateSecurityPreferences = async (req: Request, res: Response) =>
|
||||
|
||||
return res.status(200).json({ message: 'Security preferences updated successfully' });
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error updating security preferences: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
logger.error('Error updating security preferences:' + error);
|
||||
return res.status(500).json({ message: 'Error updating security preferences' });
|
||||
handleError(res, error, 'updating security preferences');
|
||||
}
|
||||
};
|
||||
|
||||
@ -44,15 +38,11 @@ export const getSecurityPreferences = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const preferenceService = container.get(MeetStorageService);
|
||||
|
||||
logger.verbose('Getting security preferences');
|
||||
|
||||
try {
|
||||
const preferences = await preferenceService.getGlobalPreferences();
|
||||
|
||||
if (!preferences) {
|
||||
//TODO: Create OpenViduMeetError for this case
|
||||
logger.error('Security preferences not found');
|
||||
return res.status(404).json({ message: 'Security preferences not found' });
|
||||
}
|
||||
|
||||
// Convert the preferences to the DTO format by removing credentials
|
||||
const securityPreferences = preferences.securityPreferences;
|
||||
const securityPreferencesDTO: SecurityPreferencesDTO = {
|
||||
@ -66,12 +56,6 @@ export const getSecurityPreferences = async (_req: Request, res: Response) => {
|
||||
};
|
||||
return res.status(200).json(securityPreferencesDTO);
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error getting security preferences: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
logger.error('Error getting security preferences:' + error);
|
||||
return res.status(500).json({ message: 'Error fetching security preferences from database' });
|
||||
handleError(res, error, 'getting security preferences');
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import { WebhookPreferences } from '@typings-ce';
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../../config/index.js';
|
||||
import { OpenViduMeetError } from '../../models/error.model.js';
|
||||
import { handleError } from '../../models/error.model.js';
|
||||
import { LoggerService, MeetStorageService } from '../../services/index.js';
|
||||
|
||||
export const updateWebhookPreferences = async (req: Request, res: Response) => {
|
||||
@ -18,13 +18,7 @@ export const updateWebhookPreferences = async (req: Request, res: Response) => {
|
||||
|
||||
return res.status(200).json({ message: 'Webhooks preferences updated successfully' });
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error updating webhooks preferences: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
logger.error('Error updating webhooks preferences:' + error);
|
||||
return res.status(500).json({ message: 'Error updating webhooks preferences' });
|
||||
handleError(res, error, 'updating webhooks preferences');
|
||||
}
|
||||
};
|
||||
|
||||
@ -32,23 +26,12 @@ export const getWebhookPreferences = async (_req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const preferenceService = container.get(MeetStorageService);
|
||||
|
||||
logger.verbose('Getting webhooks preferences');
|
||||
|
||||
try {
|
||||
const preferences = await preferenceService.getGlobalPreferences();
|
||||
|
||||
if (!preferences) {
|
||||
//TODO: Creare OpenViduMeetError for this case
|
||||
logger.error('Webhooks preferences not found');
|
||||
return res.status(404).json({ message: 'Webhooks preferences not found' });
|
||||
}
|
||||
|
||||
return res.status(200).json(preferences.webhooksPreferences);
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error getting webhooks preferences: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
logger.error('Error getting webhooks preferences:' + error);
|
||||
return res.status(500).json({ message: 'Error fetching webhooks preferences from database' });
|
||||
handleError(res, error, 'getting webhooks preferences');
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import { OpenViduMeetError } from '../models/error.model.js';
|
||||
import { handleError } from '../models/error.model.js';
|
||||
import { LiveKitService, LoggerService, RoomService } from '../services/index.js';
|
||||
|
||||
export const endMeeting = async (req: Request, res: Response) => {
|
||||
@ -14,30 +14,15 @@ export const endMeeting = async (req: Request, res: Response) => {
|
||||
try {
|
||||
await roomService.getMeetRoom(roomId);
|
||||
} catch (error) {
|
||||
logger.error(`Error getting room '${roomId}'`);
|
||||
return handleError(res, error);
|
||||
return handleError(res, error, `getting room '${roomId}'`);
|
||||
}
|
||||
|
||||
try {
|
||||
logger.info(`Ending meeting from room '${roomId}'`);
|
||||
// To end a meeting, we need to delete the room from LiveKit
|
||||
await livekitService.deleteRoom(roomId);
|
||||
res.status(200).json({ message: 'Meeting ended successfully' });
|
||||
} catch (error) {
|
||||
logger.error(`Error ending meeting from room: ${roomId}`);
|
||||
return handleError(res, error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (res: Response, error: OpenViduMeetError | unknown) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.error(String(error));
|
||||
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
} else {
|
||||
res.status(500).json({
|
||||
name: 'Meeting Error',
|
||||
message: 'Internal server error. Meeting operation failed'
|
||||
});
|
||||
handleError(res, error, `ending meeting from room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import { Request, Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
import { MEET_PARTICIPANT_TOKEN_EXPIRATION } from '../environment.js';
|
||||
import { OpenViduMeetError } from '../models/error.model.js';
|
||||
import { errorParticipantTokenStillValid, handleError, rejectRequestFromMeetError } from '../models/error.model.js';
|
||||
import { LoggerService, ParticipantService, RoomService, TokenService } from '../services/index.js';
|
||||
import { getCookieOptions } from '../utils/cookie-utils.js';
|
||||
|
||||
@ -15,7 +15,7 @@ export const generateParticipantToken = async (req: Request, res: Response) => {
|
||||
const { roomId } = participantOptions;
|
||||
|
||||
try {
|
||||
logger.verbose(`Generating participant token for room ${roomId}`);
|
||||
logger.verbose(`Generating participant token for room '${roomId}'`);
|
||||
await roomService.createLivekitRoom(roomId);
|
||||
const token = await participantService.generateOrRefreshParticipantToken(participantOptions);
|
||||
|
||||
@ -26,8 +26,7 @@ export const generateParticipantToken = async (req: Request, res: Response) => {
|
||||
);
|
||||
return res.status(200).json({ token });
|
||||
} catch (error) {
|
||||
logger.error(`Error generating participant token for room: ${roomId}`);
|
||||
return handleError(res, error);
|
||||
handleError(res, error, `generating participant token for room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -44,7 +43,9 @@ export const refreshParticipantToken = async (req: Request, res: Response) => {
|
||||
try {
|
||||
await tokenService.verifyToken(previousToken);
|
||||
logger.verbose('Previous participant token is valid. No need to refresh');
|
||||
return res.status(409).json({ message: 'Participant token is still valid' });
|
||||
|
||||
const error = errorParticipantTokenStillValid();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
} catch (error) {
|
||||
logger.verbose('Previous participant token is invalid');
|
||||
}
|
||||
@ -63,11 +64,10 @@ export const refreshParticipantToken = async (req: Request, res: Response) => {
|
||||
token,
|
||||
getCookieOptions('/', MEET_PARTICIPANT_TOKEN_EXPIRATION)
|
||||
);
|
||||
logger.verbose(`Participant token refreshed for room ${roomId}`);
|
||||
logger.verbose(`Participant token refreshed for room '${roomId}'`);
|
||||
return res.status(200).json({ token });
|
||||
} catch (error) {
|
||||
logger.error(`Error refreshing participant token for room: ${roomId}`);
|
||||
return handleError(res, error);
|
||||
handleError(res, error, `refreshing participant token for room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -77,24 +77,10 @@ export const deleteParticipant = async (req: Request, res: Response) => {
|
||||
const { roomId, participantName } = req.params;
|
||||
|
||||
try {
|
||||
logger.verbose(`Deleting participant '${participantName}' from room '${roomId}'`);
|
||||
await participantService.deleteParticipant(participantName, roomId);
|
||||
res.status(200).json({ message: 'Participant deleted' });
|
||||
} catch (error) {
|
||||
logger.error(`Error deleting participant from room: ${roomId}`);
|
||||
return handleError(res, error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (res: Response, error: OpenViduMeetError | unknown) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.error(String(error));
|
||||
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
} else {
|
||||
res.status(500).json({
|
||||
name: 'Participant Error',
|
||||
message: 'Internal server error. Participant operation failed'
|
||||
});
|
||||
handleError(res, error, `deleting participant '${participantName}' from room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { Readable } from 'stream';
|
||||
import { container } from '../config/index.js';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
import { internalError, OpenViduMeetError } from '../models/error.model.js';
|
||||
import { handleError, internalError, rejectRequestFromMeetError } from '../models/error.model.js';
|
||||
import { LoggerService, RecordingService } from '../services/index.js';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
export const startRecording = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const recordingService = container.get(RecordingService);
|
||||
const { roomId } = req.body;
|
||||
logger.info(`Initiating recording for room ${roomId}`);
|
||||
logger.info(`Starting recording in room '${roomId}'`);
|
||||
|
||||
try {
|
||||
const recordingInfo = await recordingService.startRecording(roomId);
|
||||
@ -20,12 +20,7 @@ export const startRecording = async (req: Request, res: Response) => {
|
||||
|
||||
return res.status(201).json(recordingInfo);
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error starting recording: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
return res.status(500).json({ name: 'Recording Error', message: 'Failed to start recording' });
|
||||
handleError(res, error, `starting recording in room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -40,9 +35,9 @@ export const getRecordings = async (req: Request, res: Response) => {
|
||||
if (payload && payload.video) {
|
||||
const roomId = payload.video.room;
|
||||
queryParams.roomId = roomId;
|
||||
logger.debug(`Getting recordings for room ${roomId}`);
|
||||
logger.info(`Getting recordings for room '${roomId}'`);
|
||||
} else {
|
||||
logger.verbose('Getting all recordings');
|
||||
logger.info('Getting all recordings');
|
||||
}
|
||||
|
||||
try {
|
||||
@ -58,12 +53,7 @@ export const getRecordings = async (req: Request, res: Response) => {
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error getting all recordings: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
return res.status(500).json({ name: 'Recording Error', message: 'Unexpected error getting recordings' });
|
||||
handleError(res, error, 'getting recordings');
|
||||
}
|
||||
};
|
||||
|
||||
@ -88,12 +78,7 @@ export const bulkDeleteRecordings = async (req: Request, res: Response) => {
|
||||
// Some or all recordings could not be deleted
|
||||
return res.status(200).json({ deleted, notDeleted });
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error deleting recordings: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
return res.status(500).json({ name: 'Recording Error', message: 'Unexpected error deleting recordings' });
|
||||
handleError(res, error, 'deleting recordings');
|
||||
}
|
||||
};
|
||||
|
||||
@ -103,18 +88,13 @@ export const getRecording = async (req: Request, res: Response) => {
|
||||
const recordingId = req.params.recordingId;
|
||||
const fields = req.query.fields as string | undefined;
|
||||
|
||||
logger.info(`Getting recording ${recordingId}`);
|
||||
logger.info(`Getting recording '${recordingId}'`);
|
||||
|
||||
try {
|
||||
const recordingInfo = await recordingService.getRecording(recordingId, fields);
|
||||
return res.status(200).json(recordingInfo);
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error getting recording: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
return res.status(500).json({ name: 'Recording Error', message: 'Unexpected error getting recording' });
|
||||
handleError(res, error, `getting recording '${recordingId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -123,19 +103,14 @@ export const stopRecording = async (req: Request, res: Response) => {
|
||||
const recordingId = req.params.recordingId;
|
||||
|
||||
try {
|
||||
logger.info(`Initiating stop for recording ${recordingId}`);
|
||||
logger.info(`Stopping recording '${recordingId}'`);
|
||||
const recordingService = container.get(RecordingService);
|
||||
|
||||
const recordingInfo = await recordingService.stopRecording(recordingId);
|
||||
res.setHeader('Location', `${req.protocol}://${req.get('host')}${req.originalUrl}`);
|
||||
return res.status(202).json(recordingInfo);
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error stopping recording: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
return res.status(500).json({ name: 'Recording Error', message: 'Unexpected error stopping recording' });
|
||||
handleError(res, error, `stopping recording '${recordingId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -143,19 +118,14 @@ export const deleteRecording = async (req: Request, res: Response) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const recordingService = container.get(RecordingService);
|
||||
const recordingId = req.params.recordingId;
|
||||
logger.info(`Deleting recording ${recordingId}`);
|
||||
logger.info(`Deleting recording '${recordingId}'`);
|
||||
|
||||
try {
|
||||
// TODO: Check role to determine if the request is from an admin or a participant
|
||||
await recordingService.deleteRecording(recordingId);
|
||||
return res.status(204).send();
|
||||
} catch (error) {
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error deleting recording: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
return res.status(500).json({ name: 'Recording Error', message: 'Unexpected error deleting recording' });
|
||||
handleError(res, error, `deleting recording '${recordingId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -174,7 +144,7 @@ export const getRecordingMedia = async (req: Request, res: Response) => {
|
||||
let fileStream: Readable | undefined;
|
||||
|
||||
try {
|
||||
logger.info(`Streaming recording ${recordingId}`);
|
||||
logger.info(`Streaming recording '${recordingId}'`);
|
||||
const recordingService = container.get(RecordingService);
|
||||
|
||||
const result = await recordingService.getRecordingAsStream(recordingId, range);
|
||||
@ -182,11 +152,11 @@ export const getRecordingMedia = async (req: Request, res: Response) => {
|
||||
fileStream = result.fileStream;
|
||||
|
||||
fileStream.on('error', (streamError) => {
|
||||
logger.error(`Error streaming recording ${recordingId}: ${streamError.message}`);
|
||||
logger.error(`Error streaming recording '${recordingId}': ${streamError.message}`);
|
||||
|
||||
if (!res.headersSent) {
|
||||
const error = internalError(streamError);
|
||||
res.status(error.statusCode).json({ name: 'Recording Error', message: error.message });
|
||||
const error = internalError(`streaming recording '${recordingId}'`);
|
||||
rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
res.end();
|
||||
@ -195,7 +165,7 @@ export const getRecordingMedia = async (req: Request, res: Response) => {
|
||||
// Handle client disconnection
|
||||
req.on('close', () => {
|
||||
if (fileStream && !fileStream.destroyed) {
|
||||
logger.debug(`Client closed connection for recording media ${recordingId}`);
|
||||
logger.debug(`Client closed connection for recording media '${recordingId}'`);
|
||||
fileStream.destroy();
|
||||
}
|
||||
});
|
||||
@ -225,12 +195,11 @@ export const getRecordingMedia = async (req: Request, res: Response) => {
|
||||
fileStream
|
||||
.pipe(res)
|
||||
.on('finish', () => {
|
||||
logger.debug(`Finished streaming recording ${recordingId}`);
|
||||
|
||||
logger.debug(`Finished streaming recording '${recordingId}'`);
|
||||
res.end();
|
||||
})
|
||||
.on('error', (err) => {
|
||||
logger.error(`Error in response stream for ${recordingId}: ${err.message}`);
|
||||
logger.error(`Error in response stream for recording '${recordingId}': ${err.message}`);
|
||||
|
||||
if (!res.headersSent) {
|
||||
res.status(500).end();
|
||||
@ -241,14 +210,6 @@ export const getRecordingMedia = async (req: Request, res: Response) => {
|
||||
fileStream.destroy();
|
||||
}
|
||||
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
logger.error(`Error streaming recording: ${error.message}`);
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
logger.error(`Unexpected error streaming recording ${recordingId}: ${error}`);
|
||||
return res
|
||||
.status(500)
|
||||
.json({ name: 'Recording Error', message: 'An unexpected error occurred while processing the recording' });
|
||||
handleError(res, error, `streaming recording '${recordingId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -3,7 +3,7 @@ import { Request, Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
import { MEET_RECORDING_TOKEN_EXPIRATION } from '../environment.js';
|
||||
import { OpenViduMeetError } from '../models/error.model.js';
|
||||
import { handleError } from '../models/error.model.js';
|
||||
import { LoggerService, ParticipantService, RoomService } from '../services/index.js';
|
||||
import { getCookieOptions } from '../utils/cookie-utils.js';
|
||||
|
||||
@ -20,8 +20,7 @@ export const createRoom = async (req: Request, res: Response) => {
|
||||
res.set('Location', `${baseUrl}${INTERNAL_CONFIG.API_BASE_PATH_V1}/rooms/${room.roomId}`);
|
||||
return res.status(201).json(room);
|
||||
} catch (error) {
|
||||
logger.error(`Error creating room with options '${JSON.stringify(options)}'`);
|
||||
handleError(res, error);
|
||||
handleError(res, error, 'creating room');
|
||||
}
|
||||
};
|
||||
|
||||
@ -37,8 +36,7 @@ export const getRooms = async (req: Request, res: Response) => {
|
||||
const maxItems = Number(queryParams.maxItems);
|
||||
return res.status(200).json({ rooms, pagination: { isTruncated, nextPageToken, maxItems } });
|
||||
} catch (error) {
|
||||
logger.error('Error getting rooms');
|
||||
handleError(res, error);
|
||||
handleError(res, error, 'getting rooms');
|
||||
}
|
||||
};
|
||||
|
||||
@ -49,15 +47,14 @@ export const getRoom = async (req: Request, res: Response) => {
|
||||
const fields = req.query.fields as string | undefined;
|
||||
|
||||
try {
|
||||
logger.verbose(`Getting room with id '${roomId}'`);
|
||||
logger.verbose(`Getting room '${roomId}'`);
|
||||
|
||||
const roomService = container.get(RoomService);
|
||||
const room = await roomService.getMeetRoom(roomId, fields);
|
||||
|
||||
return res.status(200).json(room);
|
||||
} catch (error) {
|
||||
logger.error(`Error getting room with id '${roomId}'`);
|
||||
handleError(res, error);
|
||||
handleError(res, error, `getting room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -70,7 +67,7 @@ export const deleteRoom = async (req: Request, res: Response) => {
|
||||
const forceDelete = force === 'true';
|
||||
|
||||
try {
|
||||
logger.verbose(`Deleting room: ${roomId}`);
|
||||
logger.verbose(`Deleting room '${roomId}'`);
|
||||
|
||||
const { deleted } = await roomService.bulkDeleteRooms([roomId], forceDelete);
|
||||
|
||||
@ -80,10 +77,9 @@ export const deleteRoom = async (req: Request, res: Response) => {
|
||||
}
|
||||
|
||||
// Room was marked as deleted
|
||||
return res.status(202).json({ message: `Room ${roomId} marked as deleted` });
|
||||
return res.status(202).json({ message: `Room '${roomId}' marked as deleted` });
|
||||
} catch (error) {
|
||||
logger.error(`Error deleting room: ${roomId}`);
|
||||
handleError(res, error);
|
||||
handleError(res, error, `deleting room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -110,8 +106,8 @@ export const bulkDeleteRooms = async (req: Request, res: Response) => {
|
||||
if (deleted.length === 0 && markedForDeletion.length > 0) {
|
||||
const message =
|
||||
markedForDeletion.length === 1
|
||||
? `Room ${markedForDeletion[0]} marked for deletion`
|
||||
: `Rooms ${markedForDeletion.join(', ')} marked for deletion`;
|
||||
? `Room '${markedForDeletion[0]}' marked for deletion`
|
||||
: `Rooms '${markedForDeletion.join(', ')}' marked for deletion`;
|
||||
|
||||
return res.status(202).json({ message });
|
||||
}
|
||||
@ -119,8 +115,7 @@ export const bulkDeleteRooms = async (req: Request, res: Response) => {
|
||||
// Mixed result (some rooms deleted, some marked for deletion)
|
||||
return res.status(200).json({ deleted, markedForDeletion });
|
||||
} catch (error) {
|
||||
logger.error(`Error deleting rooms: ${error}`);
|
||||
handleError(res, error);
|
||||
handleError(res, error, `deleting rooms`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -130,14 +125,13 @@ export const updateRoomPreferences = async (req: Request, res: Response) => {
|
||||
const roomPreferences = req.body;
|
||||
const { roomId } = req.params;
|
||||
|
||||
logger.verbose(`Updating room preferences`);
|
||||
logger.verbose(`Updating room preferences for room '${roomId}'`);
|
||||
|
||||
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);
|
||||
handleError(res, error, `updating room preferences for room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -159,8 +153,7 @@ export const generateRecordingToken = async (req: Request, res: Response) => {
|
||||
);
|
||||
return res.status(200).json({ token });
|
||||
} catch (error) {
|
||||
logger.error(`Error generating recording token for room '${roomId}'`);
|
||||
handleError(res, error);
|
||||
handleError(res, error, `generating recording token for room '${roomId}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -175,8 +168,7 @@ export const getRoomRolesAndPermissions = async (req: Request, res: Response) =>
|
||||
try {
|
||||
await roomService.getMeetRoom(roomId);
|
||||
} catch (error) {
|
||||
logger.error(`Error getting room '${roomId}'`);
|
||||
return handleError(res, error);
|
||||
return handleError(res, error, `getting room '${roomId}'`);
|
||||
}
|
||||
|
||||
logger.verbose(`Getting roles and associated permissions for room '${roomId}'`);
|
||||
@ -214,18 +206,6 @@ export const getRoomRoleAndPermissions = async (req: Request, res: Response) =>
|
||||
};
|
||||
return res.status(200).json(roleAndPermissions);
|
||||
} catch (error) {
|
||||
logger.error(`Error getting room role and permissions for room '${roomId}' and secret '${secret}'`);
|
||||
handleError(res, error);
|
||||
}
|
||||
};
|
||||
|
||||
const handleError = (res: Response, error: OpenViduMeetError | unknown) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.error(String(error));
|
||||
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
} else {
|
||||
res.status(500).json({ name: 'Room Error', message: 'Internal server error. Room operation failed' });
|
||||
handleError(res, error, `getting room role and permissions for room '${roomId}' and secret '${secret}'`);
|
||||
}
|
||||
};
|
||||
|
||||
@ -12,7 +12,9 @@ import {
|
||||
errorInvalidToken,
|
||||
errorInvalidTokenSubject,
|
||||
errorUnauthorized,
|
||||
OpenViduMeetError
|
||||
internalError,
|
||||
OpenViduMeetError,
|
||||
rejectRequestFromMeetError
|
||||
} from '../models/index.js';
|
||||
import { LoggerService, TokenService, UserService } from '../services/index.js';
|
||||
|
||||
@ -42,10 +44,11 @@ export const withAuth = (...validators: ((req: Request) => Promise<void>)[]): Re
|
||||
}
|
||||
|
||||
if (lastError) {
|
||||
return res.status(lastError.statusCode).json({ message: lastError.message });
|
||||
return rejectRequestFromMeetError(res, lastError);
|
||||
}
|
||||
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
const error = internalError('authenticating user');
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
};
|
||||
};
|
||||
|
||||
@ -94,10 +97,7 @@ export const recordingTokenValidator = async (req: Request) => {
|
||||
await validateTokenAndSetSession(req, INTERNAL_CONFIG.RECORDING_TOKEN_COOKIE_NAME);
|
||||
};
|
||||
|
||||
const validateTokenAndSetSession = async (
|
||||
req: Request,
|
||||
cookieName: string
|
||||
) => {
|
||||
const validateTokenAndSetSession = async (req: Request, cookieName: string) => {
|
||||
const token = req.cookies[cookieName];
|
||||
|
||||
if (!token) {
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { errorMalformedBody, errorUnsupportedMediaType, rejectRequestFromMeetError } from '../models/error.model.js';
|
||||
|
||||
export const mediaTypeValidatorMiddleware = (req: Request, res: Response, next: NextFunction) => {
|
||||
if (req.method === 'GET') {
|
||||
@ -9,9 +10,8 @@ export const mediaTypeValidatorMiddleware = (req: Request, res: Response, next:
|
||||
const contentType = req.headers['content-type'];
|
||||
|
||||
if (!contentType || !supportedMediaTypes.includes(contentType)) {
|
||||
return res.status(415).json({
|
||||
error: `Unsupported Media Type. Supported types: ${supportedMediaTypes.join(', ')}`
|
||||
});
|
||||
const error = errorUnsupportedMediaType(supportedMediaTypes);
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
next();
|
||||
@ -29,12 +29,9 @@ export const mediaTypeValidatorMiddleware = (req: Request, res: Response, next:
|
||||
* @param next - Express next function to continue to the next middleware
|
||||
*/
|
||||
export const jsonSyntaxErrorHandler = (err: any, req: Request, res: Response, next: NextFunction): void => {
|
||||
// This middleware handles JSON syntax errors
|
||||
if (err instanceof SyntaxError && (err as any).status === 400 && 'body' in err) {
|
||||
res.status(400).json({
|
||||
error: 'Bad Request',
|
||||
message: 'Malformed Body'
|
||||
});
|
||||
const error = errorMalformedBody();
|
||||
rejectRequestFromMeetError(res, error);
|
||||
} else {
|
||||
next(err);
|
||||
}
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import { AuthMode, ParticipantOptions, ParticipantRole, UserRole } from '@typings-ce';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import { OpenViduMeetError } from '../models/error.model.js';
|
||||
import { LoggerService, MeetStorageService, RoomService } from '../services/index.js';
|
||||
import { errorInsufficientPermissions, handleError, rejectRequestFromMeetError } from '../models/error.model.js';
|
||||
import { MeetStorageService, RoomService } from '../services/index.js';
|
||||
import { allowAnonymous, tokenAndRoleValidator, withAuth } from './auth.middleware.js';
|
||||
|
||||
/**
|
||||
@ -13,7 +13,6 @@ import { allowAnonymous, tokenAndRoleValidator, withAuth } from './auth.middlewa
|
||||
* - Otherwise, allow anonymous access.
|
||||
*/
|
||||
export const configureParticipantTokenAuth = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const globalPrefService = container.get(MeetStorageService);
|
||||
const roomService = container.get(RoomService);
|
||||
|
||||
@ -23,16 +22,7 @@ export const configureParticipantTokenAuth = async (req: Request, res: Response,
|
||||
const { roomId, secret } = req.body as ParticipantOptions;
|
||||
role = await roomService.getRoomRoleBySecret(roomId, secret);
|
||||
} catch (error) {
|
||||
logger.error('Error getting room role by secret', error);
|
||||
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
} else {
|
||||
return res.status(500).json({
|
||||
name: 'Participant Error',
|
||||
message: 'Internal server error. Participant operation failed'
|
||||
});
|
||||
}
|
||||
return handleError(res, error, 'getting room role by secret');
|
||||
}
|
||||
|
||||
let authMode: AuthMode;
|
||||
@ -41,8 +31,7 @@ export const configureParticipantTokenAuth = async (req: Request, res: Response,
|
||||
const { securityPreferences } = await globalPrefService.getGlobalPreferences();
|
||||
authMode = securityPreferences.authentication.authMode;
|
||||
} catch (error) {
|
||||
logger.error('Error checking authentication preferences', error);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
return handleError(res, error, 'checking authentication preferences');
|
||||
}
|
||||
|
||||
const authValidators = [];
|
||||
@ -68,7 +57,8 @@ export const withModeratorPermissions = async (req: Request, res: Response, next
|
||||
const payload = req.session?.tokenClaims;
|
||||
|
||||
if (!payload) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
const sameRoom = payload.video?.room === roomId;
|
||||
@ -76,7 +66,8 @@ export const withModeratorPermissions = async (req: Request, res: Response, next
|
||||
const role = metadata.role as ParticipantRole;
|
||||
|
||||
if (!sameRoom || role !== ParticipantRole.MODERATOR) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
return next();
|
||||
|
||||
@ -2,9 +2,21 @@ import { MeetRecordingAccess, MeetRoom, OpenViduMeetPermissions, RecordingPermis
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import { RecordingHelper } from '../helpers/index.js';
|
||||
import { OpenViduMeetError } from '../models/error.model.js';
|
||||
import {
|
||||
errorInsufficientPermissions,
|
||||
errorRecordingDisabled,
|
||||
errorRoomMetadataNotFound,
|
||||
handleError,
|
||||
rejectRequestFromMeetError
|
||||
} from '../models/error.model.js';
|
||||
import { LoggerService, MeetStorageService, RoomService } from '../services/index.js';
|
||||
import { allowAnonymous, apiKeyValidator, recordingTokenValidator, tokenAndRoleValidator, withAuth } from './auth.middleware.js';
|
||||
import {
|
||||
allowAnonymous,
|
||||
apiKeyValidator,
|
||||
recordingTokenValidator,
|
||||
tokenAndRoleValidator,
|
||||
withAuth
|
||||
} from './auth.middleware.js';
|
||||
|
||||
export const withRecordingEnabled = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger = container.get(LoggerService);
|
||||
@ -15,23 +27,14 @@ export const withRecordingEnabled = async (req: Request, res: Response, next: Ne
|
||||
const room: MeetRoom = await roomService.getMeetRoom(roomId!);
|
||||
|
||||
if (!room.preferences?.recordingPreferences?.enabled) {
|
||||
logger.debug(`Recording is disabled for room ${roomId}`);
|
||||
return res.status(403).json({
|
||||
message: 'Recording is disabled in this room'
|
||||
});
|
||||
logger.debug(`Recording is disabled for room '${roomId}'`);
|
||||
const error = errorRecordingDisabled(roomId!);
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
return next();
|
||||
} catch (error) {
|
||||
logger.error(`Error checking recording preferences: ${error}`);
|
||||
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
}
|
||||
|
||||
return res.status(500).json({
|
||||
message: 'Unexpected error checking recording preferences'
|
||||
});
|
||||
handleError(res, error, 'checking recording preferences');
|
||||
}
|
||||
};
|
||||
|
||||
@ -40,7 +43,8 @@ export const withCanRecordPermission = async (req: Request, res: Response, next:
|
||||
const payload = req.session?.tokenClaims;
|
||||
|
||||
if (!payload) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
const sameRoom = payload.video?.room === roomId;
|
||||
@ -49,7 +53,8 @@ export const withCanRecordPermission = async (req: Request, res: Response, next:
|
||||
const canRecord = permissions?.canRecord;
|
||||
|
||||
if (!sameRoom || !canRecord) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
return next();
|
||||
@ -72,7 +77,8 @@ export const withCanRetrieveRecordingsPermission = async (req: Request, res: Res
|
||||
const canRetrieveRecordings = permissions?.canRetrieveRecordings;
|
||||
|
||||
if (!sameRoom || !canRetrieveRecordings) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
return next();
|
||||
@ -94,7 +100,8 @@ export const withCanDeleteRecordingsPermission = async (req: Request, res: Respo
|
||||
const canDeleteRecordings = permissions?.canDeleteRecordings;
|
||||
|
||||
if (!sameRoom || !canDeleteRecordings) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
return next();
|
||||
@ -107,7 +114,6 @@ export const withCanDeleteRecordingsPermission = async (req: Request, res: Respo
|
||||
* - If recording access is public, anonymous users are allowed
|
||||
*/
|
||||
export const configureRecordingMediaAuth = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const storageService = container.get(MeetStorageService);
|
||||
|
||||
let recordingAccess: MeetRecordingAccess;
|
||||
@ -117,17 +123,13 @@ export const configureRecordingMediaAuth = async (req: Request, res: Response, n
|
||||
const room = await storageService.getArchivedRoomMetadata(roomId!);
|
||||
|
||||
if (!room) {
|
||||
return res.status(404).json({
|
||||
message: 'Room metadata associated with the recording not found'
|
||||
});
|
||||
const error = errorRoomMetadataNotFound(roomId!);
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
recordingAccess = room.preferences!.recordingPreferences.allowAccessTo;
|
||||
} catch (error) {
|
||||
logger.error(`Error checking recording permissions: ${error}`);
|
||||
return res.status(500).json({
|
||||
message: 'Unexpected error checking recording permissions'
|
||||
});
|
||||
return handleError(res, error, 'checking recording permissions');
|
||||
}
|
||||
|
||||
const authValidators = [apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN), recordingTokenValidator];
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { rejectUnprocessableRequest } from '../../models/error.model.js';
|
||||
|
||||
const LoginRequestSchema = z.object({
|
||||
username: z.string().min(4, 'Username must be at least 4 characters long'),
|
||||
@ -10,16 +11,7 @@ export const validateLoginRequest = (req: Request, res: Response, next: NextFunc
|
||||
const { success, error, data } = LoginRequestSchema.safeParse(req.body);
|
||||
|
||||
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',
|
||||
details: errors
|
||||
});
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { ParticipantOptions } from '@typings-ce';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { rejectUnprocessableRequest } from '../../models/error.model.js';
|
||||
import { nonEmptySanitizedRoomId } from './room-validator.middleware.js';
|
||||
|
||||
const ParticipantTokenRequestSchema: z.ZodType<ParticipantOptions> = z.object({
|
||||
@ -13,24 +14,9 @@ export const validateParticipantTokenRequest = (req: Request, res: Response, nex
|
||||
const { success, error, data } = ParticipantTokenRequestSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
next();
|
||||
};
|
||||
|
||||
const rejectRequest = (res: Response, error: z.ZodError) => {
|
||||
const errors = error.errors.map((error) => ({
|
||||
field: error.path.join('.'),
|
||||
message: error.message
|
||||
}));
|
||||
|
||||
console.log(errors);
|
||||
|
||||
return res.status(422).json({
|
||||
error: 'Unprocessable Entity',
|
||||
message: 'Invalid request',
|
||||
details: errors
|
||||
});
|
||||
};
|
||||
|
||||
@ -10,6 +10,7 @@ import {
|
||||
} from '@typings-ce';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { rejectUnprocessableRequest } from '../../models/error.model.js';
|
||||
|
||||
const WebhookPreferencesSchema: z.ZodType<WebhookPreferences> = z.object({
|
||||
enabled: z.boolean(),
|
||||
@ -49,7 +50,7 @@ export const validateWebhookPreferences = (req: Request, res: Response, next: Ne
|
||||
const { success, error, data } = WebhookPreferencesSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
@ -60,22 +61,9 @@ export const validateSecurityPreferences = (req: Request, res: Response, next: N
|
||||
const { success, error, data } = UpdateSecurityPreferencesDTOSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
next();
|
||||
};
|
||||
|
||||
const rejectRequest = (res: Response, error: z.ZodError) => {
|
||||
const errors = error.errors.map((error) => ({
|
||||
field: error.path.join('.'),
|
||||
message: error.message
|
||||
}));
|
||||
|
||||
return res.status(422).json({
|
||||
error: 'Unprocessable Entity',
|
||||
message: 'Invalid request',
|
||||
details: errors
|
||||
});
|
||||
};
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import { MeetRecordingFilters } from '@typings-ce';
|
||||
import { NextFunction, Request, Response } from 'express';
|
||||
import { z } from 'zod';
|
||||
import { rejectUnprocessableRequest } from '../../models/error.model.js';
|
||||
import { nonEmptySanitizedRoomId } from './room-validator.middleware.js';
|
||||
|
||||
const nonEmptySanitizedRecordingId = (fieldName: string) =>
|
||||
@ -114,7 +115,7 @@ export const withValidStartRecordingRequest = (req: Request, res: Response, next
|
||||
const { success, error, data } = StartRecordingRequestSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
@ -125,7 +126,7 @@ export const withValidRecordingId = (req: Request, res: Response, next: NextFunc
|
||||
const { success, error, data } = GetRecordingSchema.safeParse({ recordingId: req.params.recordingId });
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.params.recordingId = data.recordingId;
|
||||
@ -136,7 +137,7 @@ export const withValidRecordingFiltersRequest = (req: Request, res: Response, ne
|
||||
const { success, error, data } = GetRecordingsFiltersSchema.safeParse(req.query);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.query = {
|
||||
@ -150,7 +151,7 @@ export const withValidRecordingBulkDeleteRequest = (req: Request, res: Response,
|
||||
const { success, error, data } = BulkDeleteRecordingsSchema.safeParse(req.query);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.query.recordingIds = data.recordingIds.join(',');
|
||||
@ -164,23 +165,10 @@ export const withValidGetMediaRequest = (req: Request, res: Response, next: Next
|
||||
});
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.params.recordingId = data.params.recordingId;
|
||||
req.headers.range = data.headers.range;
|
||||
next();
|
||||
};
|
||||
|
||||
const rejectRequest = (res: Response, error: z.ZodError) => {
|
||||
const errors = error.errors.map((error) => ({
|
||||
field: error.path.join('.'),
|
||||
message: error.message
|
||||
}));
|
||||
|
||||
return res.status(422).json({
|
||||
error: 'Unprocessable Entity',
|
||||
message: 'Invalid request',
|
||||
details: errors
|
||||
});
|
||||
};
|
||||
|
||||
@ -11,6 +11,7 @@ import { NextFunction, Request, Response } from 'express';
|
||||
import ms from 'ms';
|
||||
import { z } from 'zod';
|
||||
import INTERNAL_CONFIG from '../../config/internal-config.js';
|
||||
import { rejectUnprocessableRequest } from '../../models/error.model.js';
|
||||
|
||||
/**
|
||||
* Sanitizes an identifier by removing/replacing invalid characters
|
||||
@ -171,7 +172,7 @@ export const withValidRoomOptions = (req: Request, res: Response, next: NextFunc
|
||||
const { success, error, data } = RoomRequestOptionsSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
@ -182,14 +183,13 @@ export const withValidRoomFiltersRequest = (req: Request, res: Response, next: N
|
||||
const { success, error, data } = GetRoomFiltersSchema.safeParse(req.query);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.query = {
|
||||
...data,
|
||||
maxItems: data.maxItems?.toString()
|
||||
};
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
@ -197,7 +197,7 @@ export const withValidRoomPreferences = (req: Request, res: Response, next: Next
|
||||
const { success, error, data } = RoomPreferencesSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
@ -209,7 +209,7 @@ export const withValidRoomId = (req: Request, res: Response, next: NextFunction)
|
||||
|
||||
if (!success) {
|
||||
error.errors[0].path = ['roomId'];
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.params.roomId = data;
|
||||
@ -220,7 +220,7 @@ export const withValidRoomBulkDeleteRequest = (req: Request, res: Response, next
|
||||
const { success, error, data } = BulkDeleteRoomsSchema.safeParse(req.query);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.query.roomIds = data.roomIds as any;
|
||||
@ -232,7 +232,7 @@ export const withValidRoomDeleteRequest = (req: Request, res: Response, next: Ne
|
||||
const roomIdResult = nonEmptySanitizedRoomId('roomId').safeParse(req.params.roomId);
|
||||
|
||||
if (!roomIdResult.success) {
|
||||
return rejectRequest(res, roomIdResult.error);
|
||||
return rejectUnprocessableRequest(res, roomIdResult.error);
|
||||
}
|
||||
|
||||
req.params.roomId = roomIdResult.data;
|
||||
@ -240,11 +240,10 @@ export const withValidRoomDeleteRequest = (req: Request, res: Response, next: Ne
|
||||
const forceResult = validForceQueryParam().safeParse(req.query.force);
|
||||
|
||||
if (!forceResult.success) {
|
||||
return rejectRequest(res, forceResult.error);
|
||||
return rejectUnprocessableRequest(res, forceResult.error);
|
||||
}
|
||||
|
||||
req.query.force = forceResult.data ? 'true' : 'false';
|
||||
|
||||
next();
|
||||
};
|
||||
|
||||
@ -252,22 +251,9 @@ export const withValidRoomSecret = (req: Request, res: Response, next: NextFunct
|
||||
const { success, error, data } = RecordingTokenRequestSchema.safeParse(req.body);
|
||||
|
||||
if (!success) {
|
||||
return rejectRequest(res, error);
|
||||
return rejectUnprocessableRequest(res, error);
|
||||
}
|
||||
|
||||
req.body = data;
|
||||
next();
|
||||
};
|
||||
|
||||
const rejectRequest = (res: Response, error: z.ZodError) => {
|
||||
const errors = error.errors.map((error) => ({
|
||||
field: error.path.join('.'),
|
||||
message: error.message
|
||||
}));
|
||||
|
||||
return res.status(422).json({
|
||||
error: 'Unprocessable Entity',
|
||||
message: 'Invalid request',
|
||||
details: errors
|
||||
});
|
||||
};
|
||||
|
||||
@ -3,10 +3,11 @@ import { NextFunction, Request, Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import {
|
||||
errorInsufficientPermissions,
|
||||
errorRoomNotFoundOrEmptyRecordings,
|
||||
OpenViduMeetError
|
||||
errorRoomMetadataNotFound,
|
||||
handleError,
|
||||
rejectRequestFromMeetError
|
||||
} from '../models/error.model.js';
|
||||
import { LoggerService, MeetStorageService, RoomService } from '../services/index.js';
|
||||
import { MeetStorageService, RoomService } from '../services/index.js';
|
||||
import { allowAnonymous, apiKeyValidator, tokenAndRoleValidator, withAuth } from './auth.middleware.js';
|
||||
|
||||
/**
|
||||
@ -17,7 +18,6 @@ import { allowAnonymous, apiKeyValidator, tokenAndRoleValidator, withAuth } from
|
||||
* - If room creation is allowed and does not require authentication, anonymous users are allowed.
|
||||
*/
|
||||
export const configureCreateRoomAuth = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const globalPrefService = container.get(MeetStorageService);
|
||||
let allowRoomCreation: boolean;
|
||||
let requireAuthentication: boolean;
|
||||
@ -26,8 +26,7 @@ export const configureCreateRoomAuth = async (req: Request, res: Response, next:
|
||||
const { securityPreferences } = await globalPrefService.getGlobalPreferences();
|
||||
({ allowRoomCreation, requireAuthentication } = securityPreferences.roomCreationPolicy);
|
||||
} catch (error) {
|
||||
logger.error('Error checking room creation policy:' + error);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
return handleError(res, error, 'checking room creation policy');
|
||||
}
|
||||
|
||||
const authValidators = [apiKeyValidator, tokenAndRoleValidator(UserRole.ADMIN)];
|
||||
@ -70,10 +69,10 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne
|
||||
const role = metadata.role as ParticipantRole;
|
||||
|
||||
if (!sameRoom) {
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
}
|
||||
|
||||
const logger = container.get(LoggerService);
|
||||
const globalPrefService = container.get(MeetStorageService);
|
||||
let authMode: AuthMode;
|
||||
|
||||
@ -81,8 +80,7 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne
|
||||
const { securityPreferences } = await globalPrefService.getGlobalPreferences();
|
||||
authMode = securityPreferences.authentication.authMode;
|
||||
} catch (error) {
|
||||
logger.error('Error checking authentication preferences', error);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
return handleError(res, error, 'checking authentication preferences');
|
||||
}
|
||||
|
||||
// If the user is a moderator, it is necessary to add the user role validator
|
||||
@ -96,7 +94,8 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne
|
||||
}
|
||||
|
||||
// If the user is not a moderator, it is not allowed to access the resource
|
||||
return res.status(403).json({ message: 'Insufficient permissions to access this resource' });
|
||||
const error = errorInsufficientPermissions();
|
||||
return rejectRequestFromMeetError(res, error);
|
||||
};
|
||||
|
||||
/**
|
||||
@ -108,7 +107,6 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne
|
||||
* - Otherwise, allow anonymous access.
|
||||
*/
|
||||
export const configureRecordingTokenAuth = async (req: Request, res: Response, next: NextFunction) => {
|
||||
const logger = container.get(LoggerService);
|
||||
const storageService = container.get(MeetStorageService);
|
||||
const roomService = container.get(RoomService);
|
||||
|
||||
@ -121,7 +119,7 @@ export const configureRecordingTokenAuth = async (req: Request, res: Response, n
|
||||
|
||||
if (!room) {
|
||||
// If the room is not found, it means that there are no recordings for that room or the room doesn't exist
|
||||
throw errorRoomNotFoundOrEmptyRecordings(roomId);
|
||||
throw errorRoomMetadataNotFound(roomId);
|
||||
}
|
||||
|
||||
const recordingAccess = room.preferences!.recordingPreferences.allowAccessTo;
|
||||
@ -133,16 +131,7 @@ export const configureRecordingTokenAuth = async (req: Request, res: Response, n
|
||||
|
||||
role = roomService.getRoomRoleBySecretFromRoom(room as MeetRoom, secret);
|
||||
} catch (error) {
|
||||
logger.error('Error getting room role by secret', error);
|
||||
|
||||
if (error instanceof OpenViduMeetError) {
|
||||
return res.status(error.statusCode).json({ name: error.name, message: error.message });
|
||||
} else {
|
||||
return res.status(500).json({
|
||||
name: 'Room Error',
|
||||
message: 'Internal server error. Room operation failed'
|
||||
});
|
||||
}
|
||||
return handleError(res, error, 'getting room role by secret');
|
||||
}
|
||||
|
||||
let authMode: AuthMode;
|
||||
@ -151,8 +140,7 @@ export const configureRecordingTokenAuth = async (req: Request, res: Response, n
|
||||
const { securityPreferences } = await storageService.getGlobalPreferences();
|
||||
authMode = securityPreferences.authentication.authMode;
|
||||
} catch (error) {
|
||||
logger.error('Error checking authentication preferences', error);
|
||||
return res.status(500).json({ message: 'Internal server error' });
|
||||
return handleError(res, error, 'checking authentication preferences');
|
||||
}
|
||||
|
||||
const authValidators = [];
|
||||
|
||||
@ -1,7 +1,13 @@
|
||||
type StatusError = 400 | 401 | 403 | 404 | 406 | 409 | 416 | 422 | 500 | 503;
|
||||
import { Response } from 'express';
|
||||
import { container } from '../config/index.js';
|
||||
import { LoggerService } from '../services/index.js';
|
||||
import { z } from 'zod';
|
||||
|
||||
type StatusError = 400 | 401 | 402 | 403 | 404 | 406 | 409 | 415 | 416 | 422 | 500 | 503;
|
||||
export class OpenViduMeetError extends Error {
|
||||
name: string;
|
||||
statusCode: StatusError;
|
||||
|
||||
constructor(error: string, message: string, statusCode: StatusError) {
|
||||
super(message);
|
||||
this.name = error;
|
||||
@ -9,9 +15,42 @@ export class OpenViduMeetError extends Error {
|
||||
}
|
||||
}
|
||||
|
||||
interface ErrorResponse {
|
||||
error: string;
|
||||
message: string;
|
||||
details?: {
|
||||
field: string;
|
||||
message: string;
|
||||
}[];
|
||||
}
|
||||
|
||||
// General errors
|
||||
|
||||
export const errorLivekitIsNotAvailable = (): OpenViduMeetError => {
|
||||
export const errorMalformedBody = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Bad Request', 'Malformed body', 400);
|
||||
};
|
||||
|
||||
export const errorProFeature = (operation: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError(
|
||||
'Pro Feature Error',
|
||||
`The operation '${operation}' is a PRO feature. Please, upgrade to OpenVidu PRO`,
|
||||
402
|
||||
);
|
||||
};
|
||||
|
||||
export const errorUnsupportedMediaType = (supportedTypes: string[]): OpenViduMeetError => {
|
||||
return new OpenViduMeetError(
|
||||
'Unsupported Media Type',
|
||||
`Unsupported media type. Supported types: ${supportedTypes.join(', ')}`,
|
||||
415
|
||||
);
|
||||
};
|
||||
|
||||
export const internalError = (operationDescription: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Interal Server Error', `Unexpected error while ${operationDescription}`, 500);
|
||||
};
|
||||
|
||||
export const errorLivekitNotAvailable = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('LiveKit Error', 'LiveKit is not available', 503);
|
||||
};
|
||||
|
||||
@ -19,42 +58,46 @@ export const errorS3NotAvailable = (error: any): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('S3 Error', `S3 is not available ${error}`, 503);
|
||||
};
|
||||
|
||||
export const internalError = (error: any): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Unexpected error', `Something went wrong ${error}`, 500);
|
||||
};
|
||||
|
||||
export const errorRequest = (error: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Wrong request', `Problem with some body parameter. ${error}`, 400);
|
||||
};
|
||||
|
||||
export const errorUnprocessableParams = (error: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Wrong request', `Some parameters are not valid. ${error}`, 422);
|
||||
};
|
||||
|
||||
// Auth errors
|
||||
|
||||
export const errorInvalidCredentials = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Login Error', 'Invalid username or password', 404);
|
||||
};
|
||||
|
||||
export const errorUnauthorized = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Authentication error', 'Unauthorized', 401);
|
||||
return new OpenViduMeetError('Authentication Error', 'Unauthorized', 401);
|
||||
};
|
||||
|
||||
export const errorInvalidToken = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Authentication error', 'Invalid token', 401);
|
||||
return new OpenViduMeetError('Authentication Error', 'Invalid token', 401);
|
||||
};
|
||||
|
||||
export const errorInvalidTokenSubject = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Authentication error', 'Invalid token subject', 403);
|
||||
return new OpenViduMeetError('Authorization Error', 'Invalid token subject', 403);
|
||||
};
|
||||
|
||||
export const errorRefreshTokenNotPresent = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Refresh Token Error', 'No refresh token provided', 400);
|
||||
};
|
||||
|
||||
export const errorInvalidRefreshToken = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Refresh Token Error', 'Invalid refresh token', 400);
|
||||
};
|
||||
|
||||
export const errorInsufficientPermissions = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Authentication error', 'You do not have permission to access this resource', 403);
|
||||
return new OpenViduMeetError('Authorization Error', 'You do not have permission to access this resource', 403);
|
||||
};
|
||||
|
||||
export const errorInvalidApiKey = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Authentication error', 'Invalid API key', 401);
|
||||
return new OpenViduMeetError('Authentication Error', 'Invalid API key', 401);
|
||||
};
|
||||
|
||||
// Recording errors
|
||||
|
||||
export const errorRecordingDisabled = (roomId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Recording Error', `Recording is disabled for room '${roomId}'`, 403);
|
||||
};
|
||||
|
||||
export const errorRecordingNotFound = (recordingId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Recording Error', `Recording '${recordingId}' not found`, 404);
|
||||
};
|
||||
@ -72,7 +115,7 @@ export const errorRecordingCannotBeStoppedWhileStarting = (recordingId: string):
|
||||
};
|
||||
|
||||
export const errorRecordingAlreadyStarted = (roomId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Recording Error', `The room '${roomId}' is already being recorded`, 409);
|
||||
return new OpenViduMeetError('Recording Error', `Room '${roomId}' is already being recorded`, 409);
|
||||
};
|
||||
|
||||
export const errorRecordingStartTimeout = (roomId: string): OpenViduMeetError => {
|
||||
@ -88,7 +131,7 @@ export const errorRecordingRangeNotSatisfiable = (recordingId: string, fileSize:
|
||||
};
|
||||
|
||||
export const errorRoomHasNoParticipants = (roomId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Recording Error', `The room '${roomId}' has no participants`, 409);
|
||||
return new OpenViduMeetError('Recording Error', `Room '${roomId}' has no participants`, 409);
|
||||
};
|
||||
|
||||
const isMatchingError = (error: OpenViduMeetError, originalError: OpenViduMeetError): boolean => {
|
||||
@ -118,23 +161,74 @@ export const isErrorRecordingCannotBeStoppedWhileStarting = (
|
||||
// Room errors
|
||||
|
||||
export const errorRoomNotFound = (roomId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Room Error', `The room '${roomId}' does not exist`, 404);
|
||||
return new OpenViduMeetError('Room Error', `Room '${roomId}' does not exist`, 404);
|
||||
};
|
||||
|
||||
export const errorRoomNotFoundOrEmptyRecordings = (roomId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Room Error', `The room '${roomId}' does not exist or has no recordings`, 404);
|
||||
export const errorRoomMetadataNotFound = (roomId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError(
|
||||
'Room Error',
|
||||
`Room metadata for '${roomId}' not found. Room '${roomId}' does not exist or has no recordings associated`,
|
||||
404
|
||||
);
|
||||
};
|
||||
|
||||
export const errorInvalidRoomSecret = (roomId: string, secret: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Room Error', `The secret '${secret}' is not recognized for room '${roomId}'`, 400);
|
||||
return new OpenViduMeetError('Room Error', `Secret '${secret}' is not recognized for room '${roomId}'`, 400);
|
||||
};
|
||||
|
||||
// Participant errors
|
||||
|
||||
export const errorParticipantNotFound = (participantName: string, roomId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Participant Error', `'${participantName}' not found in room '${roomId}'`, 404);
|
||||
return new OpenViduMeetError(
|
||||
'Participant Error',
|
||||
`Participant '${participantName}' not found in room '${roomId}'`,
|
||||
404
|
||||
);
|
||||
};
|
||||
|
||||
export const errorParticipantAlreadyExists = (participantName: string, roomId: string): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Room Error', `'${participantName}' already exists in room in ${roomId}`, 409);
|
||||
return new OpenViduMeetError(
|
||||
'Participant Error',
|
||||
`Participant '${participantName}' already exists in room '${roomId}'`,
|
||||
409
|
||||
);
|
||||
};
|
||||
|
||||
export const errorParticipantTokenStillValid = (): OpenViduMeetError => {
|
||||
return new OpenViduMeetError('Participant Error', 'Participant token is still valid', 409);
|
||||
};
|
||||
|
||||
// Handlers
|
||||
|
||||
export const handleError = (res: Response, error: OpenViduMeetError | unknown, operationDescription: string) => {
|
||||
const logger = container.get(LoggerService);
|
||||
logger.error(`Error while ${operationDescription}: ${error}`);
|
||||
|
||||
if (!(error instanceof OpenViduMeetError)) {
|
||||
error = internalError(operationDescription);
|
||||
}
|
||||
|
||||
return rejectRequestFromMeetError(res, error as OpenViduMeetError);
|
||||
};
|
||||
|
||||
export const rejectRequestFromMeetError = (res: Response, error: OpenViduMeetError) => {
|
||||
const errorResponse: ErrorResponse = {
|
||||
error: error.name,
|
||||
message: error.message
|
||||
};
|
||||
return res.status(error.statusCode).json(errorResponse);
|
||||
};
|
||||
|
||||
export const rejectUnprocessableRequest = (res: Response, error: z.ZodError) => {
|
||||
const errorDetails = error.errors.map((error) => ({
|
||||
field: error.path.join('.'),
|
||||
message: error.message
|
||||
}));
|
||||
|
||||
const errorResponse: ErrorResponse = {
|
||||
error: 'Unprocessable Entity',
|
||||
message: 'Invalid request',
|
||||
details: errorDetails
|
||||
};
|
||||
return res.status(422).json(errorResponse);
|
||||
};
|
||||
|
||||
@ -69,9 +69,11 @@ const createApp = () => {
|
||||
// Serve OpenVidu Meet webcomponent bundle file
|
||||
app.get('/meet/v1/openvidu-meet.js', (_req: Request, res: Response) => res.sendFile(webcomponentBundlePath));
|
||||
// Serve OpenVidu Meet index.html file for all non-API routes
|
||||
app.get(/^(?!\/api).*$/, (_req: Request, res: Response) => res.sendFile(indexHtmlPath));
|
||||
app.get(/^(?!.*\/(api|internal-api)\/).*$/, (_req: Request, res: Response) => res.sendFile(indexHtmlPath));
|
||||
// Catch all other routes and return 404
|
||||
app.use((_req: Request, res: Response) => res.status(404).json({ error: 'Not found' }));
|
||||
app.use((_req: Request, res: Response) =>
|
||||
res.status(404).json({ error: 'Path Not Found', message: 'API path not implemented' })
|
||||
);
|
||||
|
||||
return app;
|
||||
};
|
||||
|
||||
@ -17,7 +17,7 @@ import {
|
||||
import { LIVEKIT_API_KEY, LIVEKIT_API_SECRET, LIVEKIT_URL_PRIVATE } from '../environment.js';
|
||||
import { RecordingHelper } from '../helpers/index.js';
|
||||
import {
|
||||
errorLivekitIsNotAvailable,
|
||||
errorLivekitNotAvailable,
|
||||
errorParticipantNotFound,
|
||||
errorRoomNotFound,
|
||||
internalError,
|
||||
@ -41,7 +41,7 @@ export class LiveKitService {
|
||||
return await this.roomClient.createRoom(options);
|
||||
} catch (error) {
|
||||
this.logger.error('Error creating LiveKit room:', error);
|
||||
throw internalError(`Error creating room: ${error}`);
|
||||
throw internalError('creating LiveKit room');
|
||||
}
|
||||
}
|
||||
|
||||
@ -89,8 +89,8 @@ export class LiveKitService {
|
||||
try {
|
||||
rooms = await this.roomClient.listRooms([roomName]);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error getting room ${error}`);
|
||||
throw internalError(`Error getting room: ${error}`);
|
||||
this.logger.error(`Error getting room: ${error}`);
|
||||
throw internalError(`getting LiveKit room '${roomName}'`);
|
||||
}
|
||||
|
||||
if (rooms.length === 0) {
|
||||
@ -104,8 +104,8 @@ export class LiveKitService {
|
||||
try {
|
||||
return await this.roomClient.listRooms();
|
||||
} catch (error) {
|
||||
this.logger.error(`Error getting LiveKit rooms ${error}`);
|
||||
throw internalError(`Error getting rooms: ${error}`);
|
||||
this.logger.error(`Error getting LiveKit rooms: ${error}`);
|
||||
throw internalError('getting LiveKit rooms');
|
||||
}
|
||||
}
|
||||
|
||||
@ -120,8 +120,8 @@ export class LiveKitService {
|
||||
|
||||
await this.roomClient.deleteRoom(roomName);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error deleting LiveKit room ${error}`);
|
||||
throw internalError(`Error deleting room: ${error}`);
|
||||
this.logger.error(`Error deleting LiveKit room: ${error}`);
|
||||
throw internalError(`deleting LiveKit room '${roomName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -137,8 +137,8 @@ export class LiveKitService {
|
||||
try {
|
||||
return await this.roomClient.getParticipant(roomName, participantName);
|
||||
} catch (error) {
|
||||
this.logger.warn(`Participant ${participantName} not found in room ${roomName} ${error}`);
|
||||
throw internalError(`Error getting participant: ${error}`);
|
||||
this.logger.warn(`Participant ${participantName} not found in room ${roomName}: ${error}`);
|
||||
throw internalError(`getting participant '${participantName}' in room '${roomName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -154,15 +154,11 @@ export class LiveKitService {
|
||||
|
||||
async sendData(roomName: string, rawData: Record<string, any>, options: SendDataOptions): Promise<void> {
|
||||
try {
|
||||
if (this.roomClient) {
|
||||
const data: Uint8Array = new TextEncoder().encode(JSON.stringify(rawData));
|
||||
await this.roomClient.sendData(roomName, data, DataPacket_Kind.RELIABLE, options);
|
||||
} else {
|
||||
throw internalError(`No RoomServiceClient available`);
|
||||
}
|
||||
const data: Uint8Array = new TextEncoder().encode(JSON.stringify(rawData));
|
||||
await this.roomClient.sendData(roomName, data, DataPacket_Kind.RELIABLE, options);
|
||||
} catch (error) {
|
||||
this.logger.error(`Error sending data ${error}`);
|
||||
throw internalError(`Error sending data: ${error}`);
|
||||
this.logger.error(`Error sending data: ${error}`);
|
||||
throw internalError(`sending data to LiveKit room '${roomName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,8 +170,8 @@ export class LiveKitService {
|
||||
try {
|
||||
return await this.egressClient.startRoomCompositeEgress(roomName, output, options);
|
||||
} catch (error: any) {
|
||||
this.logger.error('Error starting Room Composite Egress');
|
||||
throw internalError(`Error starting Room Composite Egress: ${JSON.stringify(error)}`);
|
||||
this.logger.error('Error starting Room Composite Egress:', error);
|
||||
throw internalError(`starting Room Composite Egress for room '${roomName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -184,8 +180,8 @@ export class LiveKitService {
|
||||
this.logger.info(`Stopping ${egressId} egress`);
|
||||
return await this.egressClient.stopEgress(egressId);
|
||||
} catch (error: any) {
|
||||
this.logger.error(`Error stopping egress: JSON.stringify(error)`);
|
||||
throw internalError(`Error stopping egress: ${error}`);
|
||||
this.logger.error(`Error stopping egress: ${JSON.stringify(error)}`);
|
||||
throw internalError(`stopping egress '${egressId}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +206,7 @@ export class LiveKitService {
|
||||
}
|
||||
|
||||
this.logger.error(`Error getting egress: ${JSON.stringify(error)}`);
|
||||
throw internalError(`Error getting egress: ${error}`);
|
||||
throw internalError(`getting egress '${egressId}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -292,7 +288,7 @@ export class LiveKitService {
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : String(error);
|
||||
this.logger.error(`Error getting in-progress recordings: ${errorMessage}`);
|
||||
throw internalError(`Error getting in-progress recordings: ${errorMessage}`);
|
||||
throw internalError(`getting in-progress egress for room '${roomName}'`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -309,7 +305,7 @@ export class LiveKitService {
|
||||
this.logger.error(error);
|
||||
|
||||
if (error?.cause?.code === 'ECONNREFUSED') {
|
||||
throw errorLivekitIsNotAvailable();
|
||||
throw errorLivekitNotAvailable();
|
||||
}
|
||||
|
||||
return false;
|
||||
|
||||
@ -377,7 +377,7 @@ export class RecordingService {
|
||||
|
||||
if (!fileSize) {
|
||||
this.logger.error(`Error getting file size for recording ${recordingId}`);
|
||||
throw internalError(`Error getting file size for recording ${recordingId}`);
|
||||
throw internalError(`getting file size for recording '${recordingId}'`);
|
||||
}
|
||||
|
||||
if (range) {
|
||||
@ -509,7 +509,7 @@ export class RecordingService {
|
||||
const filename = RecordingHelper.extractFilename(recordingInfo);
|
||||
|
||||
if (!filename) {
|
||||
throw internalError(`Error extracting path from recording ${recordingId}`);
|
||||
throw internalError(`extracting path from recording '${recordingId}'`);
|
||||
}
|
||||
|
||||
const recordingPath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/${filename}`;
|
||||
|
||||
@ -183,7 +183,7 @@ export class RedisService extends EventEmitter {
|
||||
}
|
||||
} catch (error) {
|
||||
this.logger.error('Error getting value from Redis', error);
|
||||
throw internalError(error);
|
||||
throw internalError('getting value from Redis');
|
||||
}
|
||||
}
|
||||
|
||||
@ -234,7 +234,7 @@ export class RedisService extends EventEmitter {
|
||||
|
||||
return this.redisPublisher.del(keys);
|
||||
} catch (error) {
|
||||
throw internalError(`Error deleting key from Redis ${error}`);
|
||||
throw internalError(`deleting key from Redis`);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -14,7 +14,7 @@ import { uid } from 'uid/single';
|
||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||
import { MEET_NAME_ID } from '../environment.js';
|
||||
import { MeetRoomHelper, OpenViduComponentsAdapterHelper, UtilsHelper } from '../helpers/index.js';
|
||||
import { errorInvalidRoomSecret, errorRoomNotFoundOrEmptyRecordings, internalError } from '../models/error.model.js';
|
||||
import { errorInvalidRoomSecret, errorRoomMetadataNotFound, internalError } from '../models/error.model.js';
|
||||
import {
|
||||
IScheduledTask,
|
||||
LiveKitService,
|
||||
@ -212,7 +212,7 @@ export class RoomService {
|
||||
|
||||
if (deleted.length === 0 && markedForDeletion.length === 0) {
|
||||
this.logger.error('No rooms were deleted or marked as deleted.');
|
||||
throw internalError('No rooms were deleted or marked as deleted.');
|
||||
throw internalError('while deleting rooms. No rooms were deleted or marked as deleted.');
|
||||
}
|
||||
|
||||
return { deleted, markedForDeletion };
|
||||
@ -275,7 +275,7 @@ export class RoomService {
|
||||
|
||||
if (!room) {
|
||||
// If the room is not found, it means that there are no recordings for that room or the room doesn't exist
|
||||
throw errorRoomNotFoundOrEmptyRecordings(roomId);
|
||||
throw errorRoomMetadataNotFound(roomId);
|
||||
}
|
||||
|
||||
const role = this.getRoomRoleBySecretFromRoom(room as MeetRoom, secret);
|
||||
|
||||
@ -84,7 +84,7 @@ export class S3Service {
|
||||
throw errorS3NotAvailable(error);
|
||||
}
|
||||
|
||||
throw internalError(error);
|
||||
throw internalError('saving object to S3');
|
||||
}
|
||||
}
|
||||
|
||||
@ -109,7 +109,7 @@ export class S3Service {
|
||||
return result;
|
||||
} catch (error: any) {
|
||||
this.logger.error(`S3 bulk delete: error deleting objects in bucket ${bucket}: ${error}`);
|
||||
throw internalError(error);
|
||||
throw internalError('deleting objects from S3');
|
||||
}
|
||||
}
|
||||
|
||||
@ -158,7 +158,7 @@ export class S3Service {
|
||||
return response;
|
||||
} catch (error: any) {
|
||||
this.logger.error(`S3 listObjectsPaginated: error listing objects with prefix "${basePrefix}": ${error}`);
|
||||
throw internalError(error);
|
||||
throw internalError('listing objects from S3');
|
||||
}
|
||||
}
|
||||
|
||||
@ -182,7 +182,7 @@ export class S3Service {
|
||||
}
|
||||
|
||||
this.logger.error(`S3 getObjectAsJson: error retrieving object ${name} from bucket ${bucket}: ${error}`);
|
||||
throw internalError(error);
|
||||
throw internalError('getting object as JSON from S3');
|
||||
}
|
||||
}
|
||||
|
||||
@ -212,7 +212,7 @@ export class S3Service {
|
||||
throw errorS3NotAvailable(error);
|
||||
}
|
||||
|
||||
throw internalError(error);
|
||||
throw internalError('getting object as stream from S3');
|
||||
}
|
||||
}
|
||||
|
||||
@ -230,7 +230,7 @@ export class S3Service {
|
||||
`S3 getHeaderObject: error getting header for object ${this.getFullKey(name)} in bucket ${bucket}: ${error}`
|
||||
);
|
||||
|
||||
throw internalError(error);
|
||||
throw internalError('getting header for object from S3');
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@ import { inject, injectable } from 'inversify';
|
||||
import ms from 'ms';
|
||||
import { MEET_NAME_ID, MEET_SECRET, MEET_USER, MEET_WEBHOOK_ENABLED, MEET_WEBHOOK_URL } from '../../environment.js';
|
||||
import { MeetLock, PasswordHelper } from '../../helpers/index.js';
|
||||
import { errorRoomNotFound, OpenViduMeetError } from '../../models/error.model.js';
|
||||
import { errorRoomNotFound, internalError, OpenViduMeetError } from '../../models/error.model.js';
|
||||
import { LoggerService, MutexService, StorageFactory, StorageProvider } from '../index.js';
|
||||
|
||||
/**
|
||||
@ -56,13 +56,19 @@ export class MeetStorageService<G extends GlobalPreferences = GlobalPreferences,
|
||||
* @returns {Promise<GlobalPreferences>}
|
||||
*/
|
||||
async getGlobalPreferences(): Promise<G> {
|
||||
const preferences = await this.storageProvider.getGlobalPreferences();
|
||||
let preferences = await this.storageProvider.getGlobalPreferences();
|
||||
|
||||
if (preferences) return preferences as G;
|
||||
|
||||
await this.initializeGlobalPreferences();
|
||||
preferences = await this.storageProvider.getGlobalPreferences();
|
||||
|
||||
return this.storageProvider.getGlobalPreferences() as Promise<G>;
|
||||
if (!preferences) {
|
||||
this.logger.error('Global preferences not found after initialization');
|
||||
throw internalError('getting global preferences');
|
||||
}
|
||||
|
||||
return preferences as G;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user