backend: Implement room metadata archiving and retrieval in storage service
This commit is contained in:
parent
c33ee7218b
commit
32c0c9d242
@ -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<void> {
|
||||
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}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -245,13 +245,74 @@ export class S3StorageProvider<G extends GlobalPreferences = GlobalPreferences,
|
||||
const redisKeysToDelete = roomIds.map((id) => 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<Partial<R> | null> {
|
||||
try {
|
||||
const filePath = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}/room_metadata.json`;
|
||||
const roomMetadata = await this.getFromS3<Partial<R>>(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<void> {
|
||||
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.
|
||||
|
||||
@ -78,4 +78,35 @@ export interface StorageProvider<T extends GlobalPreferences = GlobalPreferences
|
||||
* @returns A promise that resolves when the room have been deleted.
|
||||
**/
|
||||
deleteMeetRooms(roomIds: string[]): Promise<void>;
|
||||
|
||||
/**
|
||||
* 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<Partial<R> | 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<void>;
|
||||
|
||||
//TODO:
|
||||
// deleteArchivedRoomMetadata(roomId: string): Promise<void>;
|
||||
|
||||
//TODO:
|
||||
// saveRecordingMetadata;
|
||||
|
||||
//TODO:
|
||||
// getRecordingMetadata;
|
||||
|
||||
//TODO:
|
||||
// deleteRecordingMetadata;
|
||||
}
|
||||
|
||||
@ -121,6 +121,15 @@ export class MeetStorageService<G extends GlobalPreferences = GlobalPreferences,
|
||||
return this.storageProvider.deleteMeetRooms(roomIds);
|
||||
}
|
||||
|
||||
async getArchivedRoomMetadata(roomId: string): Promise<Partial<R> | null> {
|
||||
return this.storageProvider.getArchivedRoomMetadata(roomId) as Promise<Partial<R> | null>;
|
||||
}
|
||||
|
||||
|
||||
async archiveRoomMetadata(roomId: string): Promise<void> {
|
||||
return this.storageProvider.archiveRoomMetadata(roomId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the default global preferences.
|
||||
* @returns {G}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user