1
0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2024-12-28 22:51:59 +00:00

refactor: api validators (boolean and date) (#7709)

* refactor: api validators (boolean and date)

* chore: open api

* revert: time bucket change
This commit is contained in:
Jason Rasmussen 2024-03-07 22:59:02 -05:00 committed by GitHub
parent 753842745d
commit a50f125dd1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 243 additions and 355 deletions

View file

@ -66,8 +66,8 @@ class Asset {
assetData: new File([await fs.openAsBlob(this.path)], basename(this.path)),
deviceAssetId: this.deviceAssetId,
deviceId: 'CLI',
fileCreatedAt: this.fileCreatedAt,
fileModifiedAt: this.fileModifiedAt,
fileCreatedAt: this.fileCreatedAt.toISOString(),
fileModifiedAt: this.fileModifiedAt.toISOString(),
isFavorite: String(false),
};
const formData = new FormData();

View file

@ -6,10 +6,9 @@ import request from 'supertest';
import { beforeAll, beforeEach, describe, expect, it } from 'vitest';
const invalidBirthday = [
// TODO: enable after replacing `@Type(() => Date)`
// { birthDate: 'false', response: 'Invalid date' },
// { birthDate: '123567', response: 'Invalid date },
// { birthDate: 123_567, response: ['Birth date cannot be in the future'] },
{ birthDate: 'false', response: 'birthDate must be a date string' },
{ birthDate: '123567', response: 'birthDate must be a date string' },
{ birthDate: 123_567, response: 'birthDate must be a date string' },
{ birthDate: new Date(9999, 0, 0).toISOString(), response: ['Birth date cannot be in the future'] },
];
@ -152,16 +151,16 @@ describe('/person', () => {
expect(body).toEqual(errorDto.unauthorized);
});
it('should not accept invalid birth dates', async () => {
for (const { birthDate, response } of invalidBirthday) {
for (const { birthDate, response } of invalidBirthday) {
it(`should not accept an invalid birth date [${birthDate}]`, async () => {
const { status, body } = await request(app)
.post(`/person`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(response));
}
});
});
}
it('should create a person', async () => {
const { status, body } = await request(app)
@ -202,16 +201,16 @@ describe('/person', () => {
});
}
it('should not accept invalid birth dates', async () => {
for (const { birthDate, response } of invalidBirthday) {
for (const { birthDate, response } of invalidBirthday) {
it(`should not accept an invalid birth date [${birthDate}]`, async () => {
const { status, body } = await request(app)
.put(`/person/${visiblePerson.id}`)
.set('Authorization', `Bearer ${admin.accessToken}`)
.send({ birthDate });
expect(status).toBe(400);
expect(body).toEqual(errorDto.badRequest(response));
}
});
});
}
it('should update a date of birth', async () => {
const { status, body } = await request(app)

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -4012,7 +4012,6 @@
"required": false,
"in": "query",
"schema": {
"default": false,
"type": "boolean"
}
}
@ -8937,7 +8936,6 @@
"ScanLibraryDto": {
"properties": {
"refreshAllFiles": {
"default": false,
"type": "boolean"
},
"refreshModifiedFiles": {
@ -9346,7 +9344,6 @@
"type": "boolean"
},
"allowUpload": {
"default": false,
"type": "boolean"
},
"assetIds": {

View file

@ -1,5 +1,5 @@
import { IsBoolean, IsString } from 'class-validator';
import { Optional, ValidateUUID } from '../../domain.util';
import { IsString } from 'class-validator';
import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util';
export class UpdateAlbumDto {
@Optional()
@ -13,7 +13,6 @@ export class UpdateAlbumDto {
@ValidateUUID({ optional: true })
albumThumbnailAssetId?: string;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
isActivityEnabled?: boolean;
}

View file

@ -1,10 +1,6 @@
import { Transform } from 'class-transformer';
import { IsBoolean } from 'class-validator';
import { Optional, toBoolean } from '../../domain.util';
import { ValidateBoolean } from '../../domain.util';
export class AlbumInfoDto {
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
withoutAssets?: boolean;
}

View file

@ -1,13 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsBoolean } from 'class-validator';
import { Optional, toBoolean, ValidateUUID } from '../../domain.util';
import { ValidateBoolean, ValidateUUID } from '../../domain.util';
export class GetAlbumsDto {
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ApiProperty()
@ValidateBoolean({ optional: true })
/**
* true: only shared albums
* false: only non-shared own albums

View file

@ -1,24 +1,16 @@
import { AssetType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsBoolean } from 'class-validator';
import { Optional, toBoolean } from '../../domain.util';
import { ValidateBoolean } from '../../domain.util';
import { AssetStats } from '../../repositories';
export class AssetStatsDto {
@IsBoolean()
@Transform(toBoolean)
@Optional()
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@IsBoolean()
@Transform(toBoolean)
@Optional()
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@IsBoolean()
@Transform(toBoolean)
@Optional()
@ValidateBoolean({ optional: true })
isTrashed?: boolean;
}

View file

@ -1,6 +1,5 @@
import { Type } from 'class-transformer';
import {
IsBoolean,
IsDateString,
IsInt,
IsLatitude,
@ -10,7 +9,7 @@ import {
IsString,
ValidateIf,
} from 'class-validator';
import { Optional, ValidateUUID } from '../../domain.util';
import { Optional, ValidateBoolean, ValidateUUID } from '../../domain.util';
import { BulkIdsDto } from '../response-dto';
export class DeviceIdDto {
@ -28,23 +27,13 @@ const hasGPS = (o: { latitude: undefined; longitude: undefined }) =>
o.latitude !== undefined || o.longitude !== undefined;
const ValidateGPS = () => ValidateIf(hasGPS);
export class AssetBulkUpdateDto extends BulkIdsDto {
@Optional()
@IsBoolean()
export class UpdateAssetBase {
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@Optional()
@ValidateUUID()
stackParentId?: string;
@Optional()
@IsBoolean()
removeParent?: boolean;
@Optional()
@IsDateString()
dateTimeOriginal?: string;
@ -60,32 +49,21 @@ export class AssetBulkUpdateDto extends BulkIdsDto {
longitude?: number;
}
export class UpdateAssetDto {
@Optional()
@IsBoolean()
isFavorite?: boolean;
export class AssetBulkUpdateDto extends UpdateAssetBase {
@ValidateUUID({ each: true })
ids!: string[];
@Optional()
@IsBoolean()
isArchived?: boolean;
@ValidateUUID({ optional: true })
stackParentId?: string;
@ValidateBoolean({ optional: true })
removeParent?: boolean;
}
export class UpdateAssetDto extends UpdateAssetBase {
@Optional()
@IsString()
description?: string;
@Optional()
@IsDateString()
dateTimeOriginal?: string;
@ValidateGPS()
@IsLatitude()
@IsNotEmpty()
latitude?: number;
@ValidateGPS()
@IsLongitude()
@IsNotEmpty()
longitude?: number;
}
export class RandomAssetsDto {
@ -97,7 +75,6 @@ export class RandomAssetsDto {
}
export class AssetBulkDeleteDto extends BulkIdsDto {
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
force?: boolean;
}

View file

@ -1,34 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsBoolean, IsDate } from 'class-validator';
import { Optional, toBoolean } from '../../domain.util';
import { ValidateBoolean, ValidateDate } from '../../domain.util';
export class MapMarkerDto {
@ApiProperty()
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@ApiProperty()
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@Optional()
@IsDate()
@Type(() => Date)
@ValidateDate({ optional: true })
fileCreatedAfter?: Date;
@Optional()
@IsDate()
@Type(() => Date)
@ValidateDate({ optional: true })
fileCreatedBefore?: Date;
@ApiProperty()
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
withPartners?: boolean;
}

View file

@ -1,7 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsBoolean, IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { Optional, ValidateUUID, toBoolean } from '../../domain.util';
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { ValidateBoolean, ValidateUUID } from '../../domain.util';
import { TimeBucketSize } from '../../repositories';
export class TimeBucketDto {
@ -19,34 +18,23 @@ export class TimeBucketDto {
@ValidateUUID({ optional: true })
personId?: string;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isTrashed?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
withStacked?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
withPartners?: boolean;
}
export class TimeBucketAssetDto extends TimeBucketDto {
@IsString()
@IsNotEmpty()
timeBucket!: string;
}

View file

@ -1,14 +1,13 @@
import { AssetPathType, EntityType, PathType, PersonPathType, UserPathType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsArray, IsDate, IsEnum, IsString, IsUUID, ValidateNested } from 'class-validator';
import { Optional, ValidateUUID } from '../domain.util';
import { IsArray, IsEnum, IsString, IsUUID, ValidateNested } from 'class-validator';
import { Optional, ValidateDate, ValidateUUID } from '../domain.util';
const PathEnum = Object.values({ ...AssetPathType, ...PersonPathType, ...UserPathType });
export class AuditDeletesDto {
@IsDate()
@Type(() => Date)
@ValidateDate()
after!: Date;
@ApiProperty({ enum: EntityType, enumName: 'EntityType' })

View file

@ -1,7 +1,7 @@
import { ImmichLogger } from '@app/infra/logger';
import { applyDecorators } from '@nestjs/common';
import { BadRequestException, applyDecorators } from '@nestjs/common';
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { Transform } from 'class-transformer';
import {
IsArray,
IsBoolean,
@ -12,6 +12,7 @@ import {
IsUUID,
ValidateIf,
ValidationOptions,
isDateString,
} from 'class-validator';
import { CronJob } from 'cron';
import _ from 'lodash';
@ -40,14 +41,10 @@ export interface OpenGraphTags {
imageUrl?: string;
}
export type Options = {
optional?: boolean;
each?: boolean;
};
export const isConnectionAborted = (error: Error | any) => error.code === 'ECONNABORTED';
export function ValidateUUID(options?: Options) {
type UUIDOptions = { optional?: boolean; each?: boolean };
export const ValidateUUID = (options?: UUIDOptions) => {
const { optional, each } = { optional: false, each: false, ...options };
return applyDecorators(
IsUUID('4', { each }),
@ -55,7 +52,58 @@ export function ValidateUUID(options?: Options) {
optional ? Optional() : 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 {
@ -67,34 +115,7 @@ export function validateCronExpression(expression: string) {
return true;
}
interface IValue {
value?: string;
}
export const QueryBoolean = ({ optional }: { optional?: boolean }) => {
const decorators = [IsBoolean(), Transform(toBoolean)];
if (optional) {
decorators.push(Optional());
}
return applyDecorators(...decorators);
};
export const QueryDate = ({ optional }: { optional?: boolean }) => {
const decorators = [IsDate(), Type(() => Date)];
if (optional) {
decorators.push(Optional());
}
return applyDecorators(...decorators);
};
export const toBoolean = ({ value }: IValue) => {
if (value == 'true') {
return true;
} else if (value == 'false') {
return false;
}
return value;
};
type IValue = { value: string };
export const toEmail = ({ value }: IValue) => value?.toLowerCase();

View file

@ -1,6 +1,6 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsEnum, IsNotEmpty } from 'class-validator';
import { Optional } from '../domain.util';
import { IsEnum, IsNotEmpty } from 'class-validator';
import { ValidateBoolean } from '../domain.util';
import { JobCommand, QueueName } from './job.constants';
export class JobIdParamDto {
@ -16,8 +16,7 @@ export class JobCommandDto {
@ApiProperty({ type: 'string', enum: JobCommand, enumName: 'JobCommand' })
command!: JobCommand;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
force!: boolean;
}

View file

@ -1,7 +1,7 @@
import { LibraryEntity, LibraryType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { ArrayMaxSize, ArrayUnique, IsBoolean, IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { Optional, ValidateUUID } from '../domain.util';
import { ArrayMaxSize, ArrayUnique, IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { Optional, ValidateBoolean, ValidateUUID } from '../domain.util';
export class CreateLibraryDto {
@IsEnum(LibraryType)
@ -16,8 +16,7 @@ export class CreateLibraryDto {
@IsNotEmpty()
name?: string;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
isVisible?: boolean;
@Optional()
@ -34,8 +33,7 @@ export class CreateLibraryDto {
@ArrayMaxSize(128)
exclusionPatterns?: string[];
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
isWatched?: boolean;
}
@ -45,8 +43,7 @@ export class UpdateLibraryDto {
@IsNotEmpty()
name?: string;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
isVisible?: boolean;
@Optional()
@ -102,13 +99,11 @@ export class LibrarySearchDto {
}
export class ScanLibraryDto {
@IsBoolean()
@Optional()
@ValidateBoolean({ optional: true })
refreshModifiedFiles?: boolean;
@IsBoolean()
@Optional()
refreshAllFiles?: boolean = false;
@ValidateBoolean({ optional: true })
refreshAllFiles?: boolean;
}
export class SearchLibraryDto {

View file

@ -1,9 +1,9 @@
import { AssetFaceEntity, PersonEntity } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsArray, IsBoolean, IsDate, IsNotEmpty, IsString, MaxDate, ValidateNested } from 'class-validator';
import { Type } from 'class-transformer';
import { IsArray, IsNotEmpty, IsString, MaxDate, ValidateNested } from 'class-validator';
import { AuthDto } from '../auth';
import { Optional, ValidateUUID, toBoolean } from '../domain.util';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from '../domain.util';
export class PersonCreateDto {
/**
@ -17,18 +17,14 @@ export class PersonCreateDto {
* Person date of birth.
* Note: the mobile app cannot currently set the birth date to null.
*/
@Optional({ nullable: true })
@IsDate()
@Type(() => Date)
@MaxDate(() => new Date(), { message: 'Birth date cannot be in the future' })
@ApiProperty({ format: 'date' })
@ValidateDate({ optional: true, nullable: true, format: 'date' })
birthDate?: Date | null;
/**
* Person visibility
*/
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
isHidden?: boolean;
}
@ -63,9 +59,8 @@ export class MergePersonDto {
}
export class PersonSearchDto {
@IsBoolean()
@Transform(toBoolean)
withHidden?: boolean = false;
@ValidateBoolean({ optional: true })
withHidden?: boolean;
}
export class PersonResponseDto {

View file

@ -1,9 +1,9 @@
import { AssetOrder } from '@app/domain/asset/dto/asset.dto';
import { AssetType, GeodataPlacesEntity } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsBoolean, IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
import { Optional, QueryBoolean, QueryDate, ValidateUUID, toBoolean } from '../../domain.util';
import { Type } from 'class-transformer';
import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from '../../domain.util';
class BaseSearchDto {
@ValidateUUID({ optional: true })
@ -19,62 +19,62 @@ class BaseSearchDto {
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
type?: AssetType;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
@ApiProperty({ default: false })
withArchived?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isEncoded?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isExternal?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isMotion?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isOffline?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isReadOnly?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isVisible?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
withDeleted?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
withExif?: boolean;
@QueryDate({ optional: true })
@ValidateDate({ optional: true })
createdBefore?: Date;
@QueryDate({ optional: true })
@ValidateDate({ optional: true })
createdAfter?: Date;
@QueryDate({ optional: true })
@ValidateDate({ optional: true })
updatedBefore?: Date;
@QueryDate({ optional: true })
@ValidateDate({ optional: true })
updatedAfter?: Date;
@QueryDate({ optional: true })
@ValidateDate({ optional: true })
trashedBefore?: Date;
@QueryDate({ optional: true })
@ValidateDate({ optional: true })
trashedAfter?: Date;
@QueryDate({ optional: true })
@ValidateDate({ optional: true })
takenBefore?: Date;
@QueryDate({ optional: true })
@ValidateDate({ optional: true })
takenAfter?: Date;
@IsString()
@ -120,7 +120,7 @@ class BaseSearchDto {
@Optional()
size?: number;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
isNotInAlbum?: boolean;
@Optional()
@ -141,10 +141,10 @@ export class MetadataSearchDto extends BaseSearchDto {
@Optional()
checksum?: string;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
withStacked?: boolean;
@QueryBoolean({ optional: true })
@ValidateBoolean({ optional: true })
withPeople?: boolean;
@IsString()
@ -197,34 +197,24 @@ export class SearchDto {
@Optional()
query?: string;
@IsBoolean()
@Optional()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
smart?: boolean;
/** @deprecated */
@IsBoolean()
@Optional()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
clip?: boolean;
@IsEnum(AssetType)
@Optional()
type?: AssetType;
@IsBoolean()
@Optional()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
recent?: boolean;
@IsBoolean()
@Optional()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
motion?: boolean;
@IsBoolean()
@Optional()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
withArchived?: boolean;
@IsInt()
@ -252,9 +242,7 @@ export class SearchPeopleDto {
@IsNotEmpty()
name!: string;
@IsBoolean()
@Transform(toBoolean)
@Optional()
@ValidateBoolean({ optional: true })
withHidden?: boolean;
}

View file

@ -1,8 +1,7 @@
import { SharedLinkType } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsBoolean, IsDate, IsEnum, IsString } from 'class-validator';
import { Optional, ValidateUUID } from '../domain.util';
import { IsEnum, IsString } from 'class-validator';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from '../domain.util';
export class SharedLinkCreateDto {
@IsEnum(SharedLinkType)
@ -23,21 +22,16 @@ export class SharedLinkCreateDto {
@Optional()
password?: string;
@IsDate()
@Type(() => Date)
@Optional({ nullable: true })
@ValidateDate({ optional: true, nullable: true })
expiresAt?: Date | null = null;
@Optional()
@IsBoolean()
allowUpload?: boolean = false;
@ValidateBoolean({ optional: true })
allowUpload?: boolean;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
allowDownload?: boolean = true;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
showMetadata?: boolean = true;
}
@ -54,10 +48,10 @@ export class SharedLinkEditDto {
@Optional()
allowUpload?: boolean;
@Optional()
@ValidateBoolean({ optional: true })
allowDownload?: boolean;
@Optional()
@ValidateBoolean({ optional: true })
showMetadata?: boolean;
/**
@ -65,8 +59,7 @@ export class SharedLinkEditDto {
* Setting this flag and not sending expiryAt is considered as null instead.
* Clients that can send null values can ignore this.
*/
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
changeExpiryTime?: boolean;
}

View file

@ -1,11 +1,11 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsBoolean, IsEnum, IsNotEmpty, IsNumber, IsString, Max, Min } from 'class-validator';
import { Optional } from '../../domain.util';
import { IsEnum, IsNotEmpty, IsNumber, IsString, Max, Min } from 'class-validator';
import { Optional, ValidateBoolean } from '../../domain.util';
import { CLIPMode, ModelType } from '../../repositories';
export class ModelConfig {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
@IsString()

View file

@ -1,7 +1,8 @@
import { AudioCodec, CQMode, ToneMapping, TranscodeHWAccel, TranscodePolicy, VideoCodec } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsBoolean, IsEnum, IsInt, IsString, Max, Min } from 'class-validator';
import { IsEnum, IsInt, IsString, Max, Min } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
export class SystemConfigFFmpegDto {
@IsInt()
@ -68,14 +69,14 @@ export class SystemConfigFFmpegDto {
@ApiProperty({ type: 'integer' })
npl!: number;
@IsBoolean()
@ValidateBoolean()
temporalAQ!: boolean;
@IsEnum(CQMode)
@ApiProperty({ enumName: 'CQMode', enum: CQMode })
cqMode!: CQMode;
@IsBoolean()
@ValidateBoolean()
twoPass!: boolean;
@IsString()

View file

@ -1,7 +1,5 @@
import { validateCronExpression } from '@app/domain';
import { Type } from 'class-transformer';
import {
IsBoolean,
IsNotEmpty,
IsObject,
IsString,
@ -11,6 +9,7 @@ import {
ValidatorConstraint,
ValidatorConstraintInterface,
} from 'class-validator';
import { ValidateBoolean, validateCronExpression } from '../../domain.util';
const isEnabled = (config: SystemConfigLibraryScanDto) => config.enabled;
@ -22,7 +21,7 @@ class CronValidator implements ValidatorConstraintInterface {
}
export class SystemConfigLibraryScanDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
@ValidateIf(isEnabled)
@ -33,7 +32,7 @@ export class SystemConfigLibraryScanDto {
}
export class SystemConfigLibraryWatchDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
}

View file

@ -1,9 +1,10 @@
import { LogLevel } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { IsBoolean, IsEnum } from 'class-validator';
import { IsEnum } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
export class SystemConfigLoggingDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
@ApiProperty({ enum: LogLevel, enumName: 'LogLevel' })

View file

@ -1,9 +1,10 @@
import { CLIPConfig, RecognitionConfig } from '@app/domain';
import { Type } from 'class-transformer';
import { IsBoolean, IsObject, IsUrl, ValidateIf, ValidateNested } from 'class-validator';
import { IsObject, IsUrl, ValidateIf, ValidateNested } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
import { CLIPConfig, RecognitionConfig } from '../../smart-info/dto/model-config.dto';
export class SystemConfigMachineLearningDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
@IsUrl({ require_tld: false, allow_underscores: true })

View file

@ -1,7 +1,8 @@
import { IsBoolean, IsString } from 'class-validator';
import { IsString } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
export class SystemConfigMapDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
@IsString()

View file

@ -1,6 +1,6 @@
import { IsBoolean } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
export class SystemConfigNewVersionCheckDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
}

View file

@ -1,13 +1,14 @@
import { IsBoolean, IsNotEmpty, IsNumber, IsString, IsUrl, Min, ValidateIf } from 'class-validator';
import { IsNotEmpty, IsNumber, IsString, IsUrl, Min, ValidateIf } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
const isEnabled = (config: SystemConfigOAuthDto) => config.enabled;
const isOverrideEnabled = (config: SystemConfigOAuthDto) => config.mobileOverrideEnabled;
export class SystemConfigOAuthDto {
@IsBoolean()
@ValidateBoolean()
autoLaunch!: boolean;
@IsBoolean()
@ValidateBoolean()
autoRegister!: boolean;
@IsString()
@ -27,7 +28,7 @@ export class SystemConfigOAuthDto {
@Min(0)
defaultStorageQuota!: number;
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
@ValidateIf(isEnabled)
@ -35,7 +36,7 @@ export class SystemConfigOAuthDto {
@IsString()
issuerUrl!: string;
@IsBoolean()
@ValidateBoolean()
mobileOverrideEnabled!: boolean;
@ValidateIf(isOverrideEnabled)

View file

@ -1,6 +1,6 @@
import { IsBoolean } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
export class SystemConfigPasswordLoginDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
}

View file

@ -1,6 +1,6 @@
import { IsBoolean } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
export class SystemConfigReverseGeocodingDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
}

View file

@ -1,10 +1,13 @@
import { IsBoolean, IsNotEmpty, IsString } from 'class-validator';
import { IsNotEmpty, IsString } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
export class SystemConfigStorageTemplateDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
@IsBoolean()
@ValidateBoolean()
hashVerificationEnabled!: boolean;
@IsNotEmpty()
@IsString()
template!: string;

View file

@ -1,9 +1,10 @@
import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsBoolean, IsInt, Min } from 'class-validator';
import { IsInt, Min } from 'class-validator';
import { ValidateBoolean } from '../../domain.util';
export class SystemConfigTrashDto {
@IsBoolean()
@ValidateBoolean()
enabled!: boolean;
@IsInt()

View file

@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsBoolean, IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator';
import { Optional, toEmail, toSanitized } from '../../domain.util';
import { IsEmail, IsNotEmpty, IsNumber, IsPositive, IsString } from 'class-validator';
import { Optional, ValidateBoolean, toEmail, toSanitized } from '../../domain.util';
export class CreateUserDto {
@IsEmail({ require_tld: false })
@ -21,8 +21,7 @@ export class CreateUserDto {
@Transform(toSanitized)
storageLabel?: string | null;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
memoriesEnabled?: boolean;
@Optional({ nullable: true })
@ -31,8 +30,7 @@ export class CreateUserDto {
@ApiProperty({ type: 'integer', format: 'int64' })
quotaSizeInBytes?: number | null;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
shouldChangePassword?: boolean;
}

View file

@ -1,8 +1,8 @@
import { UserAvatarColor } from '@app/infra/entities';
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumber, IsPositive, IsString, IsUUID } from 'class-validator';
import { Optional, toEmail, toSanitized } from '../../domain.util';
import { IsEmail, IsEnum, IsNotEmpty, IsNumber, IsPositive, IsString, IsUUID } from 'class-validator';
import { Optional, ValidateBoolean, toEmail, toSanitized } from '../../domain.util';
export class UpdateUserDto {
@Optional()
@ -30,16 +30,13 @@ export class UpdateUserDto {
@ApiProperty({ format: 'uuid' })
id!: string;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
isAdmin?: boolean;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
shouldChangePassword?: boolean;
@Optional()
@IsBoolean()
@ValidateBoolean({ optional: true })
memoriesEnabled?: boolean;
@Optional()

View file

@ -1,19 +1,13 @@
import { Optional, toBoolean } from '@app/domain';
import { Optional, ValidateBoolean, ValidateDate } from '@app/domain';
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsBoolean, IsDate, IsInt, IsNotEmpty, IsUUID } from 'class-validator';
import { Type } from 'class-transformer';
import { IsInt, IsUUID } from 'class-validator';
export class AssetSearchDto {
@Optional()
@IsNotEmpty()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@Optional()
@IsNotEmpty()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@Optional()
@ -33,13 +27,9 @@ export class AssetSearchDto {
@ApiProperty({ format: 'uuid' })
userId?: string;
@Optional()
@IsDate()
@Type(() => Date)
@ValidateDate({ optional: true })
updatedAfter?: Date;
@Optional()
@IsDate()
@Type(() => Date)
@ValidateDate({ optional: true })
updatedBefore?: Date;
}

View file

@ -1,7 +1,6 @@
import { Optional, toBoolean, UploadFieldName, ValidateUUID } from '@app/domain';
import { Optional, UploadFieldName, ValidateBoolean, ValidateDate, ValidateUUID } from '@app/domain';
import { ApiProperty } from '@nestjs/swagger';
import { Transform, Type } from 'class-transformer';
import { IsBoolean, IsDate, IsNotEmpty, IsString } from 'class-validator';
import { IsNotEmpty, IsString } from 'class-validator';
export class CreateAssetDto {
@ValidateUUID({ optional: true })
@ -15,43 +14,29 @@ export class CreateAssetDto {
@IsString()
deviceId!: string;
@IsNotEmpty()
@IsDate()
@Type(() => Date)
@ValidateDate()
fileCreatedAt!: Date;
@IsNotEmpty()
@IsDate()
@Type(() => Date)
@ValidateDate()
fileModifiedAt!: Date;
@Optional()
@IsString()
duration?: string;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isFavorite?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isArchived?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isVisible?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isOffline?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ValidateBoolean({ optional: true })
isReadOnly?: boolean;
// The properties below are added to correctly generate the API docs

View file

@ -1,18 +1,12 @@
import { Optional, toBoolean } from '@app/domain';
import { ValidateBoolean } from '@app/domain';
import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import { IsBoolean } from 'class-validator';
export class ServeFileDto {
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ApiProperty({ type: Boolean, title: 'Is serve thumbnail (resize) file' })
@ValidateBoolean({ optional: true })
@ApiProperty({ title: 'Is serve thumbnail (resize) file' })
isThumb?: boolean;
@Optional()
@IsBoolean()
@Transform(toBoolean)
@ApiProperty({ type: Boolean, title: 'Is request made from web' })
@ValidateBoolean({ optional: true })
@ApiProperty({ title: 'Is request made from web' })
isWeb?: boolean;
}