From 32c0c9d242a777fef4793dfaeb95d6fa5e97d131 Mon Sep 17 00:00:00 2001 From: Carlos Santos <4a.santos@gmail.com> Date: Tue, 22 Apr 2025 13:42:32 +0200 Subject: [PATCH] backend: Implement room metadata archiving and retrieval in storage service --- .../src/services/livekit-webhook.service.ts | 43 +------------ .../storage/providers/s3-storage.provider.ts | 63 ++++++++++++++++++- .../src/services/storage/storage.interface.ts | 31 +++++++++ .../src/services/storage/storage.service.ts | 9 +++ 4 files changed, 105 insertions(+), 41 deletions(-) diff --git a/backend/src/services/livekit-webhook.service.ts b/backend/src/services/livekit-webhook.service.ts index c039505..31073b7 100644 --- a/backend/src/services/livekit-webhook.service.ts +++ b/backend/src/services/livekit-webhook.service.ts @@ -14,6 +14,7 @@ import { SystemEventService } from './system-event.service.js'; import { SystemEventType } from '../models/system-event.model.js'; import { MeetRoomHelper } from '../helpers/room.helper.js'; import INTERNAL_CONFIG from '../config/internal-config.js'; +import { MeetStorageService } from './storage/storage.service.js'; @injectable() export class LivekitWebhookService { @@ -23,6 +24,7 @@ export class LivekitWebhookService { @inject(RecordingService) protected recordingService: RecordingService, @inject(LiveKitService) protected livekitService: LiveKitService, @inject(RoomService) protected roomService: RoomService, + @inject(MeetStorageService) protected storageService: MeetStorageService, @inject(OpenViduWebhookService) protected openViduWebhookService: OpenViduWebhookService, @inject(MutexService) protected mutexService: MutexService, @inject(SystemEventService) protected systemEventService: SystemEventService, @@ -200,7 +202,7 @@ export class LivekitWebhookService { switch (webhookAction) { case 'started': tasks.push( - this.saveRoomMetadataFileIfNeeded(roomId), + this.storageService.archiveRoomMetadata(roomId), this.openViduWebhookService.sendRecordingStartedWebhook(recordingInfo) ); break; @@ -235,43 +237,4 @@ export class LivekitWebhookService { ); } } - - /** - * Saves room metadata to a JSON file in the S3 bucket if it doesn't already exist. - * - * This method checks if the metadata file for the given room already exists in the - * S3 bucket. If not, it retrieves the room information, extracts the necessary - * secrets and preferences, and saves them to a metadata JSON file in the - * .metadata/{roomId}/ directory of the S3 bucket. - * - * @param roomId - The unique identifier of the room - */ - protected async saveRoomMetadataFileIfNeeded(roomId: string): Promise { - try { - const filePath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/room_metadata.json`; - const fileExists = await this.s3Service.exists(filePath); - - if (fileExists) { - this.logger.debug(`Room metadata already saved for room ${roomId} in recordings bucket`); - return; - } - - const room = await this.roomService.getMeetRoom(roomId); - - if (room) { - const { publisherSecret, moderatorSecret } = MeetRoomHelper.extractSecretsFromRoom(room); - const roomMetadata = { - publisherSecret, - moderatorSecret, - preferences: { - recordingPreferences: room.preferences?.recordingPreferences - } - }; - await this.s3Service.saveObject(filePath, roomMetadata); - this.logger.debug(`Room metadata saved for room ${roomId} in recordings bucket`); - } - } catch (error) { - this.logger.error(`Error saving room metadata for room ${roomId} in recordings bucket: ${error}`); - } - } } diff --git a/backend/src/services/storage/providers/s3-storage.provider.ts b/backend/src/services/storage/providers/s3-storage.provider.ts index caa76bd..39440dd 100644 --- a/backend/src/services/storage/providers/s3-storage.provider.ts +++ b/backend/src/services/storage/providers/s3-storage.provider.ts @@ -245,13 +245,74 @@ export class S3StorageProvider RedisKeyName.ROOM + id); try { - await Promise.all([this.s3Service.deleteObjects(roomsToDelete), this.redisService.delete(redisKeysToDelete)]); + await Promise.all([ + this.s3Service.deleteObjects(roomsToDelete), + this.redisService.delete(redisKeysToDelete) + ]); this.logger.verbose(`Rooms deleted successfully: ${roomIds.join(', ')}`); } catch (error) { this.handleError(error, `Error deleting rooms: ${roomIds.join(', ')}`); } } + async getArchivedRoomMetadata(roomId: string): Promise | null> { + try { + const filePath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/room_metadata.json`; + const roomMetadata = await this.getFromS3>(filePath); + + if (!roomMetadata) { + this.logger.warn(`Room metadata not found for room ${roomId} in recordings bucket`); + return null; + } + + return roomMetadata; + } catch (error) { + this.handleError(error, `Error fetching archived room metadata for room ${roomId}`); + return null; + } + } + + /** + * Saves room metadata to a JSON file in the S3 bucket if it doesn't already exist. + * + * This method checks if the metadata file for the given room already exists in the + * S3 bucket. If not, it retrieves the room information, extracts the necessary + * secrets and preferences, and saves them to a metadata JSON file in the + * .metadata/{roomId}/ directory of the S3 bucket. + * + * @param roomId - The unique identifier of the room + */ + async archiveRoomMetadata(roomId: string): Promise { + try { + const filePath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/room_metadata.json`; + const fileExists = await this.s3Service.exists(filePath); + + if (fileExists) { + this.logger.debug(`Room metadata already saved for room ${roomId} in recordings bucket`); + return; + } + + const room = await this.getMeetRoom(roomId); + + if (room) { + const roomMetadata = { + moderatorRoomUrl: room.moderatorRoomUrl, + publisherRoomUrl: room.publisherRoomUrl, + preferences: { + recordingPreferences: room.preferences?.recordingPreferences + } + }; + await this.s3Service.saveObject(filePath, roomMetadata); + this.logger.debug(`Room metadata saved for room ${roomId} in recordings bucket`); + return; + } + + this.logger.error(`Error saving room metadata for room ${roomId} in recordings bucket`); + } catch (error) { + this.logger.error(`Error saving room metadata for room ${roomId} in recordings bucket: ${error}`); + } + } + /** * Retrieves an object of type U from Redis by the given key. * Returns null if the key is not found or an error occurs. diff --git a/backend/src/services/storage/storage.interface.ts b/backend/src/services/storage/storage.interface.ts index 0166d30..044ba0e 100644 --- a/backend/src/services/storage/storage.interface.ts +++ b/backend/src/services/storage/storage.interface.ts @@ -78,4 +78,35 @@ export interface StorageProvider; + + /** + * Gets the archived metadata for a specific room. + * + * The archived metadata is necessary for checking the permissions of the recording viewer when the room is deleted. + * + * @param roomId - The name of the room to retrieve. + */ + getArchivedRoomMetadata(roomId: string): Promise | null>; + + /** + * Archives the metadata for a specific room. + * + * This is necessary for persisting the metadata of a room although it is deleted. + * The metadata will be used to check the permissions of the recording viewer. + * + * @param roomId: The room ID to archive. + */ + archiveRoomMetadata(roomId: string): Promise; + + //TODO: + // deleteArchivedRoomMetadata(roomId: string): Promise; + + //TODO: + // saveRecordingMetadata; + + //TODO: + // getRecordingMetadata; + + //TODO: + // deleteRecordingMetadata; } diff --git a/backend/src/services/storage/storage.service.ts b/backend/src/services/storage/storage.service.ts index 393ddd8..77a2035 100644 --- a/backend/src/services/storage/storage.service.ts +++ b/backend/src/services/storage/storage.service.ts @@ -121,6 +121,15 @@ export class MeetStorageService | null> { + return this.storageProvider.getArchivedRoomMetadata(roomId) as Promise | null>; + } + + + async archiveRoomMetadata(roomId: string): Promise { + return this.storageProvider.archiveRoomMetadata(roomId); + } + /** * Returns the default global preferences. * @returns {G}