1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-01-04 02:46:47 +01:00
immich/server/src/validation.ts
Daniel Dietzler 5e9a7b17d9
fix(server): allow library id to be null in metadata search (#10512)
* fix: allow library id to be null in metadata search

* chore: open api
2024-06-20 16:02:05 -07:00

164 lines
4.1 KiB
TypeScript

import {
ArgumentMetadata,
BadRequestException,
FileValidator,
Injectable,
ParseUUIDPipe,
applyDecorators,
} from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
IsArray,
IsBoolean,
IsDate,
IsNotEmpty,
IsOptional,
IsString,
IsUUID,
ValidateIf,
ValidationOptions,
isDateString,
} from 'class-validator';
import { CronJob } from 'cron';
import sanitize from 'sanitize-filename';
@Injectable()
export class ParseMeUUIDPipe extends ParseUUIDPipe {
async transform(value: string, metadata: ArgumentMetadata) {
if (value == 'me') {
return value;
}
return super.transform(value, metadata);
}
}
@Injectable()
export class FileNotEmptyValidator extends FileValidator {
constructor(private requiredFields: string[]) {
super({});
this.requiredFields = requiredFields;
}
isValid(files?: any): boolean {
if (!files) {
return false;
}
return this.requiredFields.every((field) => files[field]);
}
buildErrorMessage(): string {
return `Field(s) ${this.requiredFields.join(', ')} should not be empty`;
}
}
export class UUIDParamDto {
@IsNotEmpty()
@IsUUID('4')
@ApiProperty({ format: 'uuid' })
id!: string;
}
export interface OptionalOptions extends ValidationOptions {
nullable?: boolean;
}
/**
* Checks if value is missing and if so, ignores all validators.
*
* @param validationOptions {@link OptionalOptions}
*
* @see IsOptional exported from `class-validator.
*/
// https://stackoverflow.com/a/71353929
export function Optional({ nullable, ...validationOptions }: OptionalOptions = {}) {
if (nullable === true) {
return IsOptional(validationOptions);
}
return ValidateIf((object: any, v: any) => v !== undefined, validationOptions);
}
type UUIDOptions = { optional?: boolean; each?: boolean; nullable?: boolean };
export const ValidateUUID = (options?: UUIDOptions) => {
const { optional, each, nullable } = { optional: false, each: false, nullable: false, ...options };
return applyDecorators(
IsUUID('4', { each }),
ApiProperty({ format: 'uuid' }),
optional ? Optional({ nullable }) : IsNotEmpty(),
each ? IsArray() : IsString(),
);
};
type DateOptions = { optional?: boolean; nullable?: boolean; format?: 'date' | 'date-time' };
export const ValidateDate = (options?: DateOptions) => {
const { optional, nullable, format } = { optional: false, nullable: false, format: 'date-time', ...options };
const decorators = [
ApiProperty({ format }),
IsDate(),
optional ? Optional({ nullable: true }) : IsNotEmpty(),
Transform(({ key, value }) => {
if (value === null || value === undefined) {
return value;
}
if (!isDateString(value)) {
throw new BadRequestException(`${key} must be a date string`);
}
return new Date(value as string);
}),
];
if (optional) {
decorators.push(Optional({ nullable }));
}
return applyDecorators(...decorators);
};
type BooleanOptions = { optional?: boolean };
export const ValidateBoolean = (options?: BooleanOptions) => {
const { optional } = { optional: false, ...options };
const decorators = [
// ApiProperty(),
IsBoolean(),
Transform(({ value }) => {
if (value == 'true') {
return true;
} else if (value == 'false') {
return false;
}
return value;
}),
];
if (optional) {
decorators.push(Optional());
}
return applyDecorators(...decorators);
};
export function validateCronExpression(expression: string) {
try {
new CronJob(expression, () => {});
} catch {
return false;
}
return true;
}
type IValue = { value: string };
export const toEmail = ({ value }: IValue) => (value ? value.toLowerCase() : value);
export const toSanitized = ({ value }: IValue) => sanitize((value || '').replaceAll('.', ''));
export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => {
const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options;
return Number.isInteger(value) && value >= min && value <= max;
};