feat(server): use nestjs events to validate config (#7986)

* use events for config validation

* chore: better types

* add unit tests

---------

Co-authored-by: Jason Rasmussen <jrasm91@gmail.com>
This commit is contained in:
Daniel Dietzler 2024-03-17 20:16:02 +01:00 committed by GitHub
parent 14da671bf9
commit 148428a564
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
14 changed files with 170 additions and 81 deletions

View file

@ -1,6 +1,7 @@
import { LogLevel, SystemConfig } from '@app/infra/entities';
import { ImmichLogger } from '@app/infra/logger';
import { Inject, Injectable } from '@nestjs/common';
import { BadRequestException, Inject, Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { instanceToPlain } from 'class-transformer';
import _ from 'lodash';
import {
@ -8,6 +9,8 @@ import {
ICommunicationRepository,
ISearchRepository,
ISystemConfigRepository,
InternalEvent,
InternalEventMap,
ServerEvent,
} from '../repositories';
import { SystemConfigDto, mapConfig } from './dto/system-config.dto';
@ -22,7 +25,7 @@ import {
supportedWeekTokens,
supportedYearTokens,
} from './system-config.constants';
import { SystemConfigCore, SystemConfigValidator } from './system-config.core';
import { SystemConfigCore } from './system-config.core';
@Injectable()
export class SystemConfigService {
@ -37,7 +40,6 @@ export class SystemConfigService {
this.core = SystemConfigCore.create(repository);
this.communicationRepository.on(ServerEvent.CONFIG_UPDATE, () => this.handleConfigUpdate());
this.core.config$.subscribe((config) => this.setLogLevel(config));
this.core.addValidator((newConfig, oldConfig) => this.validateConfig(newConfig, oldConfig));
}
async init() {
@ -59,8 +61,23 @@ export class SystemConfigService {
return mapConfig(config);
}
@OnEvent(InternalEvent.VALIDATE_CONFIG)
validateConfig({ newConfig, oldConfig }: InternalEventMap[InternalEvent.VALIDATE_CONFIG]) {
if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) {
throw new Error('Logging cannot be changed while the environment variable LOG_LEVEL is set.');
}
}
async updateConfig(dto: SystemConfigDto): Promise<SystemConfigDto> {
const oldConfig = await this.core.getConfig();
try {
await this.communicationRepository.emitAsync(InternalEvent.VALIDATE_CONFIG, { newConfig: 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.core.updateConfig(dto);
this.communicationRepository.broadcast(ClientEvent.CONFIG_UPDATE, {});
@ -79,10 +96,6 @@ export class SystemConfigService {
return true;
}
addValidator(validator: SystemConfigValidator) {
this.core.addValidator(validator);
}
getStorageTemplateOptions(): SystemConfigTemplateStorageOptionDto {
const options = new SystemConfigTemplateStorageOptionDto();
@ -129,10 +142,4 @@ export class SystemConfigService {
private getEnvLogLevel() {
return process.env.LOG_LEVEL as LogLevel;
}
private validateConfig(newConfig: SystemConfig, oldConfig: SystemConfig) {
if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) {
throw new Error('Logging cannot be changed while the environment variable LOG_LEVEL is set.');
}
}
}