From 3d8a82a18d30fba1a987c42136dd1fc635d26f40 Mon Sep 17 00:00:00 2001 From: juancarmore Date: Mon, 29 Sep 2025 14:55:05 +0200 Subject: [PATCH] backend: enhance appearance configuration handling and validation in global settings --- .../appearance-config.controller.ts | 26 ++++++------ .../room-validator.middleware.ts | 6 ++- backend/src/models/error.model.ts | 6 --- .../providers/gcp/gcs-storage.provider.ts | 2 +- .../storage/providers/gcp/gcs.service.ts | 1 + .../src/services/storage/storage.service.ts | 12 ++++-- typings/src/global-config.ts | 2 +- typings/src/room-config.ts | 42 +++++++++---------- 8 files changed, 52 insertions(+), 45 deletions(-) diff --git a/backend/src/controllers/global-config/appearance-config.controller.ts b/backend/src/controllers/global-config/appearance-config.controller.ts index 8766988..568d8e2 100644 --- a/backend/src/controllers/global-config/appearance-config.controller.ts +++ b/backend/src/controllers/global-config/appearance-config.controller.ts @@ -1,11 +1,7 @@ import { MeetAppearanceConfig } from '@typings-ce'; import { Request, Response } from 'express'; import { container } from '../../config/index.js'; -import { - errorRoomsAppearanceConfigNotDefined, - handleError, - rejectRequestFromMeetError -} from '../../models/error.model.js'; +import { handleError } from '../../models/error.model.js'; import { LoggerService, MeetStorageService } from '../../services/index.js'; export const updateRoomsAppearanceConfig = async (req: Request, res: Response) => { @@ -17,6 +13,18 @@ export const updateRoomsAppearanceConfig = async (req: Request, res: Response) = try { const globalConfig = await storageService.getGlobalConfig(); + + if (globalConfig.roomsConfig.appearance.themes.length > 0) { + // Preserve existing theme colors if they are not provided in the update + const existingTheme = globalConfig.roomsConfig.appearance.themes[0]; + const newTheme = appearanceConfig.appearance.themes[0]; + + newTheme.backgroundColor = newTheme.backgroundColor || existingTheme.backgroundColor; + newTheme.primaryColor = newTheme.primaryColor || existingTheme.primaryColor; + newTheme.secondaryColor = newTheme.secondaryColor || existingTheme.secondaryColor; + newTheme.surfaceColor = newTheme.surfaceColor || existingTheme.surfaceColor; + } + globalConfig.roomsConfig = appearanceConfig; await storageService.saveGlobalConfig(globalConfig); @@ -34,13 +42,7 @@ export const getRoomsAppearanceConfig = async (_req: Request, res: Response) => try { const globalConfig = await storageService.getGlobalConfig(); - const appearanceConfig = globalConfig.roomsConfig?.appearance; - - if (!appearanceConfig) { - const error = errorRoomsAppearanceConfigNotDefined(); - return rejectRequestFromMeetError(res, error); - } - + const appearanceConfig = globalConfig.roomsConfig.appearance; return res.status(200).json({ appearance: appearanceConfig }); } catch (error) { handleError(res, error, 'getting rooms appearance config'); diff --git a/backend/src/middlewares/request-validators/room-validator.middleware.ts b/backend/src/middlewares/request-validators/room-validator.middleware.ts index 28a8142..60f7eb7 100644 --- a/backend/src/middlewares/request-validators/room-validator.middleware.ts +++ b/backend/src/middlewares/request-validators/room-validator.middleware.ts @@ -95,8 +95,12 @@ const ThemeModeSchema: z.ZodType = z.enum([MeetRoomThemeMode. const hexColorSchema = z.string().regex(/^#([0-9A-Fa-f]{6}|[0-9A-Fa-f]{3})$/, 'Must be a valid hex color code'); const RoomThemeSchema = z.object({ + name: z + .string() + .min(1, 'Theme name cannot be empty') + .max(50, 'Theme name cannot exceed 50 characters') + .regex(/^[a-z0-9_-]+$/, 'Theme name can only contain lowercase letters, numbers, hyphens and underscores'), enabled: z.boolean(), - name: z.string().min(1, 'Theme name cannot be empty').max(50, 'Theme name cannot exceed 50 characters'), baseTheme: ThemeModeSchema, backgroundColor: hexColorSchema.optional(), primaryColor: hexColorSchema.optional(), diff --git a/backend/src/models/error.model.ts b/backend/src/models/error.model.ts index 7f5c868..65b7460 100644 --- a/backend/src/models/error.model.ts +++ b/backend/src/models/error.model.ts @@ -247,12 +247,6 @@ export const errorParticipantIdentityNotProvided = (): OpenViduMeetError => { return new OpenViduMeetError('Participant Error', 'No participant identity provided', 400); }; -// Global config errors - -export const errorRoomsAppearanceConfigNotDefined = (): OpenViduMeetError => { - return new OpenViduMeetError('Global Config Error', 'Rooms appearance config not defined', 404); -}; - // Webhook errors export const errorInvalidWebhookUrl = (url: string, reason: string): OpenViduMeetError => { diff --git a/backend/src/services/storage/providers/gcp/gcs-storage.provider.ts b/backend/src/services/storage/providers/gcp/gcs-storage.provider.ts index 3d7cf86..ef768b6 100644 --- a/backend/src/services/storage/providers/gcp/gcs-storage.provider.ts +++ b/backend/src/services/storage/providers/gcp/gcs-storage.provider.ts @@ -11,7 +11,7 @@ export class GCSStorageProvider implements StorageProvider { constructor( @inject(LoggerService) protected logger: LoggerService, @inject(GCSService) protected gcsService: GCSService - ) { } + ) {} /** * Retrieves an object from GCS Storage as a JSON object. diff --git a/backend/src/services/storage/providers/gcp/gcs.service.ts b/backend/src/services/storage/providers/gcp/gcs.service.ts index 1a928a8..ebf72e5 100644 --- a/backend/src/services/storage/providers/gcp/gcs.service.ts +++ b/backend/src/services/storage/providers/gcp/gcs.service.ts @@ -163,6 +163,7 @@ export class GCSService { }; const [nextFiles] = await bucketObj.getFiles(nextOptions); + if (nextFiles.length === 0) { NextContinuationToken = undefined; isTruncated = false; diff --git a/backend/src/services/storage/storage.service.ts b/backend/src/services/storage/storage.service.ts index 0584a14..3cb1a35 100644 --- a/backend/src/services/storage/storage.service.ts +++ b/backend/src/services/storage/storage.service.ts @@ -711,10 +711,10 @@ export class MeetStorageService< * @returns {GConfig} */ protected getDefaultConfig(): GConfig { - return { + const defaultConfig: GlobalConfig = { projectId: MEET_NAME_ID, webhooksConfig: { - enabled: MEET_INITIAL_WEBHOOK_ENABLED === 'true' && MEET_INITIAL_API_KEY, + enabled: MEET_INITIAL_WEBHOOK_ENABLED === 'true' && !!MEET_INITIAL_API_KEY, url: MEET_INITIAL_WEBHOOK_URL }, securityConfig: { @@ -724,8 +724,14 @@ export class MeetStorageService< }, authModeToAccessRoom: AuthMode.NONE } + }, + roomsConfig: { + appearance: { + themes: [] + } } - } as GConfig; + }; + return defaultConfig as GConfig; } /** diff --git a/typings/src/global-config.ts b/typings/src/global-config.ts index 1cd177e..482ad7a 100644 --- a/typings/src/global-config.ts +++ b/typings/src/global-config.ts @@ -8,7 +8,7 @@ export interface GlobalConfig { projectId: string; securityConfig: SecurityConfig; webhooksConfig: WebhookConfig; - roomsConfig?: { + roomsConfig: { appearance: MeetAppearanceConfig; }; } diff --git a/typings/src/room-config.ts b/typings/src/room-config.ts index 7cffba4..feca0d6 100644 --- a/typings/src/room-config.ts +++ b/typings/src/room-config.ts @@ -2,49 +2,49 @@ * Interface representing the config for a room. */ export interface MeetRoomConfig { - chat: MeetChatConfig; - recording: MeetRecordingConfig; - virtualBackground: MeetVirtualBackgroundConfig; - // appearance?: MeetAppearanceConfig; + chat: MeetChatConfig; + recording: MeetRecordingConfig; + virtualBackground: MeetVirtualBackgroundConfig; + // appearance: MeetAppearanceConfig; } /** * Interface representing the config for recordings in a room. */ export interface MeetRecordingConfig { - enabled: boolean; - allowAccessTo?: MeetRecordingAccess; + enabled: boolean; + allowAccessTo?: MeetRecordingAccess; } export const enum MeetRecordingAccess { - ADMIN = 'admin', // Only admins can access the recording - ADMIN_MODERATOR = 'admin_moderator', // Admins and moderators can access - ADMIN_MODERATOR_SPEAKER = 'admin_moderator_speaker', // Admins, moderators and speakers can access + ADMIN = 'admin', // Only admins can access the recording + ADMIN_MODERATOR = 'admin_moderator', // Admins and moderators can access + ADMIN_MODERATOR_SPEAKER = 'admin_moderator_speaker' // Admins, moderators and speakers can access } export interface MeetChatConfig { - enabled: boolean; + enabled: boolean; } export interface MeetVirtualBackgroundConfig { - enabled: boolean; + enabled: boolean; } export interface MeetAppearanceConfig { - themes: MeetRoomTheme[]; + themes: MeetRoomTheme[]; } export interface MeetRoomTheme { - enabled: boolean; - name: string; - baseTheme: MeetRoomThemeMode; - backgroundColor?: string; - primaryColor?: string; - secondaryColor?: string; - surfaceColor?: string; + name: string; + enabled: boolean; + baseTheme: MeetRoomThemeMode; + backgroundColor?: string; + primaryColor?: string; + secondaryColor?: string; + surfaceColor?: string; } export const enum MeetRoomThemeMode { - LIGHT = 'light', - DARK = 'dark', + LIGHT = 'light', + DARK = 'dark' }