mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-03 19:17:11 -05:00 
			
		
		
		
	refactor(server): streamline get config, enable the use of arrays (#4562)
This commit is contained in:
		
							parent
							
								
									8dcc01b2be
								
							
						
					
					
						commit
						013da0aa3d
					
				@ -13,11 +13,10 @@ import {
 | 
			
		||||
  VideoCodec,
 | 
			
		||||
} from '@app/infra/entities';
 | 
			
		||||
import { BadRequestException, ForbiddenException, Injectable, Logger } from '@nestjs/common';
 | 
			
		||||
import { plainToClass } from 'class-transformer';
 | 
			
		||||
import { plainToInstance } from 'class-transformer';
 | 
			
		||||
import { validate } from 'class-validator';
 | 
			
		||||
import * as _ from 'lodash';
 | 
			
		||||
import { Subject } from 'rxjs';
 | 
			
		||||
import { DeepPartial } from 'typeorm';
 | 
			
		||||
import { QueueName } from '../job/job.constants';
 | 
			
		||||
import { ISystemConfigRepository } from '../repositories';
 | 
			
		||||
import { SystemConfigDto } from './dto';
 | 
			
		||||
@ -140,7 +139,7 @@ let instance: SystemConfigCore | null;
 | 
			
		||||
export class SystemConfigCore {
 | 
			
		||||
  private logger = new Logger(SystemConfigCore.name);
 | 
			
		||||
  private validators: SystemConfigValidator[] = [];
 | 
			
		||||
  private configCache: SystemConfig | null = null;
 | 
			
		||||
  private configCache: SystemConfigEntity<SystemConfigValue>[] | null = null;
 | 
			
		||||
 | 
			
		||||
  public config$ = new Subject<SystemConfig>();
 | 
			
		||||
 | 
			
		||||
@ -218,9 +217,28 @@ export class SystemConfigCore {
 | 
			
		||||
    this.validators.push(validator);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public getConfig(force = false): Promise<SystemConfig> {
 | 
			
		||||
  public async getConfig(force = false): Promise<SystemConfig> {
 | 
			
		||||
    const configFilePath = process.env.IMMICH_CONFIG_FILE;
 | 
			
		||||
    return configFilePath ? this.loadFromFile(configFilePath, force) : this.loadFromDatabase();
 | 
			
		||||
    const config = _.cloneDeep(defaults);
 | 
			
		||||
    const overrides = configFilePath ? await this.loadFromFile(configFilePath, force) : await this.repository.load();
 | 
			
		||||
 | 
			
		||||
    for (const { key, value } of overrides) {
 | 
			
		||||
      // set via dot notation
 | 
			
		||||
      _.set(config, key, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const errors = await validate(plainToInstance(SystemConfigDto, config), {
 | 
			
		||||
      forbidNonWhitelisted: true,
 | 
			
		||||
      forbidUnknownValues: true,
 | 
			
		||||
    });
 | 
			
		||||
    if (errors.length > 0) {
 | 
			
		||||
      this.logger.error('Validation error', errors);
 | 
			
		||||
      if (configFilePath) {
 | 
			
		||||
        throw new Error(`Invalid value(s) in file: ${errors}`);
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return config;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  public async updateConfig(config: SystemConfig): Promise<SystemConfig> {
 | 
			
		||||
@ -246,7 +264,13 @@ export class SystemConfigCore {
 | 
			
		||||
      const defaultValue = _.get(defaults, key);
 | 
			
		||||
      const isMissing = !_.has(config, key);
 | 
			
		||||
 | 
			
		||||
      if (isMissing || item.value === null || item.value === '' || item.value === defaultValue) {
 | 
			
		||||
      if (
 | 
			
		||||
        isMissing ||
 | 
			
		||||
        item.value === null ||
 | 
			
		||||
        item.value === '' ||
 | 
			
		||||
        item.value === defaultValue ||
 | 
			
		||||
        _.isEqual(item.value, defaultValue)
 | 
			
		||||
      ) {
 | 
			
		||||
        deletes.push(item);
 | 
			
		||||
        continue;
 | 
			
		||||
      }
 | 
			
		||||
@ -275,34 +299,25 @@ export class SystemConfigCore {
 | 
			
		||||
    this.config$.next(newConfig);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async loadFromDatabase() {
 | 
			
		||||
    const config: DeepPartial<SystemConfig> = {};
 | 
			
		||||
    const overrides = await this.repository.load();
 | 
			
		||||
    for (const { key, value } of overrides) {
 | 
			
		||||
      // set via dot notation
 | 
			
		||||
      _.set(config, key, value);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    return plainToClass(SystemConfigDto, _.defaultsDeep(config, defaults));
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private async loadFromFile(filepath: string, force = false) {
 | 
			
		||||
    if (force || !this.configCache) {
 | 
			
		||||
      try {
 | 
			
		||||
        const overrides = JSON.parse((await this.repository.readFile(filepath)).toString());
 | 
			
		||||
        const config = plainToClass(SystemConfigDto, _.defaultsDeep(overrides, defaults));
 | 
			
		||||
        const file = JSON.parse((await this.repository.readFile(filepath)).toString());
 | 
			
		||||
        const overrides: SystemConfigEntity<SystemConfigValue>[] = [];
 | 
			
		||||
 | 
			
		||||
        const errors = await validate(config, {
 | 
			
		||||
          whitelist: true,
 | 
			
		||||
          forbidNonWhitelisted: true,
 | 
			
		||||
          forbidUnknownValues: true,
 | 
			
		||||
        });
 | 
			
		||||
        if (errors.length > 0) {
 | 
			
		||||
          this.logger.error('Validation error', errors);
 | 
			
		||||
          throw new Error(`Invalid value(s) in file: ${errors}`);
 | 
			
		||||
        for (const key of Object.values(SystemConfigKey)) {
 | 
			
		||||
          const value = _.get(file, key);
 | 
			
		||||
          this.unsetDeep(file, key);
 | 
			
		||||
          if (value !== undefined) {
 | 
			
		||||
            overrides.push({ key, value });
 | 
			
		||||
          }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.configCache = config;
 | 
			
		||||
        if (!_.isEmpty(file)) {
 | 
			
		||||
          throw new Error(`Unknown keys found: ${file}`);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        this.configCache = overrides;
 | 
			
		||||
      } catch (error: Error | any) {
 | 
			
		||||
        this.logger.error(`Unable to load configuration file: ${filepath} due to ${error}`, error?.stack);
 | 
			
		||||
        throw new Error('Invalid configuration file');
 | 
			
		||||
@ -311,4 +326,15 @@ export class SystemConfigCore {
 | 
			
		||||
 | 
			
		||||
    return this.configCache;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  private unsetDeep(object: object, key: string) {
 | 
			
		||||
    _.unset(object, key);
 | 
			
		||||
    const path = key.split('.');
 | 
			
		||||
    while (path.pop()) {
 | 
			
		||||
      if (!_.isEmpty(_.get(object, path))) {
 | 
			
		||||
        return;
 | 
			
		||||
      }
 | 
			
		||||
      _.unset(object, path);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user