backend: enhance StorageProvider interface with recording metadata methods and type parameters

This commit is contained in:
Carlos Santos 2025-05-28 11:56:41 +02:00
parent 172e8edcfd
commit b9a11dd45d
2 changed files with 75 additions and 27 deletions

View File

@ -17,14 +17,17 @@ import { LoggerService, RedisService, S3Service, StorageProvider } from '../../i
* The storage operations are performed in parallel to both systems when writing data, * The storage operations are performed in parallel to both systems when writing data,
* with transaction-like rollback behavior if one operation fails. * with transaction-like rollback behavior if one operation fails.
* *
* @template G - Type for global preferences data, defaults to GlobalPreferences * @template GPrefs - Type for global preferences data, defaults to GlobalPreferences
* @template R - Type for room data, defaults to MeetRoom * @template MRoom - Type for room data, defaults to MeetRoom
* *
* @implements {StorageProvider} * @implements {StorageProvider}
*/ */
@injectable() @injectable()
export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences, R extends MeetRoom = MeetRoom> export class S3StorageProvider<
implements StorageProvider GPrefs extends GlobalPreferences = GlobalPreferences,
MRoom extends MeetRoom = MeetRoom,
MRec extends MeetRecordingInfo = MeetRecordingInfo
> implements StorageProvider
{ {
protected readonly S3_GLOBAL_PREFERENCES_KEY = `global-preferences.json`; protected readonly S3_GLOBAL_PREFERENCES_KEY = `global-preferences.json`;
constructor( constructor(
@ -39,7 +42,7 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
* *
* @param defaultPreferences - The default preferences to initialize with. * @param defaultPreferences - The default preferences to initialize with.
*/ */
async initialize(defaultPreferences: G): Promise<void> { async initialize(defaultPreferences: GPrefs): Promise<void> {
try { try {
const existingPreferences = await this.getGlobalPreferences(); const existingPreferences = await this.getGlobalPreferences();
@ -77,14 +80,14 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
* *
* @returns A promise that resolves to the global preferences or null if not found. * @returns A promise that resolves to the global preferences or null if not found.
*/ */
async getGlobalPreferences(): Promise<G | null> { async getGlobalPreferences(): Promise<GPrefs | null> {
try { try {
// Try to get preferences from Redis cache // Try to get preferences from Redis cache
let preferences: G | null = await this.getFromRedis<G>(RedisKeyName.GLOBAL_PREFERENCES); let preferences: GPrefs | null = await this.getFromRedis<GPrefs>(RedisKeyName.GLOBAL_PREFERENCES);
if (!preferences) { if (!preferences) {
this.logger.debug('Global preferences not found in Redis. Fetching from S3...'); this.logger.debug('Global preferences not found in Redis. Fetching from S3...');
preferences = await this.getFromS3<G>(this.S3_GLOBAL_PREFERENCES_KEY); preferences = await this.getFromS3<GPrefs>(this.S3_GLOBAL_PREFERENCES_KEY);
if (preferences) { if (preferences) {
this.logger.verbose('Fetched global preferences from S3. Caching them in Redis.'); this.logger.verbose('Fetched global preferences from S3. Caching them in Redis.');
@ -112,7 +115,7 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
* @returns The saved preferences. * @returns The saved preferences.
* @throws Rethrows any error if saving fails. * @throws Rethrows any error if saving fails.
*/ */
async saveGlobalPreferences(preferences: G): Promise<G> { async saveGlobalPreferences(preferences: GPrefs): Promise<GPrefs> {
try { try {
const redisPayload = JSON.stringify(preferences); const redisPayload = JSON.stringify(preferences);
@ -136,7 +139,7 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
* @returns The saved room if both operations succeed. * @returns The saved room if both operations succeed.
* @throws The error from the first failed operation. * @throws The error from the first failed operation.
*/ */
async saveMeetRoom(meetRoom: R): Promise<R> { async saveMeetRoom(meetRoom: MRoom): Promise<MRoom> {
const { roomId } = meetRoom; const { roomId } = meetRoom;
const s3Path = `${INTERNAL_CONFIG.S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`; const s3Path = `${INTERNAL_CONFIG.S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`;
const redisPayload = JSON.stringify(meetRoom); const redisPayload = JSON.stringify(meetRoom);
@ -173,7 +176,7 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
maxItems: number, maxItems: number,
nextPageToken?: string nextPageToken?: string
): Promise<{ ): Promise<{
rooms: R[]; rooms: MRoom[];
isTruncated: boolean; isTruncated: boolean;
nextPageToken?: string; nextPageToken?: string;
}> { }> {
@ -208,7 +211,7 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
); );
// Filter out null values // Filter out null values
const validRooms = rooms.filter((room) => room !== null) as R[]; const validRooms = rooms.filter((room) => room !== null) as MRoom[];
return { rooms: validRooms, isTruncated: !!IsTruncated, nextPageToken: NextContinuationToken }; return { rooms: validRooms, isTruncated: !!IsTruncated, nextPageToken: NextContinuationToken };
} catch (error) { } catch (error) {
this.handleError(error, 'Error fetching Room preferences'); this.handleError(error, 'Error fetching Room preferences');
@ -216,16 +219,16 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
} }
} }
async getMeetRoom(roomId: string): Promise<R | null> { async getMeetRoom(roomId: string): Promise<MRoom | null> {
try { try {
// Try to get room preferences from Redis cache // Try to get room preferences from Redis cache
const room: R | null = await this.getFromRedis<R>(roomId); const room: MRoom | null = await this.getFromRedis<MRoom>(roomId);
if (!room) { if (!room) {
const s3RoomPath = `${INTERNAL_CONFIG.S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`; const s3RoomPath = `${INTERNAL_CONFIG.S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`;
this.logger.debug(`Room ${roomId} not found in Redis. Fetching from S3 at ${s3RoomPath}...`); this.logger.debug(`Room ${roomId} not found in Redis. Fetching from S3 at ${s3RoomPath}...`);
return await this.getFromS3<R>(s3RoomPath); return await this.getFromS3<MRoom>(s3RoomPath);
} }
this.logger.debug(`Room ${roomId} verified in Redis`); this.logger.debug(`Room ${roomId} verified in Redis`);
@ -251,10 +254,10 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
} }
} }
async getArchivedRoomMetadata(roomId: string): Promise<Partial<R> | null> { async getArchivedRoomMetadata(roomId: string): Promise<Partial<MRoom> | null> {
try { try {
const filePath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.room_metadata/${roomId}/room_metadata.json`; const filePath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.room_metadata/${roomId}/room_metadata.json`;
const roomMetadata = await this.getFromS3<Partial<R>>(filePath); const roomMetadata = await this.getFromS3<Partial<MRoom>>(filePath);
if (!roomMetadata) { if (!roomMetadata) {
this.logger.warn(`Room metadata not found for room ${roomId} in recordings bucket`); this.logger.warn(`Room metadata not found for room ${roomId} in recordings bucket`);
@ -345,6 +348,28 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
} }
} }
async deleteArchivedRoomMetadata(roomId: string): Promise<void> {
//TODO : Implement this method to delete archived room metadata
this.logger.warn('deleteArchivedRoomMetadata is not implemented yet');
}
async getRecordingMetadata(recordingId: string): Promise<MRec | null> {
//TODO : Implement this method to retrieve recording metadata for a room
this.logger.warn('getRecordingMetadata is not implemented yet');
return null;
}
async saveRecordingMetadata(recordingInfo: MRec): Promise<MRec> {
//TODO : Implement this method to save recording metadata for a room
this.logger.warn('saveMeetRecordingInfo is not implemented yet');
return recordingInfo;
}
async deleteRecordingMetadata(recordingId: string): Promise<void> {
//TODO : Implement this method to delete recording metadata for a room
this.logger.warn('deleteRecordingMetadata is not implemented yet');
}
/** /**
* Retrieves an object of type U from Redis by the given key. * Retrieves an object of type U from Redis by the given key.
* Returns null if the key is not found or an error occurs. * Returns null if the key is not found or an error occurs.

View File

@ -1,4 +1,4 @@
import { GlobalPreferences, MeetRoom } from '@typings-ce'; import { GlobalPreferences, MeetRecordingInfo, MeetRoom } from '@typings-ce';
/** /**
* An interface that defines the contract for storage providers in the OpenVidu Meet application. * An interface that defines the contract for storage providers in the OpenVidu Meet application.
@ -11,7 +11,11 @@ import { GlobalPreferences, MeetRoom } from '@typings-ce';
* of application settings and room information, which could be backed by * of application settings and room information, which could be backed by
* various storage solutions (database, file system, cloud storage, etc.). * various storage solutions (database, file system, cloud storage, etc.).
*/ */
export interface StorageProvider<GPrefs extends GlobalPreferences = GlobalPreferences, MRoom extends MeetRoom = MeetRoom> { export interface StorageProvider<
GPrefs extends GlobalPreferences = GlobalPreferences,
MRoom extends MeetRoom = MeetRoom,
MRec extends MeetRecordingInfo = MeetRecordingInfo
> {
/** /**
* Initializes the storage with default preferences if they are not already set. * Initializes the storage with default preferences if they are not already set.
* *
@ -107,15 +111,34 @@ export interface StorageProvider<GPrefs extends GlobalPreferences = GlobalPrefer
*/ */
updateArchivedRoomMetadata(roomId: string): Promise<void>; updateArchivedRoomMetadata(roomId: string): Promise<void>;
//TODO: /**
// deleteArchivedRoomMetadata(roomId: string): Promise<void>; * Deletes the archived metadata for a specific room.
*
* @param roomId - The room ID to delete the archived metadata for.
*/
deleteArchivedRoomMetadata(roomId: string): Promise<void>;
//TODO: /**
// saveRecordingMetadata; * Saves the recording metadata.
*
* @param recordingInfo - The recording information to save.
* @returns A promise that resolves to the saved recording information.
*/
saveRecordingMetadata(recordingInfo: MRec): Promise<MRec>;
//TODO: /**
// getRecordingMetadata; * Retrieves the recording metadata for a specific recording ID.
*
* @param recordingId - The unique identifier of the recording.
* @returns A promise that resolves to the recording metadata, or null if not found.
*/
getRecordingMetadata(recordingId: string): Promise<MRec | null>;
//TODO: /**
// deleteRecordingMetadata; * Deletes the recording metadata for a specific recording ID.
*
* @param recordingId - The unique identifier of the recording to delete.
* @returns A promise that resolves when the deletion is complete.
*/
deleteRecordingMetadata(recordingId: string): Promise<void>;
} }