1
0
mirror of https://github.com/immich-app/immich.git synced 2025-03-31 23:09:43 +02:00

refactor: more process.env references (#13106)

This commit is contained in:
Jason Rasmussen 2024-10-02 08:37:26 -04:00 committed by GitHub
parent e5457ac8ee
commit 6c7d51da34
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 62 additions and 28 deletions

View File

@ -7,7 +7,7 @@ import { useSwagger } from 'src/utils/misc';
const sync = async () => { const sync = async () => {
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { preview: true }); const app = await NestFactory.create<NestExpressApplication>(ApiModule, { preview: true });
useSwagger(app, true); useSwagger(app, { write: true });
await app.close(); await app.close();
}; };

View File

@ -12,6 +12,7 @@ import {
Colorspace, Colorspace,
CQMode, CQMode,
ImageFormat, ImageFormat,
ImmichEnvironment,
LogLevel, LogLevel,
ToneMapping, ToneMapping,
TranscodeHWAccel, TranscodeHWAccel,
@ -322,7 +323,10 @@ export const immichAppConfig: ConfigModuleOptions = {
envFilePath: '.env', envFilePath: '.env',
isGlobal: true, isGlobal: true,
validationSchema: Joi.object({ validationSchema: Joi.object({
IMMICH_ENV: Joi.string().optional().valid('development', 'testing', 'production').default('production'), IMMICH_ENV: Joi.string()
.optional()
.valid(...Object.values(ImmichEnvironment))
.default(ImmichEnvironment.PRODUCTION),
IMMICH_LOG_LEVEL: Joi.string() IMMICH_LOG_LEVEL: Joi.string()
.optional() .optional()
.valid(...Object.values(LogLevel)), .valid(...Object.values(LogLevel)),

View File

@ -21,7 +21,6 @@ export const AUDIT_LOG_MAX_DURATION = Duration.fromObject({ days: 100 });
export const ONE_HOUR = Duration.fromObject({ hours: 1 }); export const ONE_HOUR = Duration.fromObject({ hours: 1 });
export const envName = (process.env.IMMICH_ENV || 'production').toUpperCase(); export const envName = (process.env.IMMICH_ENV || 'production').toUpperCase();
export const isDev = () => process.env.IMMICH_ENV === 'development';
export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload'; export const APP_MEDIA_LOCATION = process.env.IMMICH_MEDIA_LOCATION || './upload';
const HOST_SERVER_PORT = process.env.IMMICH_PORT || '2283'; const HOST_SERVER_PORT = process.env.IMMICH_PORT || '2283';
export const DEFAULT_EXTERNAL_DOMAIN = 'http://localhost:' + HOST_SERVER_PORT; export const DEFAULT_EXTERNAL_DOMAIN = 'http://localhost:' + HOST_SERVER_PORT;

View File

@ -328,3 +328,9 @@ export enum PaginationMode {
LIMIT_OFFSET = 'limit-offset', LIMIT_OFFSET = 'limit-offset',
SKIP_TAKE = 'skip-take', SKIP_TAKE = 'skip-take',
} }
export enum ImmichEnvironment {
DEVELOPMENT = 'development',
TESTING = 'testing',
PRODUCTION = 'production',
}

View File

@ -1,16 +1,23 @@
import { ImmichEnvironment, LogLevel } from 'src/enum';
import { VectorExtension } from 'src/interfaces/database.interface'; import { VectorExtension } from 'src/interfaces/database.interface';
export const IConfigRepository = 'IConfigRepository'; export const IConfigRepository = 'IConfigRepository';
export interface EnvData { export interface EnvData {
environment: ImmichEnvironment;
configFile?: string; configFile?: string;
logLevel?: LogLevel;
database: { database: {
skipMigrations: boolean; skipMigrations: boolean;
vectorExtension: VectorExtension; vectorExtension: VectorExtension;
}; };
storage: { storage: {
ignoreMountCheckErrors: boolean; ignoreMountCheckErrors: boolean;
}; };
nodeVersion?: string;
} }
export interface IConfigRepository { export interface IConfigRepository {

View File

@ -5,7 +5,7 @@ export const ILoggerRepository = 'ILoggerRepository';
export interface ILoggerRepository { export interface ILoggerRepository {
setAppName(name: string): void; setAppName(name: string): void;
setContext(message: string): void; setContext(message: string): void;
setLogLevel(level: LogLevel): void; setLogLevel(level: LogLevel | false): void;
isLevelEnabled(level: LogLevel): boolean; isLevelEnabled(level: LogLevel): boolean;
verbose(message: any, ...args: any): void; verbose(message: any, ...args: any): void;

View File

@ -1,12 +1,17 @@
import { Injectable } from '@nestjs/common'; import { Injectable } from '@nestjs/common';
import { getVectorExtension } from 'src/database.config'; import { getVectorExtension } from 'src/database.config';
import { ImmichEnvironment, LogLevel } from 'src/enum';
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface'; import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
// TODO replace src/config validation with class-validator, here
@Injectable() @Injectable()
export class ConfigRepository implements IConfigRepository { export class ConfigRepository implements IConfigRepository {
getEnv(): EnvData { getEnv(): EnvData {
return { return {
environment: process.env.IMMICH_ENV as ImmichEnvironment,
configFile: process.env.IMMICH_CONFIG_FILE, configFile: process.env.IMMICH_CONFIG_FILE,
logLevel: process.env.IMMICH_LOG_LEVEL as LogLevel,
database: { database: {
skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true', skipMigrations: process.env.DB_SKIP_MIGRATIONS === 'true',
vectorExtension: getVectorExtension(), vectorExtension: getVectorExtension(),

View File

@ -25,8 +25,8 @@ export class LoggerRepository extends ConsoleLogger implements ILoggerRepository
return isLogLevelEnabled(level, LoggerRepository.logLevels); return isLogLevelEnabled(level, LoggerRepository.logLevels);
} }
setLogLevel(level: LogLevel): void { setLogLevel(level: LogLevel | false): void {
LoggerRepository.logLevels = LOG_LEVELS.slice(LOG_LEVELS.indexOf(level)); LoggerRepository.logLevels = level ? LOG_LEVELS.slice(LOG_LEVELS.indexOf(level)) : [];
} }
protected formatContext(context: string): string { protected formatContext(context: string): string {

View File

@ -5,6 +5,7 @@ import { readFile } from 'node:fs/promises';
import { promisify } from 'node:util'; import { promisify } from 'node:util';
import sharp from 'sharp'; import sharp from 'sharp';
import { resourcePaths } from 'src/constants'; import { resourcePaths } from 'src/constants';
import { IConfigRepository } from 'src/interfaces/config.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface'; import { GitHubRelease, IServerInfoRepository, ServerBuildVersions } from 'src/interfaces/server-info.interface';
import { Instrumentation } from 'src/utils/instrumentation'; import { Instrumentation } from 'src/utils/instrumentation';
@ -37,7 +38,10 @@ const getLockfileVersion = (name: string, lockfile?: BuildLockfile) => {
@Instrumentation() @Instrumentation()
@Injectable() @Injectable()
export class ServerInfoRepository implements IServerInfoRepository { export class ServerInfoRepository implements IServerInfoRepository {
constructor(@Inject(ILoggerRepository) private logger: ILoggerRepository) { constructor(
@Inject(IConfigRepository) private configRepository: IConfigRepository,
@Inject(ILoggerRepository) private logger: ILoggerRepository,
) {
this.logger.setContext(ServerInfoRepository.name); this.logger.setContext(ServerInfoRepository.name);
} }
@ -56,6 +60,8 @@ export class ServerInfoRepository implements IServerInfoRepository {
} }
async getBuildVersions(): Promise<ServerBuildVersions> { async getBuildVersions(): Promise<ServerBuildVersions> {
const { nodeVersion } = this.configRepository.getEnv();
const [nodejsOutput, ffmpegOutput, magickOutput] = await Promise.all([ const [nodejsOutput, ffmpegOutput, magickOutput] = await Promise.all([
maybeFirstLine('node --version'), maybeFirstLine('node --version'),
maybeFirstLine('ffmpeg -version'), maybeFirstLine('ffmpeg -version'),
@ -67,7 +73,7 @@ export class ServerInfoRepository implements IServerInfoRepository {
.catch(() => this.logger.warn(`Failed to read ${resourcePaths.lockFile}`)); .catch(() => this.logger.warn(`Failed to read ${resourcePaths.lockFile}`));
return { return {
nodejs: nodejsOutput || process.env.NODE_VERSION || '', nodejs: nodejsOutput || nodeVersion || '',
exiftool: await exiftool.version(), exiftool: await exiftool.version(),
ffmpeg: getLockfileVersion('ffmpeg', lockfile) || ffmpegOutput.replaceAll('ffmpeg version', '') || '', ffmpeg: getLockfileVersion('ffmpeg', lockfile) || ffmpegOutput.replaceAll('ffmpeg version', '') || '',
libvips: getLockfileVersion('libvips', lockfile) || sharp.versions.vips, libvips: getLockfileVersion('libvips', lockfile) || sharp.versions.vips,

View File

@ -14,7 +14,6 @@ import {
} from 'src/constants'; } from 'src/constants';
import { OnEvent } from 'src/decorators'; import { OnEvent } from 'src/decorators';
import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto'; import { SystemConfigDto, SystemConfigTemplateStorageOptionDto, mapConfig } from 'src/dtos/system-config.dto';
import { LogLevel } from 'src/enum';
import { IConfigRepository } from 'src/interfaces/config.interface'; import { IConfigRepository } from 'src/interfaces/config.interface';
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface'; import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface';
@ -52,7 +51,7 @@ export class SystemConfigService extends BaseService {
@OnEvent({ name: 'config.update', server: true }) @OnEvent({ name: 'config.update', server: true })
onConfigUpdate({ newConfig: { logging } }: ArgOf<'config.update'>) { onConfigUpdate({ newConfig: { logging } }: ArgOf<'config.update'>) {
const envLevel = this.getEnvLogLevel(); const { logLevel: envLevel } = this.configRepository.getEnv();
const configLevel = logging.enabled ? logging.level : false; const configLevel = logging.enabled ? logging.level : false;
const level = envLevel ?? configLevel; const level = envLevel ?? configLevel;
this.logger.setLogLevel(level); this.logger.setLogLevel(level);
@ -63,7 +62,8 @@ export class SystemConfigService extends BaseService {
@OnEvent({ name: 'config.validate' }) @OnEvent({ name: 'config.validate' })
onConfigValidate({ newConfig, oldConfig }: ArgOf<'config.validate'>) { onConfigValidate({ newConfig, oldConfig }: ArgOf<'config.validate'>) {
if (!_.isEqual(instanceToPlain(newConfig.logging), oldConfig.logging) && this.getEnvLogLevel()) { 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.'); throw new Error('Logging cannot be changed while the environment variable IMMICH_LOG_LEVEL is set.');
} }
} }
@ -109,8 +109,4 @@ export class SystemConfigService extends BaseService {
const { theme } = await this.getConfig({ withCache: false }); const { theme } = await this.getConfig({ withCache: false });
return theme.customCss; return theme.customCss;
} }
private getEnvLogLevel() {
return process.env.IMMICH_LOG_LEVEL as LogLevel;
}
} }

View File

@ -1,6 +1,6 @@
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import { serverVersion } from 'src/constants'; import { serverVersion } from 'src/constants';
import { SystemMetadataKey } from 'src/enum'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum';
import { IConfigRepository } from 'src/interfaces/config.interface'; import { IConfigRepository } from 'src/interfaces/config.interface';
import { IDatabaseRepository } from 'src/interfaces/database.interface'; import { IDatabaseRepository } from 'src/interfaces/database.interface';
import { IEventRepository } from 'src/interfaces/event.interface'; import { IEventRepository } from 'src/interfaces/event.interface';
@ -10,7 +10,7 @@ import { IServerInfoRepository } from 'src/interfaces/server-info.interface';
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface'; import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface'; import { IVersionHistoryRepository } from 'src/interfaces/version-history.interface';
import { VersionService } from 'src/services/version.service'; import { VersionService } from 'src/services/version.service';
import { newConfigRepositoryMock } from 'test/repositories/config.repository.mock'; import { mockEnvData, newConfigRepositoryMock } from 'test/repositories/config.repository.mock';
import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock'; import { newDatabaseRepositoryMock } from 'test/repositories/database.repository.mock';
import { newEventRepositoryMock } from 'test/repositories/event.repository.mock'; import { newEventRepositoryMock } from 'test/repositories/event.repository.mock';
import { newJobRepositoryMock } from 'test/repositories/job.repository.mock'; import { newJobRepositoryMock } from 'test/repositories/job.repository.mock';
@ -111,11 +111,11 @@ describe(VersionService.name, () => {
describe('handVersionCheck', () => { describe('handVersionCheck', () => {
beforeEach(() => { beforeEach(() => {
process.env.IMMICH_ENV = 'production'; configMock.getEnv.mockReturnValue(mockEnvData({ environment: ImmichEnvironment.PRODUCTION }));
}); });
it('should not run in dev mode', async () => { it('should not run in dev mode', async () => {
process.env.IMMICH_ENV = 'development'; configMock.getEnv.mockReturnValue(mockEnvData({ environment: ImmichEnvironment.DEVELOPMENT }));
await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.SKIPPED); await expect(sut.handleVersionCheck()).resolves.toEqual(JobStatus.SKIPPED);
}); });

View File

@ -1,11 +1,11 @@
import { Inject, Injectable } from '@nestjs/common'; import { Inject, Injectable } from '@nestjs/common';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import semver, { SemVer } from 'semver'; import semver, { SemVer } from 'semver';
import { isDev, serverVersion } from 'src/constants'; import { serverVersion } from 'src/constants';
import { OnEvent } from 'src/decorators'; import { OnEvent } from 'src/decorators';
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto'; import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
import { VersionCheckMetadata } from 'src/entities/system-metadata.entity'; import { VersionCheckMetadata } from 'src/entities/system-metadata.entity';
import { SystemMetadataKey } from 'src/enum'; import { ImmichEnvironment, SystemMetadataKey } from 'src/enum';
import { IConfigRepository } from 'src/interfaces/config.interface'; import { IConfigRepository } from 'src/interfaces/config.interface';
import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface'; import { DatabaseLock, IDatabaseRepository } from 'src/interfaces/database.interface';
import { ArgOf, IEventRepository } from 'src/interfaces/event.interface'; import { ArgOf, IEventRepository } from 'src/interfaces/event.interface';
@ -71,7 +71,8 @@ export class VersionService extends BaseService {
try { try {
this.logger.debug('Running version check'); this.logger.debug('Running version check');
if (isDev()) { const { environment } = this.configRepository.getEnv();
if (environment === ImmichEnvironment.DEVELOPMENT) {
return JobStatus.SKIPPED; return JobStatus.SKIPPED;
} }

View File

@ -11,7 +11,7 @@ import _ from 'lodash';
import { writeFileSync } from 'node:fs'; import { writeFileSync } from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { SystemConfig } from 'src/config'; import { SystemConfig } from 'src/config';
import { CLIP_MODEL_INFO, isDev, serverVersion } from 'src/constants'; import { CLIP_MODEL_INFO, serverVersion } from 'src/constants';
import { ImmichCookie, ImmichHeader } from 'src/dtos/auth.dto'; import { ImmichCookie, ImmichHeader } from 'src/dtos/auth.dto';
import { MetadataKey } from 'src/enum'; import { MetadataKey } from 'src/enum';
import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface';
@ -193,7 +193,7 @@ const patchOpenAPI = (document: OpenAPIObject) => {
return document; return document;
}; };
export const useSwagger = (app: INestApplication, force = false) => { export const useSwagger = (app: INestApplication, { write }: { write: boolean }) => {
const config = new DocumentBuilder() const config = new DocumentBuilder()
.setTitle('Immich') .setTitle('Immich')
.setDescription('Immich API') .setDescription('Immich API')
@ -230,7 +230,7 @@ export const useSwagger = (app: INestApplication, force = false) => {
SwaggerModule.setup('doc', app, specification, customOptions); SwaggerModule.setup('doc', app, specification, customOptions);
if (isDev() || force) { if (write) {
// Generate API Documentation only in development mode // Generate API Documentation only in development mode
const outputPath = path.resolve(process.cwd(), '../open-api/immich-openapi-specs.json'); const outputPath = path.resolve(process.cwd(), '../open-api/immich-openapi-specs.json');
writeFileSync(outputPath, JSON.stringify(patchOpenAPI(specification), null, 2), { encoding: 'utf8' }); writeFileSync(outputPath, JSON.stringify(patchOpenAPI(specification), null, 2), { encoding: 'utf8' });

View File

@ -5,7 +5,9 @@ import cookieParser from 'cookie-parser';
import { existsSync } from 'node:fs'; import { existsSync } from 'node:fs';
import sirv from 'sirv'; import sirv from 'sirv';
import { ApiModule } from 'src/app.module'; import { ApiModule } from 'src/app.module';
import { envName, excludePaths, isDev, resourcePaths, serverVersion } from 'src/constants'; import { envName, excludePaths, resourcePaths, serverVersion } from 'src/constants';
import { ImmichEnvironment } from 'src/enum';
import { IConfigRepository } from 'src/interfaces/config.interface';
import { ILoggerRepository } from 'src/interfaces/logger.interface'; import { ILoggerRepository } from 'src/interfaces/logger.interface';
import { WebSocketAdapter } from 'src/middleware/websocket.adapter'; import { WebSocketAdapter } from 'src/middleware/websocket.adapter';
import { ApiService } from 'src/services/api.service'; import { ApiService } from 'src/services/api.service';
@ -33,6 +35,10 @@ async function bootstrap() {
const port = Number(process.env.IMMICH_PORT) || 3001; const port = Number(process.env.IMMICH_PORT) || 3001;
const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true }); const app = await NestFactory.create<NestExpressApplication>(ApiModule, { bufferLogs: true });
const logger = await app.resolve<ILoggerRepository>(ILoggerRepository); const logger = await app.resolve<ILoggerRepository>(ILoggerRepository);
const configRepository = app.get<IConfigRepository>(IConfigRepository);
const { environment } = configRepository.getEnv();
const isDev = environment === ImmichEnvironment.DEVELOPMENT;
logger.setAppName('Api'); logger.setAppName('Api');
logger.setContext('Bootstrap'); logger.setContext('Bootstrap');
@ -41,11 +47,11 @@ async function bootstrap() {
app.set('etag', 'strong'); app.set('etag', 'strong');
app.use(cookieParser()); app.use(cookieParser());
app.use(json({ limit: '10mb' })); app.use(json({ limit: '10mb' }));
if (isDev()) { if (isDev) {
app.enableCors(); app.enableCors();
} }
app.useWebSocketAdapter(new WebSocketAdapter(app)); app.useWebSocketAdapter(new WebSocketAdapter(app));
useSwagger(app); useSwagger(app, { write: isDev });
app.setGlobalPrefix('api', { exclude: excludePaths }); app.setGlobalPrefix('api', { exclude: excludePaths });
if (existsSync(resourcePaths.web.root)) { if (existsSync(resourcePaths.web.root)) {

View File

@ -1,12 +1,16 @@
import { ImmichEnvironment } from 'src/enum';
import { EnvData, IConfigRepository } from 'src/interfaces/config.interface'; import { EnvData, IConfigRepository } from 'src/interfaces/config.interface';
import { DatabaseExtension } from 'src/interfaces/database.interface'; import { DatabaseExtension } from 'src/interfaces/database.interface';
import { Mocked, vitest } from 'vitest'; import { Mocked, vitest } from 'vitest';
const envData: EnvData = { const envData: EnvData = {
environment: ImmichEnvironment.PRODUCTION,
database: { database: {
skipMigrations: false, skipMigrations: false,
vectorExtension: DatabaseExtension.VECTORS, vectorExtension: DatabaseExtension.VECTORS,
}, },
storage: { storage: {
ignoreMountCheckErrors: false, ignoreMountCheckErrors: false,
}, },