feat(server,web): migrate oauth settings from env to system config (#1061)

This commit is contained in:
Jason Rasmussen
2022-12-09 15:51:42 -05:00
committed by GitHub
parent cefdd86b7f
commit 5e680551b9
69 changed files with 2079 additions and 1229 deletions

View File

@@ -16,12 +16,6 @@ const jwtSecretValidator: Joi.CustomValidator<string> = (value) => {
return value;
};
const WHEN_OAUTH_ENABLED = Joi.when('OAUTH_ENABLED', {
is: true,
then: Joi.string().required(),
otherwise: Joi.string().optional(),
});
export const immichAppConfig: ConfigModuleOptions = {
envFilePath: '.env',
isGlobal: true,
@@ -34,12 +28,5 @@ export const immichAppConfig: ConfigModuleOptions = {
DISABLE_REVERSE_GEOCODING: Joi.boolean().optional().valid(true, false).default(false),
REVERSE_GEOCODING_PRECISION: Joi.number().optional().valid(0, 1, 2, 3).default(3),
LOG_LEVEL: Joi.string().optional().valid('simple', 'verbose').default('simple'),
OAUTH_ENABLED: Joi.bool().valid(true, false).default(false),
OAUTH_BUTTON_TEXT: Joi.string().optional().default('Login with OAuth'),
OAUTH_AUTO_REGISTER: Joi.bool().valid(true, false).default(true),
OAUTH_ISSUER_URL: WHEN_OAUTH_ENABLED,
OAUTH_SCOPE: Joi.string().optional().default('openid email profile'),
OAUTH_CLIENT_ID: WHEN_OAUTH_ENABLED,
OAUTH_CLIENT_SECRET: WHEN_OAUTH_ENABLED,
}),
};

View File

@@ -1,27 +1,47 @@
import { Column, Entity, PrimaryColumn } from 'typeorm';
@Entity('system_config')
export class SystemConfigEntity {
export class SystemConfigEntity<T = string> {
@PrimaryColumn()
key!: SystemConfigKey;
@Column({ type: 'varchar', nullable: true })
value!: SystemConfigValue;
@Column({ type: 'varchar', nullable: true, transformer: { to: JSON.stringify, from: JSON.parse } })
value!: T;
}
export type SystemConfig = SystemConfigEntity[];
export type SystemConfigValue = any;
// dot notation matches path in `SystemConfig`
export enum SystemConfigKey {
FFMPEG_CRF = 'ffmpeg_crf',
FFMPEG_PRESET = 'ffmpeg_preset',
FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg_target_video_codec',
FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg_target_audio_codec',
FFMPEG_TARGET_SCALING = 'ffmpeg_target_scaling',
FFMPEG_CRF = 'ffmpeg.crf',
FFMPEG_PRESET = 'ffmpeg.preset',
FFMPEG_TARGET_VIDEO_CODEC = 'ffmpeg.targetVideoCodec',
FFMPEG_TARGET_AUDIO_CODEC = 'ffmpeg.targetAudioCodec',
FFMPEG_TARGET_SCALING = 'ffmpeg.targetScaling',
OAUTH_ENABLED = 'oauth.enabled',
OAUTH_ISSUER_URL = 'oauth.issuerUrl',
OAUTH_CLIENT_ID = 'oauth.clientId',
OAUTH_CLIENT_SECRET = 'oauth.clientSecret',
OAUTH_SCOPE = 'oauth.scope',
OAUTH_BUTTON_TEXT = 'oauth.buttonText',
OAUTH_AUTO_REGISTER = 'oauth.autoRegister',
}
export type SystemConfigValue = string | null;
export interface SystemConfigItem {
key: SystemConfigKey;
value: SystemConfigValue;
export interface SystemConfig {
ffmpeg: {
crf: string;
preset: string;
targetVideoCodec: string;
targetAudioCodec: string;
targetScaling: string;
};
oauth: {
enabled: boolean;
issuerUrl: string;
clientId: string;
clientSecret: string;
scope: string;
buttonText: string;
autoRegister: boolean;
};
}

View File

@@ -0,0 +1,11 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class TruncateOldConfigItems1670607437008 implements MigrationInterface {
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`TRUNCATE TABLE "system_config"`);
}
public async down(): Promise<void> {
// noop
}
}

View File

@@ -1,32 +1,27 @@
import { SystemConfigEntity, SystemConfigKey, SystemConfigValue } from '@app/database/entities/system-config.entity';
import { SystemConfig, SystemConfigEntity, SystemConfigKey } from '@app/database/entities/system-config.entity';
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { In, Repository } from 'typeorm';
import * as _ from 'lodash';
import { DeepPartial, In, Repository } from 'typeorm';
type SystemConfigMap = Record<SystemConfigKey, SystemConfigValue>;
const configDefaults: Record<SystemConfigKey, { name: string; value: SystemConfigValue }> = {
[SystemConfigKey.FFMPEG_CRF]: {
name: 'FFmpeg Constant Rate Factor (-crf)',
value: '23',
const defaults: SystemConfig = Object.freeze({
ffmpeg: {
crf: '23',
preset: 'ultrafast',
targetVideoCodec: 'libx264',
targetAudioCodec: 'mp3',
targetScaling: '1280:-2',
},
[SystemConfigKey.FFMPEG_PRESET]: {
name: 'FFmpeg preset (-preset)',
value: 'ultrafast',
oauth: {
enabled: false,
issuerUrl: '',
clientId: '',
clientSecret: '',
scope: 'openid email profile',
buttonText: 'Login with OAuth',
autoRegister: true,
},
[SystemConfigKey.FFMPEG_TARGET_VIDEO_CODEC]: {
name: 'FFmpeg target video codec (-vcodec)',
value: 'libx264',
},
[SystemConfigKey.FFMPEG_TARGET_AUDIO_CODEC]: {
name: 'FFmpeg target audio codec (-acodec)',
value: 'mp3',
},
[SystemConfigKey.FFMPEG_TARGET_SCALING]: {
name: 'FFmpeg target scaling (-vf scale=)',
value: '1280:-2',
},
};
});
@Injectable()
export class ImmichConfigService {
@@ -35,38 +30,32 @@ export class ImmichConfigService {
private systemConfigRepository: Repository<SystemConfigEntity>,
) {}
public async getSystemConfig() {
const items = this._getDefaults();
public getDefaults(): SystemConfig {
return defaults;
}
// override default values
public async getConfig() {
const overrides = await this.systemConfigRepository.find();
for (const override of overrides) {
const item = items.find((_item) => _item.key === override.key);
if (item) {
item.value = override.value;
}
const config: DeepPartial<SystemConfig> = {};
for (const { key, value } of overrides) {
// set via dot notation
_.set(config, key, value);
}
return items;
return _.defaultsDeep(config, defaults) as SystemConfig;
}
public async getSystemConfigMap(): Promise<SystemConfigMap> {
const items = await this.getSystemConfig();
const map: Partial<SystemConfigMap> = {};
for (const { key, value } of items) {
map[key] = value;
}
return map as SystemConfigMap;
}
public async updateSystemConfig(items: SystemConfigEntity[]): Promise<void> {
const deletes: SystemConfigEntity[] = [];
public async updateConfig(config: DeepPartial<SystemConfig> | null): Promise<void> {
const updates: SystemConfigEntity[] = [];
const deletes: SystemConfigEntity[] = [];
for (const item of items) {
if (item.value === null || item.value === this._getDefaultValue(item.key)) {
for (const key of Object.values(SystemConfigKey)) {
// get via dot notation
const item = { key, value: _.get(config, key) };
const defaultValue = _.get(defaults, key);
const isMissing = !_.has(config, key);
if (isMissing || item.value === null || item.value === '' || item.value === defaultValue) {
deletes.push(item);
continue;
}
@@ -82,16 +71,4 @@ export class ImmichConfigService {
await this.systemConfigRepository.delete({ key: In(deletes.map((item) => item.key)) });
}
}
private _getDefaults() {
return Object.values(SystemConfigKey).map((key) => ({
key,
defaultValue: configDefaults[key].value,
...configDefaults[key],
}));
}
private _getDefaultValue(key: SystemConfigKey) {
return this._getDefaults().find((item) => item.key === key)?.value || null;
}
}