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
+148 -221
View File
@@ -1,242 +1,169 @@
import { ApiProperty, ApiPropertyOptional, ApiResponseProperty } from '@nestjs/swagger';
import { SemVer } from 'semver';
import { SystemConfigThemeDto } from 'src/dtos/system-config.dto';
import { createZodDto } from 'nestjs-zod';
import type { SemVer } from 'semver';
import { isoDatetimeToDate } from 'src/validation';
import z from 'zod';
export class ServerPingResponse {
@ApiResponseProperty({ type: String, example: 'pong' })
res!: string;
}
const ServerPingResponseSchema = z
.object({
res: z.string().meta({ example: 'pong' }),
})
.meta({ id: 'ServerPingResponse' });
export class ServerAboutResponseDto {
@ApiProperty({ description: 'Server version' })
version!: string;
@ApiProperty({ description: 'URL to version information' })
versionUrl!: string;
const ServerAboutResponseSchema = z
.object({
version: z.string().describe('Server version'),
versionUrl: z.string().describe('URL to version information'),
repository: z.string().optional().describe('Repository name'),
repositoryUrl: z.string().optional().describe('Repository URL'),
sourceRef: z.string().optional().describe('Source reference (branch/tag)'),
sourceCommit: z.string().optional().describe('Source commit hash'),
sourceUrl: z.string().optional().describe('Source URL'),
build: z.string().optional().describe('Build identifier'),
buildUrl: z.string().optional().describe('Build URL'),
buildImage: z.string().optional().describe('Build image name'),
buildImageUrl: z.string().optional().describe('Build image URL'),
nodejs: z.string().optional().describe('Node.js version'),
ffmpeg: z.string().optional().describe('FFmpeg version'),
imagemagick: z.string().optional().describe('ImageMagick version'),
libvips: z.string().optional().describe('libvips version'),
exiftool: z.string().optional().describe('ExifTool version'),
licensed: z.boolean().describe('Whether the server is licensed'),
thirdPartySourceUrl: z.string().optional().describe('Third-party source URL'),
thirdPartyBugFeatureUrl: z.string().optional().describe('Third-party bug/feature URL'),
thirdPartyDocumentationUrl: z.string().optional().describe('Third-party documentation URL'),
thirdPartySupportUrl: z.string().optional().describe('Third-party support URL'),
})
.meta({ id: 'ServerAboutResponseDto' });
@ApiPropertyOptional({ description: 'Repository name' })
repository?: string;
@ApiPropertyOptional({ description: 'Repository URL' })
repositoryUrl?: string;
const ServerApkLinksSchema = z
.object({
arm64v8a: z.string().describe('APK download link for ARM64 v8a architecture'),
armeabiv7a: z.string().describe('APK download link for ARM EABI v7a architecture'),
universal: z.string().describe('APK download link for universal architecture'),
x86_64: z.string().describe('APK download link for x86_64 architecture'),
})
.meta({ id: 'ServerApkLinksDto' });
@ApiPropertyOptional({ description: 'Source reference (branch/tag)' })
sourceRef?: string;
@ApiPropertyOptional({ description: 'Source commit hash' })
sourceCommit?: string;
@ApiPropertyOptional({ description: 'Source URL' })
sourceUrl?: string;
const ServerStorageResponseSchema = z
.object({
diskSize: z.string().describe('Total disk size (human-readable format)'),
diskUse: z.string().describe('Used disk space (human-readable format)'),
diskAvailable: z.string().describe('Available disk space (human-readable format)'),
diskSizeRaw: z.int().describe('Total disk size in bytes'),
diskUseRaw: z.int().describe('Used disk space in bytes'),
diskAvailableRaw: z.int().describe('Available disk space in bytes'),
diskUsagePercentage: z.number().meta({ format: 'double' }).describe('Disk usage percentage (0-100)'),
})
.meta({ id: 'ServerStorageResponseDto' });
@ApiPropertyOptional({ description: 'Build identifier' })
build?: string;
@ApiPropertyOptional({ description: 'Build URL' })
buildUrl?: string;
@ApiPropertyOptional({ description: 'Build image name' })
buildImage?: string;
@ApiPropertyOptional({ description: 'Build image URL' })
buildImageUrl?: string;
const ServerVersionResponseSchema = z
.object({
major: z.int().describe('Major version number'),
minor: z.int().describe('Minor version number'),
patch: z.int().describe('Patch version number'),
})
.meta({ id: 'ServerVersionResponseDto' });
@ApiPropertyOptional({ description: 'Node.js version' })
nodejs?: string;
@ApiPropertyOptional({ description: 'FFmpeg version' })
ffmpeg?: string;
@ApiPropertyOptional({ description: 'ImageMagick version' })
imagemagick?: string;
@ApiPropertyOptional({ description: 'libvips version' })
libvips?: string;
@ApiPropertyOptional({ description: 'ExifTool version' })
exiftool?: string;
const ServerVersionHistoryResponseSchema = z
.object({
id: z.string().describe('Version history entry ID'),
createdAt: isoDatetimeToDate.describe('When this version was first seen'),
version: z.string().describe('Version string'),
})
.meta({ id: 'ServerVersionHistoryResponseDto' });
@ApiProperty({ description: 'Whether the server is licensed' })
licensed!: boolean;
const UsageByUserSchema = z
.object({
userId: z.string().describe('User ID'),
userName: z.string().describe('User name'),
photos: z.int().describe('Number of photos'),
videos: z.int().describe('Number of videos'),
usage: z.int().describe('Total storage usage in bytes'),
usagePhotos: z.int().describe('Storage usage for photos in bytes'),
usageVideos: z.int().describe('Storage usage for videos in bytes'),
quotaSizeInBytes: z.int().nullable().describe('User quota size in bytes (null if unlimited)'),
})
.meta({ id: 'UsageByUserDto' });
@ApiPropertyOptional({ description: 'Third-party source URL' })
thirdPartySourceUrl?: string;
@ApiPropertyOptional({ description: 'Third-party bug/feature URL' })
thirdPartyBugFeatureUrl?: string;
@ApiPropertyOptional({ description: 'Third-party documentation URL' })
thirdPartyDocumentationUrl?: string;
@ApiPropertyOptional({ description: 'Third-party support URL' })
thirdPartySupportUrl?: string;
}
const ServerStatsResponseSchema = z
.object({
photos: z.int().describe('Total number of photos'),
videos: z.int().describe('Total number of videos'),
usage: z.int().describe('Total storage usage in bytes'),
usagePhotos: z.int().describe('Storage usage for photos in bytes'),
usageVideos: z.int().describe('Storage usage for videos in bytes'),
usageByUser: z.array(UsageByUserSchema).describe('Array of usage for each user'),
})
.meta({ id: 'ServerStatsResponseDto' });
export class ServerApkLinksDto {
@ApiProperty({ description: 'APK download link for ARM64 v8a architecture' })
arm64v8a!: string;
@ApiProperty({ description: 'APK download link for ARM EABI v7a architecture' })
armeabiv7a!: string;
@ApiProperty({ description: 'APK download link for universal architecture' })
universal!: string;
@ApiProperty({ description: 'APK download link for x86_64 architecture' })
x86_64!: string;
}
const ServerMediaTypesResponseSchema = z
.object({
video: z.array(z.string()).describe('Supported video MIME types'),
image: z.array(z.string()).describe('Supported image MIME types'),
sidecar: z.array(z.string()).describe('Supported sidecar MIME types'),
})
.meta({ id: 'ServerMediaTypesResponseDto' });
export class ServerStorageResponseDto {
@ApiProperty({ description: 'Total disk size (human-readable format)' })
diskSize!: string;
@ApiProperty({ description: 'Used disk space (human-readable format)' })
diskUse!: string;
@ApiProperty({ description: 'Available disk space (human-readable format)' })
diskAvailable!: string;
const ServerThemeSchema = z
.object({
customCss: z.string().describe('Custom CSS for theming'),
})
.meta({ id: 'ServerThemeDto' });
@ApiProperty({ type: 'integer', format: 'int64', description: 'Total disk size in bytes' })
diskSizeRaw!: number;
const ServerConfigSchema = z
.object({
oauthButtonText: z.string().describe('OAuth button text'),
loginPageMessage: z.string().describe('Login page message'),
trashDays: z.int().describe('Number of days before trashed assets are permanently deleted'),
userDeleteDelay: z.int().describe('Delay in days before deleted users are permanently removed'),
isInitialized: z.boolean().describe('Whether the server has been initialized'),
isOnboarded: z.boolean().describe('Whether the admin has completed onboarding'),
externalDomain: z.string().describe('External domain URL'),
publicUsers: z.boolean().describe('Whether public user registration is enabled'),
mapDarkStyleUrl: z.string().describe('Map dark style URL'),
mapLightStyleUrl: z.string().describe('Map light style URL'),
maintenanceMode: z.boolean().describe('Whether maintenance mode is active'),
})
.meta({ id: 'ServerConfigDto' });
@ApiProperty({ type: 'integer', format: 'int64', description: 'Used disk space in bytes' })
diskUseRaw!: number;
const ServerFeaturesSchema = z
.object({
smartSearch: z.boolean().describe('Whether smart search is enabled'),
duplicateDetection: z.boolean().describe('Whether duplicate detection is enabled'),
configFile: z.boolean().describe('Whether config file is available'),
facialRecognition: z.boolean().describe('Whether facial recognition is enabled'),
map: z.boolean().describe('Whether map feature is enabled'),
trash: z.boolean().describe('Whether trash feature is enabled'),
reverseGeocoding: z.boolean().describe('Whether reverse geocoding is enabled'),
importFaces: z.boolean().describe('Whether face import is enabled'),
oauth: z.boolean().describe('Whether OAuth is enabled'),
oauthAutoLaunch: z.boolean().describe('Whether OAuth auto-launch is enabled'),
passwordLogin: z.boolean().describe('Whether password login is enabled'),
sidecar: z.boolean().describe('Whether sidecar files are supported'),
search: z.boolean().describe('Whether search is enabled'),
email: z.boolean().describe('Whether email notifications are enabled'),
ocr: z.boolean().describe('Whether OCR is enabled'),
})
.meta({ id: 'ServerFeaturesDto' });
@ApiProperty({ type: 'integer', format: 'int64', description: 'Available disk space in bytes' })
diskAvailableRaw!: number;
export class ServerPingResponse extends createZodDto(ServerPingResponseSchema) {}
export class ServerAboutResponseDto extends createZodDto(ServerAboutResponseSchema) {}
export class ServerApkLinksDto extends createZodDto(ServerApkLinksSchema) {}
export class ServerStorageResponseDto extends createZodDto(ServerStorageResponseSchema) {}
@ApiProperty({ type: 'number', format: 'double', description: 'Disk usage percentage (0-100)' })
diskUsagePercentage!: number;
}
export class ServerVersionResponseDto {
@ApiProperty({ type: 'integer', description: 'Major version number' })
major!: number;
@ApiProperty({ type: 'integer', description: 'Minor version number' })
minor!: number;
@ApiProperty({ type: 'integer', description: 'Patch version number' })
patch!: number;
static fromSemVer(value: SemVer) {
export class ServerVersionResponseDto extends createZodDto(ServerVersionResponseSchema) {
static fromSemVer(value: SemVer): z.infer<typeof ServerVersionResponseSchema> {
return { major: value.major, minor: value.minor, patch: value.patch };
}
}
export class ServerVersionHistoryResponseDto {
@ApiProperty({ description: 'Version history entry ID' })
id!: string;
@ApiProperty({ description: 'When this version was first seen', format: 'date-time' })
createdAt!: Date;
@ApiProperty({ description: 'Version string' })
version!: string;
}
export class UsageByUserDto {
@ApiProperty({ type: 'string', description: 'User ID' })
userId!: string;
@ApiProperty({ type: 'string', description: 'User name' })
userName!: string;
@ApiProperty({ type: 'integer', description: 'Number of photos' })
photos!: number;
@ApiProperty({ type: 'integer', description: 'Number of videos' })
videos!: number;
@ApiProperty({ type: 'integer', format: 'int64', description: 'Total storage usage in bytes' })
usage!: number;
@ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage for photos in bytes' })
usagePhotos!: number;
@ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage for videos in bytes' })
usageVideos!: number;
@ApiProperty({
type: 'integer',
format: 'int64',
nullable: true,
description: 'User quota size in bytes (null if unlimited)',
})
quotaSizeInBytes!: number | null;
}
export class ServerStatsResponseDto {
@ApiProperty({ type: 'integer', description: 'Total number of photos' })
photos = 0;
@ApiProperty({ type: 'integer', description: 'Total number of videos' })
videos = 0;
@ApiProperty({ type: 'integer', format: 'int64', description: 'Total storage usage in bytes' })
usage = 0;
@ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage for photos in bytes' })
usagePhotos = 0;
@ApiProperty({ type: 'integer', format: 'int64', description: 'Storage usage for videos in bytes' })
usageVideos = 0;
@ApiProperty({
isArray: true,
type: UsageByUserDto,
title: 'Array of usage for each user',
example: [
{
photos: 1,
videos: 1,
diskUsageRaw: 2,
usagePhotos: 1,
usageVideos: 1,
},
],
})
usageByUser: UsageByUserDto[] = [];
}
export class ServerMediaTypesResponseDto {
@ApiProperty({ description: 'Supported video MIME types' })
video!: string[];
@ApiProperty({ description: 'Supported image MIME types' })
image!: string[];
@ApiProperty({ description: 'Supported sidecar MIME types' })
sidecar!: string[];
}
export class ServerThemeDto extends SystemConfigThemeDto {}
export class ServerConfigDto {
@ApiProperty({ description: 'OAuth button text' })
oauthButtonText!: string;
@ApiProperty({ description: 'Login page message' })
loginPageMessage!: string;
@ApiProperty({ type: 'integer', description: 'Number of days before trashed assets are permanently deleted' })
trashDays!: number;
@ApiProperty({ type: 'integer', description: 'Delay in days before deleted users are permanently removed' })
userDeleteDelay!: number;
@ApiProperty({ description: 'Whether the server has been initialized' })
isInitialized!: boolean;
@ApiProperty({ description: 'Whether the admin has completed onboarding' })
isOnboarded!: boolean;
@ApiProperty({ description: 'External domain URL' })
externalDomain!: string;
@ApiProperty({ description: 'Whether public user registration is enabled' })
publicUsers!: boolean;
@ApiProperty({ description: 'Map dark style URL' })
mapDarkStyleUrl!: string;
@ApiProperty({ description: 'Map light style URL' })
mapLightStyleUrl!: string;
@ApiProperty({ description: 'Whether maintenance mode is active' })
maintenanceMode!: boolean;
}
export class ServerFeaturesDto {
@ApiProperty({ description: 'Whether smart search is enabled' })
smartSearch!: boolean;
@ApiProperty({ description: 'Whether duplicate detection is enabled' })
duplicateDetection!: boolean;
@ApiProperty({ description: 'Whether config file is available' })
configFile!: boolean;
@ApiProperty({ description: 'Whether facial recognition is enabled' })
facialRecognition!: boolean;
@ApiProperty({ description: 'Whether map feature is enabled' })
map!: boolean;
@ApiProperty({ description: 'Whether trash feature is enabled' })
trash!: boolean;
@ApiProperty({ description: 'Whether reverse geocoding is enabled' })
reverseGeocoding!: boolean;
@ApiProperty({ description: 'Whether face import is enabled' })
importFaces!: boolean;
@ApiProperty({ description: 'Whether OAuth is enabled' })
oauth!: boolean;
@ApiProperty({ description: 'Whether OAuth auto-launch is enabled' })
oauthAutoLaunch!: boolean;
@ApiProperty({ description: 'Whether password login is enabled' })
passwordLogin!: boolean;
@ApiProperty({ description: 'Whether sidecar files are supported' })
sidecar!: boolean;
@ApiProperty({ description: 'Whether search is enabled' })
search!: boolean;
@ApiProperty({ description: 'Whether email notifications are enabled' })
email!: boolean;
@ApiProperty({ description: 'Whether OCR is enabled' })
ocr!: boolean;
}
export class ServerVersionHistoryResponseDto extends createZodDto(ServerVersionHistoryResponseSchema) {}
export class UsageByUserDto extends createZodDto(UsageByUserSchema) {}
export class ServerStatsResponseDto extends createZodDto(ServerStatsResponseSchema) {}
export class ServerMediaTypesResponseDto extends createZodDto(ServerMediaTypesResponseSchema) {}
export class ServerThemeDto extends createZodDto(ServerThemeSchema) {}
export class ServerConfigDto extends createZodDto(ServerConfigSchema) {}
export class ServerFeaturesDto extends createZodDto(ServerFeaturesSchema) {}
export interface ReleaseNotification {
isAvailable: boolean;