backend: enhance appearance configuration handling and validation in global settings

This commit is contained in:
juancarmore 2025-09-29 14:55:05 +02:00
parent 8f6af28bc2
commit 3d8a82a18d
8 changed files with 52 additions and 45 deletions

View File

@ -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');

View File

@ -95,8 +95,12 @@ const ThemeModeSchema: z.ZodType<MeetRoomThemeMode> = 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(),

View File

@ -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 => {

View File

@ -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.

View File

@ -163,6 +163,7 @@ export class GCSService {
};
const [nextFiles] = await bucketObj.getFiles(nextOptions);
if (nextFiles.length === 0) {
NextContinuationToken = undefined;
isTruncated = false;

View File

@ -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;
}
/**

View File

@ -8,7 +8,7 @@ export interface GlobalConfig {
projectId: string;
securityConfig: SecurityConfig;
webhooksConfig: WebhookConfig;
roomsConfig?: {
roomsConfig: {
appearance: MeetAppearanceConfig;
};
}

View File

@ -5,7 +5,7 @@ export interface MeetRoomConfig {
chat: MeetChatConfig;
recording: MeetRecordingConfig;
virtualBackground: MeetVirtualBackgroundConfig;
// appearance?: MeetAppearanceConfig;
// appearance: MeetAppearanceConfig;
}
/**
@ -19,7 +19,7 @@ export interface MeetRecordingConfig {
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_MODERATOR_SPEAKER = 'admin_moderator_speaker' // Admins, moderators and speakers can access
}
export interface MeetChatConfig {
@ -35,8 +35,8 @@ export interface MeetAppearanceConfig {
}
export interface MeetRoomTheme {
enabled: boolean;
name: string;
enabled: boolean;
baseTheme: MeetRoomThemeMode;
backgroundColor?: string;
primaryColor?: string;
@ -46,5 +46,5 @@ export interface MeetRoomTheme {
export const enum MeetRoomThemeMode {
LIGHT = 'light',
DARK = 'dark',
DARK = 'dark'
}