diff --git a/backend/src/config/dependency-injector.config.ts b/backend/src/config/dependency-injector.config.ts index 92a0a53..4904247 100644 --- a/backend/src/config/dependency-injector.config.ts +++ b/backend/src/config/dependency-injector.config.ts @@ -1,8 +1,8 @@ import { Container } from 'inversify'; import { AuthService, - GlobalPreferencesService, - GlobalPreferencesStorageFactory, + MeetStorageService, + StorageFactory, LiveKitService, LivekitWebhookService, LoggerService, @@ -12,7 +12,7 @@ import { RecordingService, RedisService, RoomService, - S3PreferenceStorage, + S3Storage, S3Service, SystemEventService, TaskSchedulerService, @@ -47,11 +47,11 @@ const registerDependencies = () => { container.bind(RecordingService).toSelf().inSingletonScope(); container.bind(LivekitWebhookService).toSelf().inSingletonScope(); - container.bind(GlobalPreferencesService).toSelf().inSingletonScope(); + container.bind(MeetStorageService).toSelf().inSingletonScope(); container.bind(ParticipantService).toSelf().inSingletonScope(); - container.bind(S3PreferenceStorage).toSelf().inSingletonScope(); - container.bind(GlobalPreferencesStorageFactory).toSelf().inSingletonScope(); + container.bind(S3Storage).toSelf().inSingletonScope(); + container.bind(StorageFactory).toSelf().inSingletonScope(); initializeEagerServices(); }; diff --git a/backend/src/controllers/global-preferences/room-preferences.controller.ts b/backend/src/controllers/global-preferences/room-preferences.controller.ts index 1e02432..d3868d3 100644 --- a/backend/src/controllers/global-preferences/room-preferences.controller.ts +++ b/backend/src/controllers/global-preferences/room-preferences.controller.ts @@ -1,7 +1,7 @@ import { container } from '../../config/dependency-injector.config.js'; import { Request, Response } from 'express'; import { LoggerService } from '../../services/logger.service.js'; -import { GlobalPreferencesService } from '../../services/preferences/index.js'; +import { MeetStorageService } from '../../services/storage/index.js'; import { OpenViduMeetError } from '../../models/error.model.js'; export const updateRoomPreferences = async (req: Request, res: Response) => { @@ -11,7 +11,7 @@ export const updateRoomPreferences = async (req: Request, res: Response) => { const { roomId, roomPreferences } = req.body; try { - const preferenceService = container.get(GlobalPreferencesService); + const preferenceService = container.get(MeetStorageService); preferenceService.validateRoomPreferences(roomPreferences); const savedPreferences = await preferenceService.updateOpenViduRoomPreferences(roomId, roomPreferences); @@ -35,7 +35,7 @@ export const getRoomPreferences = async (req: Request, res: Response) => { try { const { roomId } = req.params; - const preferenceService = container.get(GlobalPreferencesService); + const preferenceService = container.get(MeetStorageService); const preferences = await preferenceService.getOpenViduRoomPreferences(roomId); if (!preferences) { diff --git a/backend/src/controllers/global-preferences/security-preferences.controller.ts b/backend/src/controllers/global-preferences/security-preferences.controller.ts index 8db8b59..f8ac508 100644 --- a/backend/src/controllers/global-preferences/security-preferences.controller.ts +++ b/backend/src/controllers/global-preferences/security-preferences.controller.ts @@ -1,13 +1,13 @@ import { container } from '../../config/dependency-injector.config.js'; import { Request, Response } from 'express'; import { LoggerService } from '../../services/logger.service.js'; -import { GlobalPreferencesService } from '../../services/preferences/index.js'; +import { MeetStorageService } from '../../services/storage/index.js'; import { OpenViduMeetError } from '../../models/error.model.js'; import { SecurityPreferencesDTO, UpdateSecurityPreferencesDTO } from '@typings-ce'; export const updateSecurityPreferences = async (req: Request, res: Response) => { const logger = container.get(LoggerService); - const globalPrefService = container.get(GlobalPreferencesService); + const globalPrefService = container.get(MeetStorageService); logger.verbose(`Updating security preferences: ${JSON.stringify(req.body)}`); const securityPreferences = req.body as UpdateSecurityPreferencesDTO; @@ -43,7 +43,7 @@ export const updateSecurityPreferences = async (req: Request, res: Response) => export const getSecurityPreferences = async (_req: Request, res: Response) => { const logger = container.get(LoggerService); - const preferenceService = container.get(GlobalPreferencesService); + const preferenceService = container.get(MeetStorageService); try { const preferences = await preferenceService.getGlobalPreferences(); diff --git a/backend/src/controllers/global-preferences/webhook-preferences.controller.ts b/backend/src/controllers/global-preferences/webhook-preferences.controller.ts index 4b6d112..502c7db 100644 --- a/backend/src/controllers/global-preferences/webhook-preferences.controller.ts +++ b/backend/src/controllers/global-preferences/webhook-preferences.controller.ts @@ -1,13 +1,13 @@ import { container } from '../../config/dependency-injector.config.js'; import { Request, Response } from 'express'; import { LoggerService } from '../../services/logger.service.js'; -import { GlobalPreferencesService } from '../../services/preferences/index.js'; +import { MeetStorageService } from '../../services/storage/index.js'; import { OpenViduMeetError } from '../../models/error.model.js'; import { WebhookPreferences } from '@typings-ce'; export const updateWebhookPreferences = async (req: Request, res: Response) => { const logger = container.get(LoggerService); - const globalPrefService = container.get(GlobalPreferencesService); + const globalPrefService = container.get(MeetStorageService); logger.verbose(`Updating webhooks preferences: ${JSON.stringify(req.body)}`); const webhookPreferences = req.body as WebhookPreferences; @@ -31,7 +31,7 @@ export const updateWebhookPreferences = async (req: Request, res: Response) => { export const getWebhookPreferences = async (_req: Request, res: Response) => { const logger = container.get(LoggerService); - const preferenceService = container.get(GlobalPreferencesService); + const preferenceService = container.get(MeetStorageService); try { const preferences = await preferenceService.getGlobalPreferences(); diff --git a/backend/src/middlewares/participant.middleware.ts b/backend/src/middlewares/participant.middleware.ts index cab0572..4fc0be9 100644 --- a/backend/src/middlewares/participant.middleware.ts +++ b/backend/src/middlewares/participant.middleware.ts @@ -1,7 +1,7 @@ import { Request, Response, NextFunction } from 'express'; import { AuthMode, ParticipantRole, UserRole, TokenOptions } from '@typings-ce'; import { container } from '../config/dependency-injector.config.js'; -import { GlobalPreferencesService, LoggerService, RoomService } from '../services/index.js'; +import { MeetStorageService, LoggerService, RoomService } from '../services/index.js'; import { allowAnonymous, tokenAndRoleValidator, withAuth } from './auth.middleware.js'; /** @@ -13,7 +13,7 @@ import { allowAnonymous, tokenAndRoleValidator, withAuth } from './auth.middlewa */ export const configureTokenAuth = async (req: Request, res: Response, next: NextFunction) => { const logger = container.get(LoggerService); - const globalPrefService = container.get(GlobalPreferencesService); + const globalPrefService = container.get(MeetStorageService); const roomService = container.get(RoomService); let role: ParticipantRole; diff --git a/backend/src/middlewares/room.middleware.ts b/backend/src/middlewares/room.middleware.ts index 73e5851..c739215 100644 --- a/backend/src/middlewares/room.middleware.ts +++ b/backend/src/middlewares/room.middleware.ts @@ -1,7 +1,7 @@ import { container } from '../config/dependency-injector.config.js'; import { NextFunction, Request, Response } from 'express'; import { LoggerService } from '../services/logger.service.js'; -import { GlobalPreferencesService } from '../services/index.js'; +import { MeetStorageService } from '../services/index.js'; import { allowAnonymous, apiKeyValidator, tokenAndRoleValidator, withAuth } from './auth.middleware.js'; import { AuthMode, ParticipantRole, UserRole } from '@typings-ce'; @@ -14,7 +14,7 @@ import { AuthMode, ParticipantRole, UserRole } from '@typings-ce'; */ export const configureCreateRoomAuth = async (req: Request, res: Response, next: NextFunction) => { const logger = container.get(LoggerService); - const globalPrefService = container.get(GlobalPreferencesService); + const globalPrefService = container.get(MeetStorageService); let allowRoomCreation: boolean; let requireAuthentication: boolean; @@ -70,7 +70,7 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne } const logger = container.get(LoggerService); - const globalPrefService = container.get(GlobalPreferencesService); + const globalPrefService = container.get(MeetStorageService); let authMode: AuthMode; try { diff --git a/backend/src/server.ts b/backend/src/server.ts index bc1713f..463a9b1 100644 --- a/backend/src/server.ts +++ b/backend/src/server.ts @@ -25,7 +25,7 @@ import { recordingRouter, roomRouter } from './routes/index.js'; -import { GlobalPreferencesService } from './services/index.js'; +import { MeetStorageService } from './services/index.js'; import { internalParticipantsRouter } from './routes/participants.routes.js'; import cookieParser from 'cookie-parser'; @@ -76,7 +76,7 @@ const createApp = () => { }; const initializeGlobalPreferences = async () => { - const globalPreferencesService = container.get(GlobalPreferencesService); + const globalPreferencesService = container.get(MeetStorageService); // TODO: This should be invoked in the constructor of the service await globalPreferencesService.ensurePreferencesInitialized(); }; diff --git a/backend/src/services/auth.service.ts b/backend/src/services/auth.service.ts index 59480d8..2f48371 100644 --- a/backend/src/services/auth.service.ts +++ b/backend/src/services/auth.service.ts @@ -2,7 +2,7 @@ import { MEET_ADMIN_SECRET, MEET_ADMIN_USER } from '../environment.js'; import { inject, injectable } from '../config/dependency-injector.config.js'; import { User } from '@typings-ce'; import { UserService } from './user.service.js'; -import { GlobalPreferencesService } from './preferences/global-preferences.service.js'; +import { MeetStorageService } from './storage/storage.service.js'; import { LoggerService } from './logger.service.js'; import { PasswordHelper } from '../helpers/password.helper.js'; @@ -11,7 +11,7 @@ export class AuthService { constructor( @inject(LoggerService) protected logger: LoggerService, @inject(UserService) protected userService: UserService, - @inject(GlobalPreferencesService) protected globalPrefService: GlobalPreferencesService + @inject(MeetStorageService) protected globalPrefService: MeetStorageService ) {} async authenticate(username: string, password: string): Promise { diff --git a/backend/src/services/index.ts b/backend/src/services/index.ts index 7e7aedb..52aa05b 100644 --- a/backend/src/services/index.ts +++ b/backend/src/services/index.ts @@ -10,9 +10,9 @@ export * from './openvidu-webhook.service.js'; export * from './system-event.service.js'; export * from './task-scheduler.service.js'; export * from './mutex.service.js'; -export * from './preferences/index.js'; +export * from './storage/index.js'; export * from './redis.service.js'; export * from './s3.service.js'; -export * from './preferences/s3-preferences-storage.js'; +export * from './storage/providers/s3-storage.js'; export * from './token.service.js'; export * from './user.service.js'; diff --git a/backend/src/services/openvidu-webhook.service.ts b/backend/src/services/openvidu-webhook.service.ts index 3e312fe..db0e112 100644 --- a/backend/src/services/openvidu-webhook.service.ts +++ b/backend/src/services/openvidu-webhook.service.ts @@ -10,13 +10,13 @@ import { MeetWebhookPayload, WebhookPreferences } from '@typings-ce'; -import { GlobalPreferencesService } from './preferences/global-preferences.service.js'; +import { MeetStorageService } from './storage/storage.service.js'; @injectable() export class OpenViduWebhookService { constructor( @inject(LoggerService) protected logger: LoggerService, - @inject(GlobalPreferencesService) protected globalPrefService: GlobalPreferencesService + @inject(MeetStorageService) protected globalPrefService: MeetStorageService ) {} // TODO: Implement Room webhooks diff --git a/backend/src/services/preferences/global-preferences.factory.ts b/backend/src/services/preferences/global-preferences.factory.ts deleted file mode 100644 index 327edf5..0000000 --- a/backend/src/services/preferences/global-preferences.factory.ts +++ /dev/null @@ -1,31 +0,0 @@ -/** - * Factory class to determine and instantiate the appropriate preferences storage - * mechanism (e.g., Database or S3), based on the configuration of the application. - */ - -import { PreferencesStorage } from './global-preferences-storage.interface.js'; -import { S3PreferenceStorage } from './s3-preferences-storage.js'; -import { MEET_PREFERENCES_STORAGE_MODE } from '../../environment.js'; -import { inject, injectable } from '../../config/dependency-injector.config.js'; -import { LoggerService } from '../logger.service.js'; - -@injectable() -export class GlobalPreferencesStorageFactory { - constructor( - @inject(S3PreferenceStorage) protected s3PreferenceStorage: S3PreferenceStorage, - @inject(LoggerService) protected logger: LoggerService - ) {} - - create(): PreferencesStorage { - const storageMode = MEET_PREFERENCES_STORAGE_MODE; - - switch (storageMode) { - case 's3': - return this.s3PreferenceStorage; - - default: - this.logger.info('No preferences storage mode specified. Defaulting to S3.'); - return this.s3PreferenceStorage; - } - } -} diff --git a/backend/src/services/preferences/index.ts b/backend/src/services/preferences/index.ts deleted file mode 100644 index 10499cd..0000000 --- a/backend/src/services/preferences/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -export * from './global-preferences.service.js'; -export * from './global-preferences-storage.interface.js'; -export * from './global-preferences.factory.js'; \ No newline at end of file diff --git a/backend/src/services/storage/index.ts b/backend/src/services/storage/index.ts new file mode 100644 index 0000000..5dc0800 --- /dev/null +++ b/backend/src/services/storage/index.ts @@ -0,0 +1,4 @@ +export * from './storage.service.js'; +export * from './storage.interface.js'; +export * from './storage.factory.js'; +export * from './providers/s3-storage.js'; diff --git a/backend/src/services/preferences/s3-preferences-storage.ts b/backend/src/services/storage/providers/s3-storage.ts similarity index 71% rename from backend/src/services/preferences/s3-preferences-storage.ts rename to backend/src/services/storage/providers/s3-storage.ts index 46ed2eb..1c30dd7 100644 --- a/backend/src/services/preferences/s3-preferences-storage.ts +++ b/backend/src/services/storage/providers/s3-storage.ts @@ -1,23 +1,33 @@ -/** - * Implements storage for preferences using S3. - * This is used when the application is configured to operate in "s3" mode. - */ - import { GlobalPreferences, MeetRoom } from '@typings-ce'; -import { PreferencesStorage } from './global-preferences-storage.interface.js'; -import { S3Service } from '../s3.service.js'; -import { LoggerService } from '../logger.service.js'; -import { RedisService } from '../redis.service.js'; -import { OpenViduMeetError } from '../../models/error.model.js'; -import { inject, injectable } from '../../config/dependency-injector.config.js'; -import { MEET_S3_ROOMS_PREFIX } from '../../environment.js'; +import { StorageProvider } from '../storage.interface.js'; +import { S3Service } from '../../s3.service.js'; +import { LoggerService } from '../../logger.service.js'; +import { RedisService } from '../../redis.service.js'; +import { OpenViduMeetError } from '../../../models/error.model.js'; +import { inject, injectable } from '../../../config/dependency-injector.config.js'; +import { MEET_S3_ROOMS_PREFIX } from '../../../environment.js'; -// TODO Rename this service to MeetStorageService? + +/** + * Implementation of the StorageProvider interface using AWS S3 for persistent storage + * with Redis caching for improved performance. + * + * This class provides operations for storing and retrieving application preferences and room data + * with a two-tiered storage approach: + * - Redis is used as a primary cache for fast access + * - S3 serves as the persistent storage layer and fallback when data is not in Redis + * + * The storage operations are performed in parallel to both systems when writing data, + * with transaction-like rollback behavior if one operation fails. + * + * @template G - Type for global preferences data, defaults to GlobalPreferences + * @template R - Type for room data, defaults to MeetRoom + * + * @implements {StorageProvider} + */ @injectable() -export class S3PreferenceStorage< - G extends GlobalPreferences = GlobalPreferences, - R extends MeetRoom = MeetRoom -> implements PreferencesStorage +export class S3Storage + implements StorageProvider { protected readonly GLOBAL_PREFERENCES_KEY = 'openvidu-meet-preferences'; constructor( @@ -79,7 +89,7 @@ export class S3PreferenceStorage< } } - async saveOpenViduRoom(ovRoom: R): Promise { + async saveMeetRoom(ovRoom: R): Promise { const { roomId } = ovRoom; const s3Path = `${MEET_S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`; const roomStr = JSON.stringify(ovRoom); @@ -122,21 +132,34 @@ export class S3PreferenceStorage< throw error; } - async getOpenViduRooms(): Promise { + async getMeetRooms( + maxItems: number, + nextPageToken?: string + ): Promise<{ + rooms: R[]; + isTruncated: boolean; + nextPageToken?: string; + }> { try { - const content = await this.s3Service.listObjects(MEET_S3_ROOMS_PREFIX); - const roomFiles = - content.Contents?.filter( - (file) => - file?.Key?.endsWith('.json') && - file.Key !== `${this.GLOBAL_PREFERENCES_KEY}.json` - ) ?? []; + const { + Contents: roomFiles, + IsTruncated, + NextContinuationToken + } = await this.s3Service.listObjectsPaginated(MEET_S3_ROOMS_PREFIX, maxItems, nextPageToken); - if (roomFiles.length === 0) { - this.logger.verbose('No OpenVidu rooms found in S3'); - return []; + if (!roomFiles) { + this.logger.verbose('No rooms found. Returning an empty array.'); + return { rooms: [], isTruncated: false }; } + // const promises: Promise[] = []; + // // Retrieve the data for each room + // roomFiles.forEach((item) => { + // if (item?.Key && item.Key.endsWith('.json')) { + // promises.push(getOpenViduRoom(item.Key) as Promise); + // } + // }); + // Extract room names from file paths const roomIds = roomFiles.map((file) => this.extractRoomId(file.Key)).filter(Boolean) as string[]; // Fetch room preferences in parallel @@ -145,7 +168,7 @@ export class S3PreferenceStorage< if (!roomId) return null; try { - return await this.getOpenViduRoom(roomId); + return await this.getMeetRoom(roomId); } catch (error: any) { this.logger.warn(`Failed to fetch room "${roomId}": ${error.message}`); return null; @@ -154,10 +177,11 @@ export class S3PreferenceStorage< ); // Filter out null values - return rooms.filter(Boolean) as R[]; + const roomsResponse = rooms.filter(Boolean) as R[]; + return { rooms: roomsResponse, isTruncated: !!IsTruncated, nextPageToken: NextContinuationToken }; } catch (error) { this.handleError(error, 'Error fetching Room preferences'); - return []; + return { rooms: [], isTruncated: false }; } } @@ -181,7 +205,7 @@ export class S3PreferenceStorage< return parts[parts.length - 2]; } - async getOpenViduRoom(roomId: string): Promise { + async getMeetRoom(roomId: string): Promise { try { const room: R | null = await this.getFromRedis(roomId); @@ -198,7 +222,7 @@ export class S3PreferenceStorage< } } - async deleteOpenViduRoom(roomId: string): Promise { + async deleteMeetRoom(roomId: string): Promise { try { await Promise.all([ this.s3Service.deleteObject(`${MEET_S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`), diff --git a/backend/src/services/storage/storage.factory.ts b/backend/src/services/storage/storage.factory.ts new file mode 100644 index 0000000..cc54478 --- /dev/null +++ b/backend/src/services/storage/storage.factory.ts @@ -0,0 +1,32 @@ +import { StorageProvider } from './storage.interface.js'; +import { S3Storage } from './providers/s3-storage.js'; +import { MEET_PREFERENCES_STORAGE_MODE } from '../../environment.js'; +import { inject, injectable } from '../../config/dependency-injector.config.js'; +import { LoggerService } from '../logger.service.js'; + +/** + * Factory class responsible for creating the appropriate storage provider based on configuration. + * + * This factory determines which storage implementation to use based on the `MEET_PREFERENCES_STORAGE_MODE` + * environment variable. Currently supports S3 storage, with more providers potentially added in the future. + */ +@injectable() +export class StorageFactory { + constructor( + @inject(S3Storage) protected s3Storage: S3Storage, + @inject(LoggerService) protected logger: LoggerService + ) {} + + create(): StorageProvider { + const storageMode = MEET_PREFERENCES_STORAGE_MODE; + + switch (storageMode) { + case 's3': + return this.s3Storage; + + default: + this.logger.info('No preferences storage mode specified. Defaulting to S3.'); + return this.s3Storage; + } + } +} diff --git a/backend/src/services/preferences/global-preferences-storage.interface.ts b/backend/src/services/storage/storage.interface.ts similarity index 54% rename from backend/src/services/preferences/global-preferences-storage.interface.ts rename to backend/src/services/storage/storage.interface.ts index cce55d1..f4ffde4 100644 --- a/backend/src/services/preferences/global-preferences-storage.interface.ts +++ b/backend/src/services/storage/storage.interface.ts @@ -1,13 +1,17 @@ import { GlobalPreferences, MeetRoom } from '@typings-ce'; /** - * Interface for managing global preferences storage. + * An interface that defines the contract for storage providers in the OpenVidu Meet application. + * Storage providers handle persistence of global application preferences and meeting room data. + * + * @template T - The type of global preferences, extending GlobalPreferences + * @template R - The type of room data, extending MeetRoom + * + * Implementations of this interface should handle the persistent storage + * of application settings and room information, which could be backed by + * various storage solutions (database, file system, cloud storage, etc.). */ - -export interface PreferencesStorage< - T extends GlobalPreferences = GlobalPreferences, - R extends MeetRoom = MeetRoom -> { +export interface StorageProvider { /** * Initializes the storage with default preferences if they are not already set. * @@ -31,7 +35,14 @@ export interface PreferencesStorage< */ saveGlobalPreferences(preferences: T): Promise; - getOpenViduRooms(): Promise; + getMeetRooms( + maxItems?: number, + nextPageToken?: string + ): Promise<{ + rooms: R[]; + isTruncated: boolean; + nextPageToken?: string; + }>; /** * Retrieves the {@link MeetRoom}. @@ -39,21 +50,21 @@ export interface PreferencesStorage< * @param roomId - The name of the room to retrieve. * @returns A promise that resolves to the OpenVidu Room, or null if not found. **/ - getOpenViduRoom(roomId: string): Promise; + getMeetRoom(roomId: string): Promise; /** - * Saves the OpenVidu Room. + * Saves the OpenVidu Meet Room. * * @param ovRoom - The OpenVidu Room to save. * @returns A promise that resolves to the saved **/ - saveOpenViduRoom(ovRoom: R): Promise; + saveMeetRoom(ovRoom: R): Promise; /** - * Deletes the OpenVidu Room for a given room name. + * Deletes the OpenVidu Meet Room for a given room name. * * @param roomId - The name of the room whose should be deleted. * @returns A promise that resolves when the room have been deleted. **/ - deleteOpenViduRoom(roomId: string): Promise; + deleteMeetRoom(roomId: string): Promise; } diff --git a/backend/src/services/preferences/global-preferences.service.ts b/backend/src/services/storage/storage.service.ts similarity index 74% rename from backend/src/services/preferences/global-preferences.service.ts rename to backend/src/services/storage/storage.service.ts index ef6a35c..cbbe2d0 100644 --- a/backend/src/services/preferences/global-preferences.service.ts +++ b/backend/src/services/storage/storage.service.ts @@ -1,28 +1,29 @@ -/** - * Service that provides high-level methods for managing application preferences, - * regardless of the underlying storage mechanism. - */ - import { AuthMode, AuthType, GlobalPreferences, MeetRoom, MeetRoomPreferences } from '@typings-ce'; import { LoggerService } from '../logger.service.js'; -import { PreferencesStorage } from './global-preferences-storage.interface.js'; -import { GlobalPreferencesStorageFactory } from './global-preferences.factory.js'; +import { StorageProvider } from './storage.interface.js'; +import { StorageFactory } from './storage.factory.js'; import { errorRoomNotFound, OpenViduMeetError } from '../../models/error.model.js'; import { MEET_NAME_ID, MEET_SECRET, MEET_USER, MEET_WEBHOOK_ENABLED, MEET_WEBHOOK_URL } from '../../environment.js'; import { injectable, inject } from '../../config/dependency-injector.config.js'; import { PasswordHelper } from '../../helpers/password.helper.js'; +/** + * A service for managing storage operations related to OpenVidu Meet rooms and preferences. + * + * This service provides an abstraction layer over the underlying storage implementation, + * handling initialization, retrieval, and persistence of global preferences and room data. + * + * @typeParam G - Type for global preferences, extends GlobalPreferences + * @typeParam R - Type for room data, extends MeetRoom + */ @injectable() -export class GlobalPreferencesService< - G extends GlobalPreferences = GlobalPreferences, - R extends MeetRoom = MeetRoom -> { - protected storage: PreferencesStorage; +export class MeetStorageService { + protected storageProvider: StorageProvider; constructor( @inject(LoggerService) protected logger: LoggerService, - @inject(GlobalPreferencesStorageFactory) protected storageFactory: GlobalPreferencesStorageFactory + @inject(StorageFactory) protected storageFactory: StorageFactory ) { - this.storage = this.storageFactory.create(); + this.storageProvider = this.storageFactory.create(); } /** @@ -33,7 +34,7 @@ export class GlobalPreferencesService< const preferences = await this.getDefaultPreferences(); try { - await this.storage.initialize(preferences); + await this.storageProvider.initialize(preferences); return preferences as G; } catch (error) { this.handleError(error, 'Error initializing default preferences'); @@ -46,7 +47,7 @@ export class GlobalPreferencesService< * @returns {Promise} */ async getGlobalPreferences(): Promise { - const preferences = await this.storage.getGlobalPreferences(); + const preferences = await this.storageProvider.getGlobalPreferences(); if (preferences) return preferences as G; @@ -60,16 +61,27 @@ export class GlobalPreferencesService< */ async saveGlobalPreferences(preferences: G): Promise { this.logger.info('Saving global preferences'); - return this.storage.saveGlobalPreferences(preferences) as Promise; + return this.storageProvider.saveGlobalPreferences(preferences) as Promise; } async saveOpenViduRoom(ovRoom: R): Promise { this.logger.info(`Saving OpenVidu room ${ovRoom.roomId}`); - return this.storage.saveOpenViduRoom(ovRoom) as Promise; + return this.storageProvider.saveMeetRoom(ovRoom) as Promise; } - async getOpenViduRooms(): Promise { - return this.storage.getOpenViduRooms() as Promise; + async getOpenViduRooms( + maxItems?: number, + nextPageToken?: string + ): Promise<{ + rooms: R[]; + isTruncated: boolean; + nextPageToken?: string; + }> { + return this.storageProvider.getMeetRooms(maxItems, nextPageToken) as Promise<{ + rooms: R[]; + isTruncated: boolean; + nextPageToken?: string; + }>; } /** @@ -80,7 +92,7 @@ export class GlobalPreferencesService< * @throws Error if the room preferences are not found. */ async getOpenViduRoom(roomId: string): Promise { - const openviduRoom = await this.storage.getOpenViduRoom(roomId); + const openviduRoom = await this.storageProvider.getMeetRoom(roomId); if (!openviduRoom) { this.logger.error(`Room not found for room ${roomId}`); @@ -91,7 +103,7 @@ export class GlobalPreferencesService< } async deleteOpenViduRoom(roomId: string): Promise { - return this.storage.deleteOpenViduRoom(roomId); + return this.storageProvider.deleteMeetRoom(roomId); } async getOpenViduRoomPreferences(roomId: string): Promise { @@ -177,7 +189,7 @@ export class GlobalPreferencesService< * @param {any} error * @param {string} message */ - protected handleError(error: any, message: string) { + protected handleError(error: OpenViduMeetError | unknown, message: string) { if (error instanceof OpenViduMeetError) { this.logger.error(`${message}: ${error.message}`); } else { diff --git a/backend/src/services/user.service.ts b/backend/src/services/user.service.ts index 00c53f2..e7db8e2 100644 --- a/backend/src/services/user.service.ts +++ b/backend/src/services/user.service.ts @@ -2,13 +2,13 @@ import { MEET_ADMIN_USER } from '../environment.js'; import { inject, injectable } from '../config/dependency-injector.config.js'; import { UserRole, SingleUserAuth, User, SingleUserCredentials } from '@typings-ce'; import { LoggerService } from './logger.service.js'; -import { GlobalPreferencesService } from './preferences/global-preferences.service.js'; +import { MeetStorageService } from './storage/storage.service.js'; @injectable() export class UserService { constructor( @inject(LoggerService) protected logger: LoggerService, - @inject(GlobalPreferencesService) protected globalPrefService: GlobalPreferencesService + @inject(MeetStorageService) protected globalPrefService: MeetStorageService ) {} async getUser(username: string): Promise {