From 37e437a56823e966fae1f79ba2b8540b53a19545 Mon Sep 17 00:00:00 2001 From: Jason Rasmussen Date: Tue, 29 Oct 2024 11:59:35 -0400 Subject: [PATCH] fix(server): keep system config transformations (#13796) --- .../src/services/system-config.service.spec.ts | 18 ++++++++++++++++++ server/src/utils/config.ts | 14 +++++++++----- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/server/src/services/system-config.service.spec.ts b/server/src/services/system-config.service.spec.ts index f81abc4795..ffbb695e12 100644 --- a/server/src/services/system-config.service.spec.ts +++ b/server/src/services/system-config.service.spec.ts @@ -237,6 +237,24 @@ describe(SystemConfigService.name, () => { expect(systemMock.readFile).toHaveBeenCalledWith('immich-config.json'); }); + it('should transform booleans', async () => { + configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); + systemMock.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { twoPass: 'false' } })); + + await expect(sut.getSystemConfig()).resolves.toMatchObject({ + ffmpeg: expect.objectContaining({ twoPass: false }), + }); + }); + + it('should transform numbers', async () => { + configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); + systemMock.readFile.mockResolvedValue(JSON.stringify({ ffmpeg: { threads: '42' } })); + + await expect(sut.getSystemConfig()).resolves.toMatchObject({ + ffmpeg: expect.objectContaining({ threads: 42 }), + }); + }); + it('should log errors with the config file', async () => { configMock.getEnv.mockReturnValue(mockEnvData({ configFile: 'immich-config.json' })); diff --git a/server/src/utils/config.ts b/server/src/utils/config.ts index 3a6e079e87..ce8a2da839 100644 --- a/server/src/utils/config.ts +++ b/server/src/utils/config.ts @@ -1,5 +1,5 @@ import AsyncLock from 'async-lock'; -import { plainToInstance } from 'class-transformer'; +import { instanceToPlain, plainToInstance } from 'class-transformer'; import { validate } from 'class-validator'; import { load as loadYaml } from 'js-yaml'; import * as _ from 'lodash'; @@ -87,13 +87,13 @@ const buildConfig = async (repos: RepoDeps) => { : await metadataRepo.get(SystemMetadataKey.SYSTEM_CONFIG); // merge with defaults - const config = _.cloneDeep(defaults); + const rawConfig = _.cloneDeep(defaults); for (const property of getKeysDeep(partial)) { - _.set(config, property, _.get(partial, property)); + _.set(rawConfig, property, _.get(partial, property)); } // check for extra properties - const unknownKeys = _.cloneDeep(config); + const unknownKeys = _.cloneDeep(rawConfig); for (const property of getKeysDeep(defaults)) { unsetDeep(unknownKeys, property); } @@ -103,7 +103,8 @@ const buildConfig = async (repos: RepoDeps) => { } // validate full config - const errors = await validate(plainToInstance(SystemConfigDto, config)); + const instance = plainToInstance(SystemConfigDto, rawConfig); + const errors = await validate(instance); if (errors.length > 0) { if (configFile) { throw new Error(`Invalid value(s) in file: ${errors}`); @@ -112,6 +113,9 @@ const buildConfig = async (repos: RepoDeps) => { } } + // return config with class-transform changes + const config = instanceToPlain(instance) as SystemConfig; + if (config.server.externalDomain.length > 0) { config.server.externalDomain = new URL(config.server.externalDomain).origin; }