backend: Refactor storage services and remove global preferences service references
This commit is contained in:
parent
9e3644ab06
commit
12ef04964c
@ -1,8 +1,8 @@
|
|||||||
import { Container } from 'inversify';
|
import { Container } from 'inversify';
|
||||||
import {
|
import {
|
||||||
AuthService,
|
AuthService,
|
||||||
GlobalPreferencesService,
|
MeetStorageService,
|
||||||
GlobalPreferencesStorageFactory,
|
StorageFactory,
|
||||||
LiveKitService,
|
LiveKitService,
|
||||||
LivekitWebhookService,
|
LivekitWebhookService,
|
||||||
LoggerService,
|
LoggerService,
|
||||||
@ -12,7 +12,7 @@ import {
|
|||||||
RecordingService,
|
RecordingService,
|
||||||
RedisService,
|
RedisService,
|
||||||
RoomService,
|
RoomService,
|
||||||
S3PreferenceStorage,
|
S3Storage,
|
||||||
S3Service,
|
S3Service,
|
||||||
SystemEventService,
|
SystemEventService,
|
||||||
TaskSchedulerService,
|
TaskSchedulerService,
|
||||||
@ -47,11 +47,11 @@ const registerDependencies = () => {
|
|||||||
container.bind(RecordingService).toSelf().inSingletonScope();
|
container.bind(RecordingService).toSelf().inSingletonScope();
|
||||||
|
|
||||||
container.bind(LivekitWebhookService).toSelf().inSingletonScope();
|
container.bind(LivekitWebhookService).toSelf().inSingletonScope();
|
||||||
container.bind(GlobalPreferencesService).toSelf().inSingletonScope();
|
container.bind(MeetStorageService).toSelf().inSingletonScope();
|
||||||
container.bind(ParticipantService).toSelf().inSingletonScope();
|
container.bind(ParticipantService).toSelf().inSingletonScope();
|
||||||
|
|
||||||
container.bind(S3PreferenceStorage).toSelf().inSingletonScope();
|
container.bind(S3Storage).toSelf().inSingletonScope();
|
||||||
container.bind(GlobalPreferencesStorageFactory).toSelf().inSingletonScope();
|
container.bind(StorageFactory).toSelf().inSingletonScope();
|
||||||
|
|
||||||
initializeEagerServices();
|
initializeEagerServices();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { container } from '../../config/dependency-injector.config.js';
|
import { container } from '../../config/dependency-injector.config.js';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { LoggerService } from '../../services/logger.service.js';
|
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 { OpenViduMeetError } from '../../models/error.model.js';
|
||||||
|
|
||||||
export const updateRoomPreferences = async (req: Request, res: Response) => {
|
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;
|
const { roomId, roomPreferences } = req.body;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const preferenceService = container.get(GlobalPreferencesService);
|
const preferenceService = container.get(MeetStorageService);
|
||||||
preferenceService.validateRoomPreferences(roomPreferences);
|
preferenceService.validateRoomPreferences(roomPreferences);
|
||||||
|
|
||||||
const savedPreferences = await preferenceService.updateOpenViduRoomPreferences(roomId, roomPreferences);
|
const savedPreferences = await preferenceService.updateOpenViduRoomPreferences(roomId, roomPreferences);
|
||||||
@ -35,7 +35,7 @@ export const getRoomPreferences = async (req: Request, res: Response) => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { roomId } = req.params;
|
const { roomId } = req.params;
|
||||||
const preferenceService = container.get(GlobalPreferencesService);
|
const preferenceService = container.get(MeetStorageService);
|
||||||
const preferences = await preferenceService.getOpenViduRoomPreferences(roomId);
|
const preferences = await preferenceService.getOpenViduRoomPreferences(roomId);
|
||||||
|
|
||||||
if (!preferences) {
|
if (!preferences) {
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { container } from '../../config/dependency-injector.config.js';
|
import { container } from '../../config/dependency-injector.config.js';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { LoggerService } from '../../services/logger.service.js';
|
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 { OpenViduMeetError } from '../../models/error.model.js';
|
||||||
import { SecurityPreferencesDTO, UpdateSecurityPreferencesDTO } from '@typings-ce';
|
import { SecurityPreferencesDTO, UpdateSecurityPreferencesDTO } from '@typings-ce';
|
||||||
|
|
||||||
export const updateSecurityPreferences = async (req: Request, res: Response) => {
|
export const updateSecurityPreferences = async (req: Request, res: Response) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
const globalPrefService = container.get(GlobalPreferencesService);
|
const globalPrefService = container.get(MeetStorageService);
|
||||||
|
|
||||||
logger.verbose(`Updating security preferences: ${JSON.stringify(req.body)}`);
|
logger.verbose(`Updating security preferences: ${JSON.stringify(req.body)}`);
|
||||||
const securityPreferences = req.body as UpdateSecurityPreferencesDTO;
|
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) => {
|
export const getSecurityPreferences = async (_req: Request, res: Response) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
const preferenceService = container.get(GlobalPreferencesService);
|
const preferenceService = container.get(MeetStorageService);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const preferences = await preferenceService.getGlobalPreferences();
|
const preferences = await preferenceService.getGlobalPreferences();
|
||||||
|
|||||||
@ -1,13 +1,13 @@
|
|||||||
import { container } from '../../config/dependency-injector.config.js';
|
import { container } from '../../config/dependency-injector.config.js';
|
||||||
import { Request, Response } from 'express';
|
import { Request, Response } from 'express';
|
||||||
import { LoggerService } from '../../services/logger.service.js';
|
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 { OpenViduMeetError } from '../../models/error.model.js';
|
||||||
import { WebhookPreferences } from '@typings-ce';
|
import { WebhookPreferences } from '@typings-ce';
|
||||||
|
|
||||||
export const updateWebhookPreferences = async (req: Request, res: Response) => {
|
export const updateWebhookPreferences = async (req: Request, res: Response) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
const globalPrefService = container.get(GlobalPreferencesService);
|
const globalPrefService = container.get(MeetStorageService);
|
||||||
|
|
||||||
logger.verbose(`Updating webhooks preferences: ${JSON.stringify(req.body)}`);
|
logger.verbose(`Updating webhooks preferences: ${JSON.stringify(req.body)}`);
|
||||||
const webhookPreferences = req.body as WebhookPreferences;
|
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) => {
|
export const getWebhookPreferences = async (_req: Request, res: Response) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
const preferenceService = container.get(GlobalPreferencesService);
|
const preferenceService = container.get(MeetStorageService);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const preferences = await preferenceService.getGlobalPreferences();
|
const preferences = await preferenceService.getGlobalPreferences();
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { AuthMode, ParticipantRole, UserRole, TokenOptions } from '@typings-ce';
|
import { AuthMode, ParticipantRole, UserRole, TokenOptions } from '@typings-ce';
|
||||||
import { container } from '../config/dependency-injector.config.js';
|
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';
|
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) => {
|
export const configureTokenAuth = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
const globalPrefService = container.get(GlobalPreferencesService);
|
const globalPrefService = container.get(MeetStorageService);
|
||||||
const roomService = container.get(RoomService);
|
const roomService = container.get(RoomService);
|
||||||
|
|
||||||
let role: ParticipantRole;
|
let role: ParticipantRole;
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
import { container } from '../config/dependency-injector.config.js';
|
import { container } from '../config/dependency-injector.config.js';
|
||||||
import { NextFunction, Request, Response } from 'express';
|
import { NextFunction, Request, Response } from 'express';
|
||||||
import { LoggerService } from '../services/logger.service.js';
|
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 { allowAnonymous, apiKeyValidator, tokenAndRoleValidator, withAuth } from './auth.middleware.js';
|
||||||
import { AuthMode, ParticipantRole, UserRole } from '@typings-ce';
|
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) => {
|
export const configureCreateRoomAuth = async (req: Request, res: Response, next: NextFunction) => {
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
const globalPrefService = container.get(GlobalPreferencesService);
|
const globalPrefService = container.get(MeetStorageService);
|
||||||
let allowRoomCreation: boolean;
|
let allowRoomCreation: boolean;
|
||||||
let requireAuthentication: boolean;
|
let requireAuthentication: boolean;
|
||||||
|
|
||||||
@ -70,7 +70,7 @@ export const configureRoomAuthorization = async (req: Request, res: Response, ne
|
|||||||
}
|
}
|
||||||
|
|
||||||
const logger = container.get(LoggerService);
|
const logger = container.get(LoggerService);
|
||||||
const globalPrefService = container.get(GlobalPreferencesService);
|
const globalPrefService = container.get(MeetStorageService);
|
||||||
let authMode: AuthMode;
|
let authMode: AuthMode;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -25,7 +25,7 @@ import {
|
|||||||
recordingRouter,
|
recordingRouter,
|
||||||
roomRouter
|
roomRouter
|
||||||
} from './routes/index.js';
|
} from './routes/index.js';
|
||||||
import { GlobalPreferencesService } from './services/index.js';
|
import { MeetStorageService } from './services/index.js';
|
||||||
import { internalParticipantsRouter } from './routes/participants.routes.js';
|
import { internalParticipantsRouter } from './routes/participants.routes.js';
|
||||||
import cookieParser from 'cookie-parser';
|
import cookieParser from 'cookie-parser';
|
||||||
|
|
||||||
@ -76,7 +76,7 @@ const createApp = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const initializeGlobalPreferences = async () => {
|
const initializeGlobalPreferences = async () => {
|
||||||
const globalPreferencesService = container.get(GlobalPreferencesService);
|
const globalPreferencesService = container.get(MeetStorageService);
|
||||||
// TODO: This should be invoked in the constructor of the service
|
// TODO: This should be invoked in the constructor of the service
|
||||||
await globalPreferencesService.ensurePreferencesInitialized();
|
await globalPreferencesService.ensurePreferencesInitialized();
|
||||||
};
|
};
|
||||||
|
|||||||
@ -2,7 +2,7 @@ import { MEET_ADMIN_SECRET, MEET_ADMIN_USER } from '../environment.js';
|
|||||||
import { inject, injectable } from '../config/dependency-injector.config.js';
|
import { inject, injectable } from '../config/dependency-injector.config.js';
|
||||||
import { User } from '@typings-ce';
|
import { User } from '@typings-ce';
|
||||||
import { UserService } from './user.service.js';
|
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 { LoggerService } from './logger.service.js';
|
||||||
import { PasswordHelper } from '../helpers/password.helper.js';
|
import { PasswordHelper } from '../helpers/password.helper.js';
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export class AuthService {
|
|||||||
constructor(
|
constructor(
|
||||||
@inject(LoggerService) protected logger: LoggerService,
|
@inject(LoggerService) protected logger: LoggerService,
|
||||||
@inject(UserService) protected userService: UserService,
|
@inject(UserService) protected userService: UserService,
|
||||||
@inject(GlobalPreferencesService) protected globalPrefService: GlobalPreferencesService
|
@inject(MeetStorageService) protected globalPrefService: MeetStorageService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async authenticate(username: string, password: string): Promise<User | null> {
|
async authenticate(username: string, password: string): Promise<User | null> {
|
||||||
|
|||||||
@ -10,9 +10,9 @@ export * from './openvidu-webhook.service.js';
|
|||||||
export * from './system-event.service.js';
|
export * from './system-event.service.js';
|
||||||
export * from './task-scheduler.service.js';
|
export * from './task-scheduler.service.js';
|
||||||
export * from './mutex.service.js';
|
export * from './mutex.service.js';
|
||||||
export * from './preferences/index.js';
|
export * from './storage/index.js';
|
||||||
export * from './redis.service.js';
|
export * from './redis.service.js';
|
||||||
export * from './s3.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 './token.service.js';
|
||||||
export * from './user.service.js';
|
export * from './user.service.js';
|
||||||
|
|||||||
@ -10,13 +10,13 @@ import {
|
|||||||
MeetWebhookPayload,
|
MeetWebhookPayload,
|
||||||
WebhookPreferences
|
WebhookPreferences
|
||||||
} from '@typings-ce';
|
} from '@typings-ce';
|
||||||
import { GlobalPreferencesService } from './preferences/global-preferences.service.js';
|
import { MeetStorageService } from './storage/storage.service.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class OpenViduWebhookService {
|
export class OpenViduWebhookService {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LoggerService) protected logger: LoggerService,
|
@inject(LoggerService) protected logger: LoggerService,
|
||||||
@inject(GlobalPreferencesService) protected globalPrefService: GlobalPreferencesService
|
@inject(MeetStorageService) protected globalPrefService: MeetStorageService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
// TODO: Implement Room webhooks
|
// TODO: Implement Room webhooks
|
||||||
|
|||||||
@ -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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,3 +0,0 @@
|
|||||||
export * from './global-preferences.service.js';
|
|
||||||
export * from './global-preferences-storage.interface.js';
|
|
||||||
export * from './global-preferences.factory.js';
|
|
||||||
4
backend/src/services/storage/index.ts
Normal file
4
backend/src/services/storage/index.ts
Normal file
@ -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';
|
||||||
@ -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 { GlobalPreferences, MeetRoom } from '@typings-ce';
|
||||||
import { PreferencesStorage } from './global-preferences-storage.interface.js';
|
import { StorageProvider } from '../storage.interface.js';
|
||||||
import { S3Service } from '../s3.service.js';
|
import { S3Service } from '../../s3.service.js';
|
||||||
import { LoggerService } from '../logger.service.js';
|
import { LoggerService } from '../../logger.service.js';
|
||||||
import { RedisService } from '../redis.service.js';
|
import { RedisService } from '../../redis.service.js';
|
||||||
import { OpenViduMeetError } from '../../models/error.model.js';
|
import { OpenViduMeetError } from '../../../models/error.model.js';
|
||||||
import { inject, injectable } from '../../config/dependency-injector.config.js';
|
import { inject, injectable } from '../../../config/dependency-injector.config.js';
|
||||||
import { MEET_S3_ROOMS_PREFIX } from '../../environment.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()
|
@injectable()
|
||||||
export class S3PreferenceStorage<
|
export class S3Storage<G extends GlobalPreferences = GlobalPreferences, R extends MeetRoom = MeetRoom>
|
||||||
G extends GlobalPreferences = GlobalPreferences,
|
implements StorageProvider
|
||||||
R extends MeetRoom = MeetRoom
|
|
||||||
> implements PreferencesStorage
|
|
||||||
{
|
{
|
||||||
protected readonly GLOBAL_PREFERENCES_KEY = 'openvidu-meet-preferences';
|
protected readonly GLOBAL_PREFERENCES_KEY = 'openvidu-meet-preferences';
|
||||||
constructor(
|
constructor(
|
||||||
@ -79,7 +89,7 @@ export class S3PreferenceStorage<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveOpenViduRoom(ovRoom: R): Promise<R> {
|
async saveMeetRoom(ovRoom: R): Promise<R> {
|
||||||
const { roomId } = ovRoom;
|
const { roomId } = ovRoom;
|
||||||
const s3Path = `${MEET_S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`;
|
const s3Path = `${MEET_S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`;
|
||||||
const roomStr = JSON.stringify(ovRoom);
|
const roomStr = JSON.stringify(ovRoom);
|
||||||
@ -122,21 +132,34 @@ export class S3PreferenceStorage<
|
|||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOpenViduRooms(): Promise<R[]> {
|
async getMeetRooms(
|
||||||
|
maxItems: number,
|
||||||
|
nextPageToken?: string
|
||||||
|
): Promise<{
|
||||||
|
rooms: R[];
|
||||||
|
isTruncated: boolean;
|
||||||
|
nextPageToken?: string;
|
||||||
|
}> {
|
||||||
try {
|
try {
|
||||||
const content = await this.s3Service.listObjects(MEET_S3_ROOMS_PREFIX);
|
const {
|
||||||
const roomFiles =
|
Contents: roomFiles,
|
||||||
content.Contents?.filter(
|
IsTruncated,
|
||||||
(file) =>
|
NextContinuationToken
|
||||||
file?.Key?.endsWith('.json') &&
|
} = await this.s3Service.listObjectsPaginated(MEET_S3_ROOMS_PREFIX, maxItems, nextPageToken);
|
||||||
file.Key !== `${this.GLOBAL_PREFERENCES_KEY}.json`
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
if (roomFiles.length === 0) {
|
if (!roomFiles) {
|
||||||
this.logger.verbose('No OpenVidu rooms found in S3');
|
this.logger.verbose('No rooms found. Returning an empty array.');
|
||||||
return [];
|
return { rooms: [], isTruncated: false };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// const promises: Promise<R>[] = [];
|
||||||
|
// // Retrieve the data for each room
|
||||||
|
// roomFiles.forEach((item) => {
|
||||||
|
// if (item?.Key && item.Key.endsWith('.json')) {
|
||||||
|
// promises.push(getOpenViduRoom(item.Key) as Promise<R>);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
|
||||||
// Extract room names from file paths
|
// Extract room names from file paths
|
||||||
const roomIds = roomFiles.map((file) => this.extractRoomId(file.Key)).filter(Boolean) as string[];
|
const roomIds = roomFiles.map((file) => this.extractRoomId(file.Key)).filter(Boolean) as string[];
|
||||||
// Fetch room preferences in parallel
|
// Fetch room preferences in parallel
|
||||||
@ -145,7 +168,7 @@ export class S3PreferenceStorage<
|
|||||||
if (!roomId) return null;
|
if (!roomId) return null;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
return await this.getOpenViduRoom(roomId);
|
return await this.getMeetRoom(roomId);
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
this.logger.warn(`Failed to fetch room "${roomId}": ${error.message}`);
|
this.logger.warn(`Failed to fetch room "${roomId}": ${error.message}`);
|
||||||
return null;
|
return null;
|
||||||
@ -154,10 +177,11 @@ export class S3PreferenceStorage<
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Filter out null values
|
// 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) {
|
} catch (error) {
|
||||||
this.handleError(error, 'Error fetching Room preferences');
|
this.handleError(error, 'Error fetching Room preferences');
|
||||||
return [];
|
return { rooms: [], isTruncated: false };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -181,7 +205,7 @@ export class S3PreferenceStorage<
|
|||||||
return parts[parts.length - 2];
|
return parts[parts.length - 2];
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOpenViduRoom(roomId: string): Promise<R | null> {
|
async getMeetRoom(roomId: string): Promise<R | null> {
|
||||||
try {
|
try {
|
||||||
const room: R | null = await this.getFromRedis<R>(roomId);
|
const room: R | null = await this.getFromRedis<R>(roomId);
|
||||||
|
|
||||||
@ -198,7 +222,7 @@ export class S3PreferenceStorage<
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteOpenViduRoom(roomId: string): Promise<void> {
|
async deleteMeetRoom(roomId: string): Promise<void> {
|
||||||
try {
|
try {
|
||||||
await Promise.all([
|
await Promise.all([
|
||||||
this.s3Service.deleteObject(`${MEET_S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`),
|
this.s3Service.deleteObject(`${MEET_S3_ROOMS_PREFIX}/${roomId}/${roomId}.json`),
|
||||||
32
backend/src/services/storage/storage.factory.ts
Normal file
32
backend/src/services/storage/storage.factory.ts
Normal file
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,13 +1,17 @@
|
|||||||
import { GlobalPreferences, MeetRoom } from '@typings-ce';
|
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 StorageProvider<T extends GlobalPreferences = GlobalPreferences, R extends MeetRoom = MeetRoom> {
|
||||||
export interface PreferencesStorage<
|
|
||||||
T extends GlobalPreferences = GlobalPreferences,
|
|
||||||
R extends MeetRoom = MeetRoom
|
|
||||||
> {
|
|
||||||
/**
|
/**
|
||||||
* Initializes the storage with default preferences if they are not already set.
|
* Initializes the storage with default preferences if they are not already set.
|
||||||
*
|
*
|
||||||
@ -31,7 +35,14 @@ export interface PreferencesStorage<
|
|||||||
*/
|
*/
|
||||||
saveGlobalPreferences(preferences: T): Promise<T>;
|
saveGlobalPreferences(preferences: T): Promise<T>;
|
||||||
|
|
||||||
getOpenViduRooms(): Promise<R[]>;
|
getMeetRooms(
|
||||||
|
maxItems?: number,
|
||||||
|
nextPageToken?: string
|
||||||
|
): Promise<{
|
||||||
|
rooms: R[];
|
||||||
|
isTruncated: boolean;
|
||||||
|
nextPageToken?: string;
|
||||||
|
}>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves the {@link MeetRoom}.
|
* Retrieves the {@link MeetRoom}.
|
||||||
@ -39,21 +50,21 @@ export interface PreferencesStorage<
|
|||||||
* @param roomId - The name of the room to retrieve.
|
* @param roomId - The name of the room to retrieve.
|
||||||
* @returns A promise that resolves to the OpenVidu Room, or null if not found.
|
* @returns A promise that resolves to the OpenVidu Room, or null if not found.
|
||||||
**/
|
**/
|
||||||
getOpenViduRoom(roomId: string): Promise<R | null>;
|
getMeetRoom(roomId: string): Promise<R | null>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Saves the OpenVidu Room.
|
* Saves the OpenVidu Meet Room.
|
||||||
*
|
*
|
||||||
* @param ovRoom - The OpenVidu Room to save.
|
* @param ovRoom - The OpenVidu Room to save.
|
||||||
* @returns A promise that resolves to the saved
|
* @returns A promise that resolves to the saved
|
||||||
**/
|
**/
|
||||||
saveOpenViduRoom(ovRoom: R): Promise<R>;
|
saveMeetRoom(ovRoom: R): Promise<R>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
* @param roomId - The name of the room whose should be deleted.
|
||||||
* @returns A promise that resolves when the room have been deleted.
|
* @returns A promise that resolves when the room have been deleted.
|
||||||
**/
|
**/
|
||||||
deleteOpenViduRoom(roomId: string): Promise<void>;
|
deleteMeetRoom(roomId: string): Promise<void>;
|
||||||
}
|
}
|
||||||
@ -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 { AuthMode, AuthType, GlobalPreferences, MeetRoom, MeetRoomPreferences } from '@typings-ce';
|
||||||
import { LoggerService } from '../logger.service.js';
|
import { LoggerService } from '../logger.service.js';
|
||||||
import { PreferencesStorage } from './global-preferences-storage.interface.js';
|
import { StorageProvider } from './storage.interface.js';
|
||||||
import { GlobalPreferencesStorageFactory } from './global-preferences.factory.js';
|
import { StorageFactory } from './storage.factory.js';
|
||||||
import { errorRoomNotFound, OpenViduMeetError } from '../../models/error.model.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 { 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 { injectable, inject } from '../../config/dependency-injector.config.js';
|
||||||
import { PasswordHelper } from '../../helpers/password.helper.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()
|
@injectable()
|
||||||
export class GlobalPreferencesService<
|
export class MeetStorageService<G extends GlobalPreferences = GlobalPreferences, R extends MeetRoom = MeetRoom> {
|
||||||
G extends GlobalPreferences = GlobalPreferences,
|
protected storageProvider: StorageProvider;
|
||||||
R extends MeetRoom = MeetRoom
|
|
||||||
> {
|
|
||||||
protected storage: PreferencesStorage;
|
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LoggerService) protected logger: LoggerService,
|
@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();
|
const preferences = await this.getDefaultPreferences();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await this.storage.initialize(preferences);
|
await this.storageProvider.initialize(preferences);
|
||||||
return preferences as G;
|
return preferences as G;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.handleError(error, 'Error initializing default preferences');
|
this.handleError(error, 'Error initializing default preferences');
|
||||||
@ -46,7 +47,7 @@ export class GlobalPreferencesService<
|
|||||||
* @returns {Promise<GlobalPreferences>}
|
* @returns {Promise<GlobalPreferences>}
|
||||||
*/
|
*/
|
||||||
async getGlobalPreferences(): Promise<G> {
|
async getGlobalPreferences(): Promise<G> {
|
||||||
const preferences = await this.storage.getGlobalPreferences();
|
const preferences = await this.storageProvider.getGlobalPreferences();
|
||||||
|
|
||||||
if (preferences) return preferences as G;
|
if (preferences) return preferences as G;
|
||||||
|
|
||||||
@ -60,16 +61,27 @@ export class GlobalPreferencesService<
|
|||||||
*/
|
*/
|
||||||
async saveGlobalPreferences(preferences: G): Promise<G> {
|
async saveGlobalPreferences(preferences: G): Promise<G> {
|
||||||
this.logger.info('Saving global preferences');
|
this.logger.info('Saving global preferences');
|
||||||
return this.storage.saveGlobalPreferences(preferences) as Promise<G>;
|
return this.storageProvider.saveGlobalPreferences(preferences) as Promise<G>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async saveOpenViduRoom(ovRoom: R): Promise<R> {
|
async saveOpenViduRoom(ovRoom: R): Promise<R> {
|
||||||
this.logger.info(`Saving OpenVidu room ${ovRoom.roomId}`);
|
this.logger.info(`Saving OpenVidu room ${ovRoom.roomId}`);
|
||||||
return this.storage.saveOpenViduRoom(ovRoom) as Promise<R>;
|
return this.storageProvider.saveMeetRoom(ovRoom) as Promise<R>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOpenViduRooms(): Promise<R[]> {
|
async getOpenViduRooms(
|
||||||
return this.storage.getOpenViduRooms() as Promise<R[]>;
|
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.
|
* @throws Error if the room preferences are not found.
|
||||||
*/
|
*/
|
||||||
async getOpenViduRoom(roomId: string): Promise<R> {
|
async getOpenViduRoom(roomId: string): Promise<R> {
|
||||||
const openviduRoom = await this.storage.getOpenViduRoom(roomId);
|
const openviduRoom = await this.storageProvider.getMeetRoom(roomId);
|
||||||
|
|
||||||
if (!openviduRoom) {
|
if (!openviduRoom) {
|
||||||
this.logger.error(`Room not found for room ${roomId}`);
|
this.logger.error(`Room not found for room ${roomId}`);
|
||||||
@ -91,7 +103,7 @@ export class GlobalPreferencesService<
|
|||||||
}
|
}
|
||||||
|
|
||||||
async deleteOpenViduRoom(roomId: string): Promise<void> {
|
async deleteOpenViduRoom(roomId: string): Promise<void> {
|
||||||
return this.storage.deleteOpenViduRoom(roomId);
|
return this.storageProvider.deleteMeetRoom(roomId);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getOpenViduRoomPreferences(roomId: string): Promise<MeetRoomPreferences> {
|
async getOpenViduRoomPreferences(roomId: string): Promise<MeetRoomPreferences> {
|
||||||
@ -177,7 +189,7 @@ export class GlobalPreferencesService<
|
|||||||
* @param {any} error
|
* @param {any} error
|
||||||
* @param {string} message
|
* @param {string} message
|
||||||
*/
|
*/
|
||||||
protected handleError(error: any, message: string) {
|
protected handleError(error: OpenViduMeetError | unknown, message: string) {
|
||||||
if (error instanceof OpenViduMeetError) {
|
if (error instanceof OpenViduMeetError) {
|
||||||
this.logger.error(`${message}: ${error.message}`);
|
this.logger.error(`${message}: ${error.message}`);
|
||||||
} else {
|
} else {
|
||||||
@ -2,13 +2,13 @@ import { MEET_ADMIN_USER } from '../environment.js';
|
|||||||
import { inject, injectable } from '../config/dependency-injector.config.js';
|
import { inject, injectable } from '../config/dependency-injector.config.js';
|
||||||
import { UserRole, SingleUserAuth, User, SingleUserCredentials } from '@typings-ce';
|
import { UserRole, SingleUserAuth, User, SingleUserCredentials } from '@typings-ce';
|
||||||
import { LoggerService } from './logger.service.js';
|
import { LoggerService } from './logger.service.js';
|
||||||
import { GlobalPreferencesService } from './preferences/global-preferences.service.js';
|
import { MeetStorageService } from './storage/storage.service.js';
|
||||||
|
|
||||||
@injectable()
|
@injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
constructor(
|
constructor(
|
||||||
@inject(LoggerService) protected logger: LoggerService,
|
@inject(LoggerService) protected logger: LoggerService,
|
||||||
@inject(GlobalPreferencesService) protected globalPrefService: GlobalPreferencesService
|
@inject(MeetStorageService) protected globalPrefService: MeetStorageService
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getUser(username: string): Promise<User | null> {
|
async getUser(username: string): Promise<User | null> {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user