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 { MeetAppearanceConfig } from '@typings-ce';
import { Request, Response } from 'express'; import { Request, Response } from 'express';
import { container } from '../../config/index.js'; import { container } from '../../config/index.js';
import { import { handleError } from '../../models/error.model.js';
errorRoomsAppearanceConfigNotDefined,
handleError,
rejectRequestFromMeetError
} from '../../models/error.model.js';
import { LoggerService, MeetStorageService } from '../../services/index.js'; import { LoggerService, MeetStorageService } from '../../services/index.js';
export const updateRoomsAppearanceConfig = async (req: Request, res: Response) => { export const updateRoomsAppearanceConfig = async (req: Request, res: Response) => {
@ -17,6 +13,18 @@ export const updateRoomsAppearanceConfig = async (req: Request, res: Response) =
try { try {
const globalConfig = await storageService.getGlobalConfig(); 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; globalConfig.roomsConfig = appearanceConfig;
await storageService.saveGlobalConfig(globalConfig); await storageService.saveGlobalConfig(globalConfig);
@ -34,13 +42,7 @@ export const getRoomsAppearanceConfig = async (_req: Request, res: Response) =>
try { try {
const globalConfig = await storageService.getGlobalConfig(); const globalConfig = await storageService.getGlobalConfig();
const appearanceConfig = globalConfig.roomsConfig?.appearance; const appearanceConfig = globalConfig.roomsConfig.appearance;
if (!appearanceConfig) {
const error = errorRoomsAppearanceConfigNotDefined();
return rejectRequestFromMeetError(res, error);
}
return res.status(200).json({ appearance: appearanceConfig }); return res.status(200).json({ appearance: appearanceConfig });
} catch (error) { } catch (error) {
handleError(res, error, 'getting rooms appearance config'); 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 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({ 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(), enabled: z.boolean(),
name: z.string().min(1, 'Theme name cannot be empty').max(50, 'Theme name cannot exceed 50 characters'),
baseTheme: ThemeModeSchema, baseTheme: ThemeModeSchema,
backgroundColor: hexColorSchema.optional(), backgroundColor: hexColorSchema.optional(),
primaryColor: 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); 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 // Webhook errors
export const errorInvalidWebhookUrl = (url: string, reason: string): OpenViduMeetError => { export const errorInvalidWebhookUrl = (url: string, reason: string): OpenViduMeetError => {

View File

@ -11,7 +11,7 @@ export class GCSStorageProvider implements StorageProvider {
constructor( constructor(
@inject(LoggerService) protected logger: LoggerService, @inject(LoggerService) protected logger: LoggerService,
@inject(GCSService) protected gcsService: GCSService @inject(GCSService) protected gcsService: GCSService
) { } ) {}
/** /**
* Retrieves an object from GCS Storage as a JSON object. * 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); const [nextFiles] = await bucketObj.getFiles(nextOptions);
if (nextFiles.length === 0) { if (nextFiles.length === 0) {
NextContinuationToken = undefined; NextContinuationToken = undefined;
isTruncated = false; isTruncated = false;

View File

@ -711,10 +711,10 @@ export class MeetStorageService<
* @returns {GConfig} * @returns {GConfig}
*/ */
protected getDefaultConfig(): GConfig { protected getDefaultConfig(): GConfig {
return { const defaultConfig: GlobalConfig = {
projectId: MEET_NAME_ID, projectId: MEET_NAME_ID,
webhooksConfig: { 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 url: MEET_INITIAL_WEBHOOK_URL
}, },
securityConfig: { securityConfig: {
@ -724,8 +724,14 @@ export class MeetStorageService<
}, },
authModeToAccessRoom: AuthMode.NONE authModeToAccessRoom: AuthMode.NONE
} }
},
roomsConfig: {
appearance: {
themes: []
}
} }
} as GConfig; };
return defaultConfig as GConfig;
} }
/** /**

View File

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

View File

@ -2,49 +2,49 @@
* Interface representing the config for a room. * Interface representing the config for a room.
*/ */
export interface MeetRoomConfig { export interface MeetRoomConfig {
chat: MeetChatConfig; chat: MeetChatConfig;
recording: MeetRecordingConfig; recording: MeetRecordingConfig;
virtualBackground: MeetVirtualBackgroundConfig; virtualBackground: MeetVirtualBackgroundConfig;
// appearance?: MeetAppearanceConfig; // appearance: MeetAppearanceConfig;
} }
/** /**
* Interface representing the config for recordings in a room. * Interface representing the config for recordings in a room.
*/ */
export interface MeetRecordingConfig { export interface MeetRecordingConfig {
enabled: boolean; enabled: boolean;
allowAccessTo?: MeetRecordingAccess; allowAccessTo?: MeetRecordingAccess;
} }
export const enum MeetRecordingAccess { export const enum MeetRecordingAccess {
ADMIN = 'admin', // Only admins can access the recording ADMIN = 'admin', // Only admins can access the recording
ADMIN_MODERATOR = 'admin_moderator', // Admins and moderators can access 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 { export interface MeetChatConfig {
enabled: boolean; enabled: boolean;
} }
export interface MeetVirtualBackgroundConfig { export interface MeetVirtualBackgroundConfig {
enabled: boolean; enabled: boolean;
} }
export interface MeetAppearanceConfig { export interface MeetAppearanceConfig {
themes: MeetRoomTheme[]; themes: MeetRoomTheme[];
} }
export interface MeetRoomTheme { export interface MeetRoomTheme {
enabled: boolean; name: string;
name: string; enabled: boolean;
baseTheme: MeetRoomThemeMode; baseTheme: MeetRoomThemeMode;
backgroundColor?: string; backgroundColor?: string;
primaryColor?: string; primaryColor?: string;
secondaryColor?: string; secondaryColor?: string;
surfaceColor?: string; surfaceColor?: string;
} }
export const enum MeetRoomThemeMode { export const enum MeetRoomThemeMode {
LIGHT = 'light', LIGHT = 'light',
DARK = 'dark', DARK = 'dark'
} }