import { BadRequestException, Injectable } from '@nestjs/common'; import { instanceToPlain } from 'class-transformer'; import _ from 'lodash'; import { defaults } from 'src/config'; import { OnEvent } from 'src/decorators'; import { SystemConfigDto, mapConfig } from 'src/dtos/system-config.dto'; import { ArgOf, BootstrapEventPriority } from 'src/interfaces/event.interface'; import { BaseService } from 'src/services/base.service'; import { clearConfigCache } from 'src/utils/config'; import { toPlainObject } from 'src/utils/object'; @Injectable() export class SystemConfigService extends BaseService { @OnEvent({ name: 'app.bootstrap', priority: BootstrapEventPriority.SystemConfig }) async onBootstrap() { const config = await this.getConfig({ withCache: false }); await this.eventRepository.emit('config.init', { newConfig: config }); } async getSystemConfig(): Promise { const config = await this.getConfig({ withCache: false }); return mapConfig(config); } getDefaults(): SystemConfigDto { return mapConfig(defaults); } @OnEvent({ name: 'config.init' }) onConfigInit({ newConfig: { logging } }: ArgOf<'config.init'>) { const { logLevel: envLevel } = this.configRepository.getEnv(); const configLevel = logging.enabled ? logging.level : false; const level = envLevel ?? configLevel; this.logger.setLogLevel(level); this.logger.log(`LogLevel=${level} ${envLevel ? '(set via IMMICH_LOG_LEVEL)' : '(set via system config)'}`); } @OnEvent({ name: 'config.update', server: true }) onConfigUpdate({ newConfig }: ArgOf<'config.update'>) { this.onConfigInit({ newConfig }); clearConfigCache(); } @OnEvent({ name: 'config.validate' }) onConfigValidate({ newConfig, oldConfig }: ArgOf<'config.validate'>) { const { logLevel } = this.configRepository.getEnv(); if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && logLevel) { throw new Error('Logging cannot be changed while the environment variable IMMICH_LOG_LEVEL is set.'); } } async updateSystemConfig(dto: SystemConfigDto): Promise { const { configFile } = this.configRepository.getEnv(); if (configFile) { throw new BadRequestException('Cannot update configuration while IMMICH_CONFIG_FILE is in use'); } const oldConfig = await this.getConfig({ withCache: false }); try { await this.eventRepository.emit('config.validate', { newConfig: toPlainObject(dto), oldConfig }); } catch (error) { this.logger.warn(`Unable to save system config due to a validation error: ${error}`); throw new BadRequestException(error instanceof Error ? error.message : error); } const newConfig = await this.updateConfig(dto); await this.eventRepository.emit('config.update', { newConfig, oldConfig }); return mapConfig(newConfig); } async getCustomCss(): Promise { const { theme } = await this.getConfig({ withCache: false }); return theme.customCss; } }