backend: refactor RecordingService to use StorageProvider for listing objects and remove S3Service dependency

This commit is contained in:
Carlos Santos 2025-05-30 11:06:48 +02:00
parent 2872383b01
commit 333c7da5b2
5 changed files with 86 additions and 16 deletions

View File

@ -32,7 +32,6 @@ import {
MutexService, MutexService,
RedisLock, RedisLock,
RoomService, RoomService,
S3Service,
SystemEventService, SystemEventService,
TaskSchedulerService TaskSchedulerService
} from './index.js'; } from './index.js';
@ -40,7 +39,6 @@ import {
@injectable() @injectable()
export class RecordingService { export class RecordingService {
constructor( constructor(
@inject(S3Service) protected s3Service: S3Service,
@inject(LiveKitService) protected livekitService: LiveKitService, @inject(LiveKitService) protected livekitService: LiveKitService,
@inject(RoomService) protected roomService: RoomService, @inject(RoomService) protected roomService: RoomService,
@inject(MutexService) protected mutexService: MutexService, @inject(MutexService) protected mutexService: MutexService,
@ -306,7 +304,7 @@ export class RecordingService {
protected async shouldDeleteRoomMetadata(roomId: string): Promise<boolean | null> { protected async shouldDeleteRoomMetadata(roomId: string): Promise<boolean | null> {
try { try {
const metadataPrefix = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}`; const metadataPrefix = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata/${roomId}`;
const { Contents } = await this.s3Service.listObjectsPaginated(metadataPrefix); const { Contents } = await this.storageService.listObjects(metadataPrefix, 1);
// If no metadata files exist or the list is empty, the room metadata should be deleted // If no metadata files exist or the list is empty, the room metadata should be deleted
return !Contents || Contents.length === 0; return !Contents || Contents.length === 0;
@ -346,10 +344,11 @@ export class RecordingService {
try { try {
// Construct the room prefix if a room ID is provided // Construct the room prefix if a room ID is provided
const roomPrefix = roomId ? `/${roomId}` : ''; const roomPrefix = roomId ? `/${roomId}` : '';
const recordingPrefix = `${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata${roomPrefix}`;
// Retrieve the recordings from the S3 bucket // Retrieve the recordings from the S3 bucket
const { Contents, IsTruncated, NextContinuationToken } = await this.s3Service.listObjectsPaginated( const { Contents, IsTruncated, NextContinuationToken } = await this.storageService.listObjects(
`${INTERNAL_CONFIG.S3_RECORDINGS_PREFIX}/.metadata${roomPrefix}`, recordingPrefix,
maxItems, maxItems,
nextPageToken nextPageToken
); );

View File

@ -128,7 +128,6 @@ export class S3Service {
additionalPrefix = '', additionalPrefix = '',
maxKeys = 50, maxKeys = 50,
continuationToken?: string, continuationToken?: string,
searchPattern = '',
bucket: string = MEET_S3_BUCKET bucket: string = MEET_S3_BUCKET
): Promise<ListObjectsV2CommandOutput> { ): Promise<ListObjectsV2CommandOutput> {
// The complete prefix is constructed by combining the subbucket and the additionalPrefix. // The complete prefix is constructed by combining the subbucket and the additionalPrefix.
@ -145,16 +144,7 @@ export class S3Service {
}); });
try { try {
const response: ListObjectsV2CommandOutput = await this.s3.send(command); return await this.s3.send(command);
// If searchPattern is provided, filter the results.
if (searchPattern) {
const regex = new RegExp(searchPattern);
response.Contents = (response.Contents || []).filter((item) => item.Key && regex.test(item.Key));
}
return response;
} catch (error: any) { } catch (error: any) {
this.logger.error(`S3 listObjectsPaginated: error listing objects with prefix "${basePrefix}": ${error}`); this.logger.error(`S3 listObjectsPaginated: error listing objects with prefix "${basePrefix}": ${error}`);
throw internalError('listing objects from S3'); throw internalError('listing objects from S3');

View File

@ -58,6 +58,39 @@ export class S3StorageProvider<
} }
} }
/**
* Lists objects in the storage with optional pagination support.
*
* @param prefix - The prefix to filter objects by (acts as a folder path)
* @param maxItems - Maximum number of items to return (optional)
* @param nextPageToken - Token for pagination to get the next page (optional)
* @returns Promise resolving to paginated list of objects with metadata
*/
async listObjects(
prefix: string,
maxItems?: number,
nextPageToken?: string
): Promise<{
Contents?: Array<{
Key?: string;
LastModified?: Date;
Size?: number;
ETag?: string;
}>;
IsTruncated?: boolean;
NextContinuationToken?: string;
}> {
try {
this.logger.debug(
`Listing objects with prefix: ${prefix}, maxItems: ${maxItems}, nextPageToken: ${nextPageToken}`
);
return await this.s3Service.listObjectsPaginated(prefix, maxItems, nextPageToken);
} catch (error) {
this.handleError(error, `Error listing objects with prefix ${prefix}`);
throw error;
}
}
/** /**
* Initializes global preferences. If no preferences exist, persists the provided defaults. * Initializes global preferences. If no preferences exist, persists the provided defaults.
* If preferences exist but belong to a different project, they are replaced. * If preferences exist but belong to a different project, they are replaced.

View File

@ -33,6 +33,29 @@ export interface StorageProvider<
*/ */
getObjectHeaders(filePath: string): Promise<{ contentLength?: number; contentType?: string }>; getObjectHeaders(filePath: string): Promise<{ contentLength?: number; contentType?: string }>;
/**
* Lists objects in the storage with optional pagination support.
*
* @param prefix - The prefix to filter objects by (acts as a folder path)
* @param maxItems - Maximum number of items to return (optional)
* @param nextPageToken - Token for pagination to get the next page (optional)
* @returns Promise resolving to paginated list of objects with metadata
*/
listObjects(
prefix: string,
maxItems?: number,
nextPageToken?: string
): Promise<{
Contents?: Array<{
Key?: string;
LastModified?: Date;
Size?: number;
ETag?: string;
}>;
IsTruncated?: boolean;
NextContinuationToken?: string;
}>;
/** /**
* Retrieves the global preferences of Openvidu Meet. * Retrieves the global preferences of Openvidu Meet.
* *

View File

@ -42,6 +42,31 @@ export class MeetStorageService<
} }
} }
/**
* Lists objects in the storage with optional pagination support.
*
* @param prefix - The prefix to filter objects by (acts as a folder path)
* @param maxItems - Maximum number of items to return (optional)
* @param nextPageToken - Token for pagination to get the next page (optional)
* @returns Promise resolving to paginated list of objects with metadata
*/
listObjects(
prefix: string,
maxItems?: number,
nextPageToken?: string
): Promise<{
Contents?: Array<{
Key?: string;
LastModified?: Date;
Size?: number;
ETag?: string;
}>;
IsTruncated?: boolean;
NextContinuationToken?: string;
}> {
return this.storageProvider.listObjects(prefix, maxItems, nextPageToken);
}
/** /**
* Initializes default preferences if not already initialized. * Initializes default preferences if not already initialized.
* @returns {Promise<GPrefs>} Default global preferences. * @returns {Promise<GPrefs>} Default global preferences.