refactor!: migrate class-validator to zod (#26597)

This commit is contained in:
Timon
2026-04-14 23:39:03 +02:00
committed by GitHub
parent 3753b7a4d1
commit 7d8f843be6
318 changed files with 7830 additions and 8316 deletions
+1 -1
View File
@@ -318,7 +318,7 @@ export class AssetRepository {
.execute();
}
upsertMetadata(id: string, items: Array<{ key: string; value: object }>) {
upsertMetadata(id: string, items: Array<{ key: string; value: Record<string, unknown> }>) {
if (items.length === 0) {
return [];
}
@@ -85,7 +85,7 @@ describe('getEnv', () => {
describe('IMMICH_MEDIA_LOCATION', () => {
it('should throw an error for relative paths', () => {
process.env.IMMICH_MEDIA_LOCATION = './relative/path';
expect(() => getEnv()).toThrowError('IMMICH_MEDIA_LOCATION must be an absolute path');
expect(() => getEnv()).toThrowError('[IMMICH_MEDIA_LOCATION] Must be an absolute path');
});
});
@@ -98,7 +98,7 @@ describe('getEnv', () => {
it('should throw an error for invalid value', () => {
process.env.IMMICH_ALLOW_EXTERNAL_PLUGINS = 'invalid';
expect(() => getEnv()).toThrowError('IMMICH_ALLOW_EXTERNAL_PLUGINS must be a boolean value');
expect(() => getEnv()).toThrowError('[IMMICH_ALLOW_EXTERNAL_PLUGINS] Invalid option: expected one of');
});
});
@@ -111,7 +111,7 @@ describe('getEnv', () => {
it('should throw an error for invalid value', () => {
process.env.IMMICH_ALLOW_SETUP = 'invalid';
expect(() => getEnv()).toThrowError('IMMICH_ALLOW_SETUP must be a boolean value');
expect(() => getEnv()).toThrowError('[IMMICH_ALLOW_SETUP] Invalid option: expected one of');
});
});
@@ -134,7 +134,7 @@ describe('getEnv', () => {
it('should validate DB_SSL_MODE', () => {
process.env.DB_SSL_MODE = 'invalid';
expect(() => getEnv()).toThrowError('DB_SSL_MODE must be one of the following values:');
expect(() => getEnv()).toThrow(/\[DB_SSL_MODE\] Invalid option: expected one of/);
});
it('should accept a valid DB_SSL_MODE', () => {
@@ -278,7 +278,7 @@ describe('getEnv', () => {
it('should reject invalid trusted proxies', () => {
process.env.IMMICH_TRUSTED_PROXIES = '10.1';
expect(() => getEnv()).toThrow('IMMICH_TRUSTED_PROXIES must be an ip address, or ip address range');
expect(() => getEnv()).toThrow('[IMMICH_TRUSTED_PROXIES] Must be an ip address or ip address range');
});
});
+8 -9
View File
@@ -2,8 +2,6 @@ import { DatabaseConnectionParams } from '@immich/sql-tools';
import { RegisterQueueOptions } from '@nestjs/bullmq';
import { Inject, Injectable, Optional } from '@nestjs/common';
import { QueueOptions } from 'bullmq';
import { plainToInstance } from 'class-transformer';
import { validateSync } from 'class-validator';
import { Request, Response } from 'express';
import { HelmetOptions } from 'helmet';
import { RedisOptions } from 'ioredis';
@@ -13,7 +11,7 @@ import { readFileSync } from 'node:fs';
import { join } from 'node:path';
import { citiesFile, excludePaths, IWorker } from 'src/constants';
import { Telemetry } from 'src/decorators';
import { EnvDto } from 'src/dtos/env.dto';
import { EnvSchema } from 'src/dtos/env.dto';
import {
DatabaseExtension,
ImmichEnvironment,
@@ -173,15 +171,16 @@ const resolveHelmetFile = (helmetFile: 'true' | 'false' | string | undefined) =>
};
const getEnv = (): EnvData => {
const dto = plainToInstance(EnvDto, process.env);
const errors = validateSync(dto);
if (errors.length > 0) {
const messages = [`Invalid environment variables: `];
for (const error of errors) {
messages.push(` - ${error.property}=${error.value} (${Object.values(error.constraints || {}).join(', ')})`);
const parseResult = EnvSchema.safeParse(process.env);
if (!parseResult.success) {
const messages = ['Invalid environment variables: '];
for (const issue of parseResult.error.issues) {
const path = issue.path.join('.');
messages.push(` - [${path}] ${issue.message}`);
}
throw new Error(messages.join('\n'));
}
const dto = parseResult.data;
const includedWorkers = asSet(dto.IMMICH_WORKERS_INCLUDE, [ImmichWorker.Api, ImmichWorker.Microservices]);
const excludedWorkers = asSet(dto.IMMICH_WORKERS_EXCLUDE, []);
+1 -2
View File
@@ -1,6 +1,5 @@
import { Injectable } from '@nestjs/common';
import { ModuleRef, Reflector } from '@nestjs/core';
import { ClassConstructor } from 'class-transformer';
import _ from 'lodash';
import { Socket } from 'socket.io';
import { SystemConfig } from 'src/config';
@@ -152,7 +151,7 @@ export class EventRepository {
this.logger.setContext(EventRepository.name);
}
setup({ services }: { services: ClassConstructor<unknown>[] }) {
setup({ services }: { services: (new (...args: any[]) => unknown)[] }) {
const reflector = this.moduleRef.get(Reflector, { strict: false });
const items: Item<EmitEvent>[] = [];
const worker = this.configRepository.getWorker();
+1 -2
View File
@@ -2,7 +2,6 @@ import { getQueueToken } from '@nestjs/bullmq';
import { Injectable } from '@nestjs/common';
import { ModuleRef, Reflector } from '@nestjs/core';
import { JobsOptions, Queue, Worker } from 'bullmq';
import { ClassConstructor } from 'class-transformer';
import { setTimeout } from 'node:timers/promises';
import { JobConfig } from 'src/decorators';
import { QueueJobResponseDto, QueueJobSearchDto } from 'src/dtos/queue.dto';
@@ -34,7 +33,7 @@ export class JobRepository {
this.logger.setContext(JobRepository.name);
}
setup(services: ClassConstructor<unknown>[]) {
setup(services: (new (...args: any[]) => unknown)[]) {
const reflector = this.moduleRef.get(Reflector, { strict: false });
// discovery
@@ -11,7 +11,6 @@ import { resourceFromAttributes } from '@opentelemetry/resources';
import { AggregationType } from '@opentelemetry/sdk-metrics';
import { NodeSDK, contextBase } from '@opentelemetry/sdk-node';
import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from '@opentelemetry/semantic-conventions';
import { ClassConstructor } from 'class-transformer';
import { snakeCase, startCase } from 'lodash';
import { MetricService } from 'nestjs-otel';
import { copyMetadataFromFunctionToFunction } from 'nestjs-otel/lib/opentelemetry.utils';
@@ -118,7 +117,7 @@ export class TelemetryRepository {
this.repo = new MetricGroupRepository(metricService).configure({ enabled: metrics.has(ImmichTelemetry.Repo) });
}
setup({ repositories }: { repositories: ClassConstructor<unknown>[] }) {
setup({ repositories }: { repositories: (new (...args: any[]) => unknown)[] }) {
const { telemetry } = this.configRepository.getEnv();
const { metrics } = telemetry;
if (!metrics.has(ImmichTelemetry.Repo)) {
@@ -136,7 +135,7 @@ export class TelemetryRepository {
}
}
private wrap(Repository: ClassConstructor<unknown>) {
private wrap(Repository: new (...args: any[]) => unknown) {
const className = Repository.name;
const descriptors = Object.getOwnPropertyDescriptors(Repository.prototype);
const unit = 'ms';