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 { SystemEventType } from '../models/system-event.model.js';
|
||||||
import { MeetRoomHelper } from '../helpers/room.helper.js';
|
import { MeetRoomHelper } from '../helpers/room.helper.js';
|
||||||
import INTERNAL_CONFIG from '../config/internal-config.js';
|
import INTERNAL_CONFIG from '../config/internal-config.js';
|
||||||
|
import { MeetStorageService } from './storage/storage.service.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class LivekitWebhookService {
|
export class LivekitWebhookService {
|
||||||
@ -23,6 +24,7 @@ export class LivekitWebhookService {
|
|||||||
@inject(RecordingService) protected recordingService: RecordingService,
|
@inject(RecordingService) protected recordingService: RecordingService,
|
||||||
@inject(LiveKitService) protected livekitService: LiveKitService,
|
@inject(LiveKitService) protected livekitService: LiveKitService,
|
||||||
@inject(RoomService) protected roomService: RoomService,
|
@inject(RoomService) protected roomService: RoomService,
|
||||||
|
@inject(MeetStorageService) protected storageService: MeetStorageService,
|
||||||
@inject(OpenViduWebhookService) protected openViduWebhookService: OpenViduWebhookService,
|
@inject(OpenViduWebhookService) protected openViduWebhookService: OpenViduWebhookService,
|
||||||
@inject(MutexService) protected mutexService: MutexService,
|
@inject(MutexService) protected mutexService: MutexService,
|
||||||
@inject(SystemEventService) protected systemEventService: SystemEventService,
|
@inject(SystemEventService) protected systemEventService: SystemEventService,
|
||||||
@ -200,7 +202,7 @@ export class LivekitWebhookService {
|
|||||||
switch (webhookAction) {
|
switch (webhookAction) {
|
||||||
case 'started':
|
case 'started':
|
||||||
tasks.push(
|
tasks.push(
|
||||||
this.saveRoomMetadataFileIfNeeded(roomId),
|
this.storageService.archiveRoomMetadata(roomId),
|
||||||
this.openViduWebhookService.sendRecordingStartedWebhook(recordingInfo)
|
this.openViduWebhookService.sendRecordingStartedWebhook(recordingInfo)
|
||||||
);
|
);
|
||||||
break;
|
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);
|
const redisKeysToDelete = roomIds.map((id) => RedisKeyName.ROOM + id);
|
||||||
|
|
||||||
try {
|
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(', ')}`);
|
this.logger.verbose(`Rooms deleted successfully: ${roomIds.join(', ')}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error, `Error deleting rooms: ${roomIds.join(', ')}`);
|
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.
|
* 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.
|
||||||
|
|||||||
@ -78,4 +78,35 @@ export interface StorageProvider<T extends GlobalPreferences = GlobalPreferences
|
|||||||
* @returns A promise that resolves when the room have been deleted.
|
* @returns A promise that resolves when the room have been deleted.
|
||||||
**/
|
**/
|
||||||
deleteMeetRooms(roomIds: string[]): Promise<void>;
|
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);
|
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 the default global preferences.
|
||||||
* @returns {G}
|
* @returns {G}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user