refactor: validate enum (#19943)

This commit is contained in:
Jason Rasmussen 2025-07-15 13:14:57 -04:00 committed by GitHub
parent 68f249bc03
commit 351701c4d6
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
23 changed files with 161 additions and 225 deletions

View File

@ -59,6 +59,13 @@ describe(APIKeyController.name, () => {
expect(status).toBe(400); expect(status).toBe(400);
expect(body).toEqual(factory.responses.badRequest(['id must be a UUID'])); expect(body).toEqual(factory.responses.badRequest(['id must be a UUID']));
}); });
it('should allow updating just the name', async () => {
const { status } = await request(ctx.getHttpServer())
.put(`/api-keys/${factory.uuid()}`)
.send({ name: 'new name' });
expect(status).toBe(200);
});
}); });
describe('DELETE /api-keys/:id', () => { describe('DELETE /api-keys/:id', () => {

View File

@ -1,8 +1,8 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty, IsString, ValidateIf } from 'class-validator'; import { IsNotEmpty, IsString, ValidateIf } from 'class-validator';
import { Activity } from 'src/database'; import { Activity } from 'src/database';
import { mapUser, UserResponseDto } from 'src/dtos/user.dto'; import { mapUser, UserResponseDto } from 'src/dtos/user.dto';
import { Optional, ValidateUUID } from 'src/validation'; import { ValidateEnum, ValidateUUID } from 'src/validation';
export enum ReactionType { export enum ReactionType {
COMMENT = 'comment', COMMENT = 'comment',
@ -19,7 +19,7 @@ export type MaybeDuplicate<T> = { duplicate: boolean; value: T };
export class ActivityResponseDto { export class ActivityResponseDto {
id!: string; id!: string;
createdAt!: Date; createdAt!: Date;
@ApiProperty({ enumName: 'ReactionType', enum: ReactionType }) @ValidateEnum({ enum: ReactionType, name: 'ReactionType' })
type!: ReactionType; type!: ReactionType;
user!: UserResponseDto; user!: UserResponseDto;
assetId!: string | null; assetId!: string | null;
@ -43,14 +43,10 @@ export class ActivityDto {
} }
export class ActivitySearchDto extends ActivityDto { export class ActivitySearchDto extends ActivityDto {
@IsEnum(ReactionType) @ValidateEnum({ enum: ReactionType, name: 'ReactionType', optional: true })
@Optional()
@ApiProperty({ enumName: 'ReactionType', enum: ReactionType })
type?: ReactionType; type?: ReactionType;
@IsEnum(ReactionLevel) @ValidateEnum({ enum: ReactionLevel, name: 'ReactionLevel', optional: true })
@Optional()
@ApiProperty({ enumName: 'ReactionLevel', enum: ReactionLevel })
level?: ReactionLevel; level?: ReactionLevel;
@ValidateUUID({ optional: true }) @ValidateUUID({ optional: true })
@ -60,8 +56,7 @@ export class ActivitySearchDto extends ActivityDto {
const isComment = (dto: ActivityCreateDto) => dto.type === ReactionType.COMMENT; const isComment = (dto: ActivityCreateDto) => dto.type === ReactionType.COMMENT;
export class ActivityCreateDto extends ActivityDto { export class ActivityCreateDto extends ActivityDto {
@IsEnum(ReactionType) @ValidateEnum({ enum: ReactionType, name: 'ReactionType' })
@ApiProperty({ enumName: 'ReactionType', enum: ReactionType })
type!: ReactionType; type!: ReactionType;
@ValidateIf(isComment) @ValidateIf(isComment)

View File

@ -1,13 +1,13 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray, IsEnum, IsString, ValidateNested } from 'class-validator'; import { ArrayNotEmpty, IsArray, IsString, ValidateNested } from 'class-validator';
import _ from 'lodash'; import _ from 'lodash';
import { AlbumUser, AuthSharedLink, User } from 'src/database'; import { AlbumUser, AuthSharedLink, User } from 'src/database';
import { AssetResponseDto, MapAsset, mapAsset } from 'src/dtos/asset-response.dto'; import { AssetResponseDto, MapAsset, mapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
import { UserResponseDto, mapUser } from 'src/dtos/user.dto'; import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
import { AlbumUserRole, AssetOrder } from 'src/enum'; import { AlbumUserRole, AssetOrder } from 'src/enum';
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
export class AlbumInfoDto { export class AlbumInfoDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
@ -18,8 +18,7 @@ export class AlbumUserAddDto {
@ValidateUUID() @ValidateUUID()
userId!: string; userId!: string;
@IsEnum(AlbumUserRole) @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole', default: AlbumUserRole.EDITOR })
@ApiProperty({ enum: AlbumUserRole, enumName: 'AlbumUserRole', default: AlbumUserRole.EDITOR })
role?: AlbumUserRole; role?: AlbumUserRole;
} }
@ -32,8 +31,7 @@ export class AlbumUserCreateDto {
@ValidateUUID() @ValidateUUID()
userId!: string; userId!: string;
@IsEnum(AlbumUserRole) @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole' })
@ApiProperty({ enum: AlbumUserRole, enumName: 'AlbumUserRole' })
role!: AlbumUserRole; role!: AlbumUserRole;
} }
@ -71,9 +69,7 @@ export class UpdateAlbumDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isActivityEnabled?: boolean; isActivityEnabled?: boolean;
@IsEnum(AssetOrder) @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true })
@Optional()
@ApiProperty({ enum: AssetOrder, enumName: 'AssetOrder' })
order?: AssetOrder; order?: AssetOrder;
} }
@ -107,14 +103,13 @@ export class AlbumStatisticsResponseDto {
} }
export class UpdateAlbumUserDto { export class UpdateAlbumUserDto {
@IsEnum(AlbumUserRole) @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole' })
@ApiProperty({ enum: AlbumUserRole, enumName: 'AlbumUserRole' })
role!: AlbumUserRole; role!: AlbumUserRole;
} }
export class AlbumUserResponseDto { export class AlbumUserResponseDto {
user!: UserResponseDto; user!: UserResponseDto;
@ApiProperty({ enum: AlbumUserRole, enumName: 'AlbumUserRole' }) @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole' })
role!: AlbumUserRole; role!: AlbumUserRole;
} }
@ -137,8 +132,7 @@ export class AlbumResponseDto {
startDate?: Date; startDate?: Date;
endDate?: Date; endDate?: Date;
isActivityEnabled!: boolean; isActivityEnabled!: boolean;
@Optional() @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true })
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder })
order?: AssetOrder; order?: AssetOrder;
} }

View File

@ -1,15 +1,13 @@
import { ApiProperty } from '@nestjs/swagger'; import { ArrayMinSize, IsNotEmpty, IsString } from 'class-validator';
import { ArrayMinSize, IsEnum, IsNotEmpty, IsString } from 'class-validator';
import { Permission } from 'src/enum'; import { Permission } from 'src/enum';
import { Optional } from 'src/validation'; import { Optional, ValidateEnum } from 'src/validation';
export class APIKeyCreateDto { export class APIKeyCreateDto {
@IsString() @IsString()
@IsNotEmpty() @IsNotEmpty()
@Optional() @Optional()
name?: string; name?: string;
@IsEnum(Permission, { each: true }) @ValidateEnum({ enum: Permission, name: 'Permission', each: true })
@ApiProperty({ enum: Permission, enumName: 'Permission', isArray: true })
@ArrayMinSize(1) @ArrayMinSize(1)
permissions!: Permission[]; permissions!: Permission[];
} }
@ -20,9 +18,7 @@ export class APIKeyUpdateDto {
@IsNotEmpty() @IsNotEmpty()
name?: string; name?: string;
@Optional() @ValidateEnum({ enum: Permission, name: 'Permission', each: true, optional: true })
@IsEnum(Permission, { each: true })
@ApiProperty({ enum: Permission, enumName: 'Permission', isArray: true })
@ArrayMinSize(1) @ArrayMinSize(1)
permissions?: Permission[]; permissions?: Permission[];
} }
@ -37,6 +33,6 @@ export class APIKeyResponseDto {
name!: string; name!: string;
createdAt!: Date; createdAt!: Date;
updatedAt!: Date; updatedAt!: Date;
@ApiProperty({ enum: Permission, enumName: 'Permission', isArray: true }) @ValidateEnum({ enum: Permission, name: 'Permission', each: true })
permissions!: Permission[]; permissions!: Permission[];
} }

View File

@ -1,4 +1,4 @@
import { ApiProperty } from '@nestjs/swagger'; import { ValidateEnum } from 'src/validation';
export enum AssetMediaStatus { export enum AssetMediaStatus {
CREATED = 'created', CREATED = 'created',
@ -6,7 +6,7 @@ export enum AssetMediaStatus {
DUPLICATE = 'duplicate', DUPLICATE = 'duplicate',
} }
export class AssetMediaResponseDto { export class AssetMediaResponseDto {
@ApiProperty({ enum: AssetMediaStatus, enumName: 'AssetMediaStatus' }) @ValidateEnum({ enum: AssetMediaStatus, name: 'AssetMediaStatus' })
status!: AssetMediaStatus; status!: AssetMediaStatus;
id!: string; id!: string;
} }

View File

@ -1,8 +1,8 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { ArrayNotEmpty, IsArray, IsEnum, IsNotEmpty, IsString, ValidateNested } from 'class-validator'; import { ArrayNotEmpty, IsArray, IsNotEmpty, IsString, ValidateNested } from 'class-validator';
import { AssetVisibility } from 'src/enum'; import { AssetVisibility } from 'src/enum';
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
export enum AssetMediaSize { export enum AssetMediaSize {
/** /**
@ -15,9 +15,7 @@ export enum AssetMediaSize {
} }
export class AssetMediaOptionsDto { export class AssetMediaOptionsDto {
@Optional() @ValidateEnum({ enum: AssetMediaSize, name: 'AssetMediaSize', optional: true })
@IsEnum(AssetMediaSize)
@ApiProperty({ enumName: 'AssetMediaSize', enum: AssetMediaSize })
size?: AssetMediaSize; size?: AssetMediaSize;
} }
@ -60,7 +58,7 @@ export class AssetMediaCreateDto extends AssetMediaBase {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isFavorite?: boolean; isFavorite?: boolean;
@ValidateAssetVisibility({ optional: true }) @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', optional: true })
visibility?: AssetVisibility; visibility?: AssetVisibility;
@ValidateUUID({ optional: true }) @ValidateUUID({ optional: true })

View File

@ -15,10 +15,11 @@ import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
import { AssetStatus, AssetType, AssetVisibility } from 'src/enum'; import { AssetStatus, AssetType, AssetVisibility } from 'src/enum';
import { hexOrBufferToBase64 } from 'src/utils/bytes'; import { hexOrBufferToBase64 } from 'src/utils/bytes';
import { mimeTypes } from 'src/utils/mime-types'; import { mimeTypes } from 'src/utils/mime-types';
import { ValidateEnum } from 'src/validation';
export class SanitizedAssetResponseDto { export class SanitizedAssetResponseDto {
id!: string; id!: string;
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType }) @ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum' })
type!: AssetType; type!: AssetType;
thumbhash!: string | null; thumbhash!: string | null;
originalMimeType?: string; originalMimeType?: string;
@ -72,7 +73,7 @@ export class AssetResponseDto extends SanitizedAssetResponseDto {
isArchived!: boolean; isArchived!: boolean;
isTrashed!: boolean; isTrashed!: boolean;
isOffline!: boolean; isOffline!: boolean;
@ApiProperty({ enum: AssetVisibility, enumName: 'AssetVisibility' }) @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility' })
visibility!: AssetVisibility; visibility!: AssetVisibility;
exifInfo?: ExifResponseDto; exifInfo?: ExifResponseDto;
tags?: TagResponseDto[]; tags?: TagResponseDto[];

View File

@ -2,7 +2,6 @@ import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { import {
IsDateString, IsDateString,
IsEnum,
IsInt, IsInt,
IsLatitude, IsLatitude,
IsLongitude, IsLongitude,
@ -16,7 +15,7 @@ import {
import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto'; import { BulkIdsDto } from 'src/dtos/asset-ids.response.dto';
import { AssetType, AssetVisibility } from 'src/enum'; import { AssetType, AssetVisibility } from 'src/enum';
import { AssetStats } from 'src/repositories/asset.repository'; import { AssetStats } from 'src/repositories/asset.repository';
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
export class DeviceIdDto { export class DeviceIdDto {
@IsNotEmpty() @IsNotEmpty()
@ -32,7 +31,7 @@ export class UpdateAssetBase {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isFavorite?: boolean; isFavorite?: boolean;
@ValidateAssetVisibility({ optional: true }) @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', optional: true })
visibility?: AssetVisibility; visibility?: AssetVisibility;
@Optional() @Optional()
@ -99,13 +98,12 @@ export enum AssetJobName {
} }
export class AssetJobsDto extends AssetIdsDto { export class AssetJobsDto extends AssetIdsDto {
@ApiProperty({ enumName: 'AssetJobName', enum: AssetJobName }) @ValidateEnum({ enum: AssetJobName, name: 'AssetJobName' })
@IsEnum(AssetJobName)
name!: AssetJobName; name!: AssetJobName;
} }
export class AssetStatsDto { export class AssetStatsDto {
@ValidateAssetVisibility({ optional: true }) @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', optional: true })
visibility?: AssetVisibility; visibility?: AssetVisibility;
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })

View File

@ -1,19 +1,14 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsNotEmpty } from 'class-validator';
import { JobCommand, ManualJobName, QueueName } from 'src/enum'; import { JobCommand, ManualJobName, QueueName } from 'src/enum';
import { ValidateBoolean } from 'src/validation'; import { ValidateBoolean, ValidateEnum } from 'src/validation';
export class JobIdParamDto { export class JobIdParamDto {
@IsNotEmpty() @ValidateEnum({ enum: QueueName, name: 'JobName' })
@IsEnum(QueueName)
@ApiProperty({ type: String, enum: QueueName, enumName: 'JobName' })
id!: QueueName; id!: QueueName;
} }
export class JobCommandDto { export class JobCommandDto {
@IsNotEmpty() @ValidateEnum({ enum: JobCommand, name: 'JobCommand' })
@IsEnum(JobCommand)
@ApiProperty({ type: 'string', enum: JobCommand, enumName: 'JobCommand' })
command!: JobCommand; command!: JobCommand;
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
@ -21,8 +16,7 @@ export class JobCommandDto {
} }
export class JobCreateDto { export class JobCreateDto {
@IsEnum(ManualJobName) @ValidateEnum({ enum: ManualJobName, name: 'ManualJobName' })
@ApiProperty({ type: 'string', enum: ManualJobName, enumName: 'ManualJobName' })
name!: ManualJobName; name!: ManualJobName;
} }

View File

@ -1,11 +1,11 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsEnum, IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator'; import { IsInt, IsObject, IsPositive, ValidateNested } from 'class-validator';
import { Memory } from 'src/database'; import { Memory } from 'src/database';
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
import { AuthDto } from 'src/dtos/auth.dto'; import { AuthDto } from 'src/dtos/auth.dto';
import { MemoryType } from 'src/enum'; import { MemoryType } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
class MemoryBaseDto { class MemoryBaseDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
@ -16,9 +16,7 @@ class MemoryBaseDto {
} }
export class MemorySearchDto { export class MemorySearchDto {
@Optional() @ValidateEnum({ enum: MemoryType, name: 'MemoryType', optional: true })
@IsEnum(MemoryType)
@ApiProperty({ enum: MemoryType, enumName: 'MemoryType' })
type?: MemoryType; type?: MemoryType;
@ValidateDate({ optional: true }) @ValidateDate({ optional: true })
@ -45,8 +43,7 @@ export class MemoryUpdateDto extends MemoryBaseDto {
} }
export class MemoryCreateDto extends MemoryBaseDto { export class MemoryCreateDto extends MemoryBaseDto {
@IsEnum(MemoryType) @ValidateEnum({ enum: MemoryType, name: 'MemoryType' })
@ApiProperty({ enum: MemoryType, enumName: 'MemoryType' })
type!: MemoryType; type!: MemoryType;
@IsObject() @IsObject()
@ -86,7 +83,7 @@ export class MemoryResponseDto {
showAt?: Date; showAt?: Date;
hideAt?: Date; hideAt?: Date;
ownerId!: string; ownerId!: string;
@ApiProperty({ enumName: 'MemoryType', enum: MemoryType }) @ValidateEnum({ enum: MemoryType, name: 'MemoryType' })
type!: MemoryType; type!: MemoryType;
data!: MemoryData; data!: MemoryData;
isSaved!: boolean; isSaved!: boolean;

View File

@ -1,7 +1,6 @@
import { ApiProperty } from '@nestjs/swagger'; import { IsString } from 'class-validator';
import { IsEnum, IsString } from 'class-validator';
import { NotificationLevel, NotificationType } from 'src/enum'; import { NotificationLevel, NotificationType } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
export class TestEmailResponseDto { export class TestEmailResponseDto {
messageId!: string; messageId!: string;
@ -19,9 +18,9 @@ export class NotificationDto {
id!: string; id!: string;
@ValidateDate() @ValidateDate()
createdAt!: Date; createdAt!: Date;
@ApiProperty({ enum: NotificationLevel, enumName: 'NotificationLevel' }) @ValidateEnum({ enum: NotificationLevel, name: 'NotificationLevel' })
level!: NotificationLevel; level!: NotificationLevel;
@ApiProperty({ enum: NotificationType, enumName: 'NotificationType' }) @ValidateEnum({ enum: NotificationType, name: 'NotificationType' })
type!: NotificationType; type!: NotificationType;
title!: string; title!: string;
description?: string; description?: string;
@ -30,18 +29,13 @@ export class NotificationDto {
} }
export class NotificationSearchDto { export class NotificationSearchDto {
@Optional()
@ValidateUUID({ optional: true }) @ValidateUUID({ optional: true })
id?: string; id?: string;
@IsEnum(NotificationLevel) @ValidateEnum({ enum: NotificationLevel, name: 'NotificationLevel', optional: true })
@Optional()
@ApiProperty({ enum: NotificationLevel, enumName: 'NotificationLevel' })
level?: NotificationLevel; level?: NotificationLevel;
@IsEnum(NotificationType) @ValidateEnum({ enum: NotificationType, name: 'NotificationType', optional: true })
@Optional()
@ApiProperty({ enum: NotificationType, enumName: 'NotificationType' })
type?: NotificationType; type?: NotificationType;
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
@ -49,14 +43,10 @@ export class NotificationSearchDto {
} }
export class NotificationCreateDto { export class NotificationCreateDto {
@Optional() @ValidateEnum({ enum: NotificationLevel, name: 'NotificationLevel', optional: true })
@IsEnum(NotificationLevel)
@ApiProperty({ enum: NotificationLevel, enumName: 'NotificationLevel' })
level?: NotificationLevel; level?: NotificationLevel;
@IsEnum(NotificationType) @ValidateEnum({ enum: NotificationType, name: 'NotificationType', optional: true })
@Optional()
@ApiProperty({ enum: NotificationType, enumName: 'NotificationType' })
type?: NotificationType; type?: NotificationType;
@IsString() @IsString()

View File

@ -1,8 +1,7 @@
import { IsBoolean, IsNotEmpty } from 'class-validator'; import { ValidateBoolean } from 'src/validation';
export class OnboardingDto { export class OnboardingDto {
@IsBoolean() @ValidateBoolean()
@IsNotEmpty()
isOnboarded!: boolean; isOnboarded!: boolean;
} }

View File

@ -1,7 +1,7 @@
import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty } from 'class-validator';
import { IsEnum, IsNotEmpty } from 'class-validator';
import { UserResponseDto } from 'src/dtos/user.dto'; import { UserResponseDto } from 'src/dtos/user.dto';
import { PartnerDirection } from 'src/repositories/partner.repository'; import { PartnerDirection } from 'src/repositories/partner.repository';
import { ValidateEnum } from 'src/validation';
export class UpdatePartnerDto { export class UpdatePartnerDto {
@IsNotEmpty() @IsNotEmpty()
@ -9,8 +9,7 @@ export class UpdatePartnerDto {
} }
export class PartnerSearchDto { export class PartnerSearchDto {
@IsEnum(PartnerDirection) @ValidateEnum({ enum: PartnerDirection, name: 'PartnerDirection' })
@ApiProperty({ enum: PartnerDirection, enumName: 'PartnerDirection' })
direction!: PartnerDirection; direction!: PartnerDirection;
} }

View File

@ -14,6 +14,7 @@ import {
MaxDateString, MaxDateString,
Optional, Optional,
ValidateBoolean, ValidateBoolean,
ValidateEnum,
ValidateHexColor, ValidateHexColor,
ValidateUUID, ValidateUUID,
} from 'src/validation'; } from 'src/validation';
@ -137,7 +138,7 @@ export class AssetFaceWithoutPersonResponseDto {
boundingBoxY1!: number; boundingBoxY1!: number;
@ApiProperty({ type: 'integer' }) @ApiProperty({ type: 'integer' })
boundingBoxY2!: number; boundingBoxY2!: number;
@ApiProperty({ enum: SourceType, enumName: 'SourceType' }) @ValidateEnum({ enum: SourceType, name: 'SourceType' })
sourceType?: SourceType; sourceType?: SourceType;
} }

View File

@ -1,12 +1,12 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator'; import { IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator';
import { Place } from 'src/database'; import { Place } from 'src/database';
import { PropertyLifecycle } from 'src/decorators'; import { PropertyLifecycle } from 'src/decorators';
import { AlbumResponseDto } from 'src/dtos/album.dto'; import { AlbumResponseDto } from 'src/dtos/album.dto';
import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { AssetOrder, AssetType, AssetVisibility } from 'src/enum'; import { AssetOrder, AssetType, AssetVisibility } from 'src/enum';
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
class BaseSearchDto { class BaseSearchDto {
@ValidateUUID({ optional: true, nullable: true }) @ValidateUUID({ optional: true, nullable: true })
@ -17,9 +17,7 @@ class BaseSearchDto {
@Optional() @Optional()
deviceId?: string; deviceId?: string;
@IsEnum(AssetType) @ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum', optional: true })
@Optional()
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
type?: AssetType; type?: AssetType;
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
@ -34,7 +32,7 @@ class BaseSearchDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
isOffline?: boolean; isOffline?: boolean;
@ValidateAssetVisibility({ optional: true }) @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility', optional: true })
visibility?: AssetVisibility; visibility?: AssetVisibility;
@ValidateDate({ optional: true }) @ValidateDate({ optional: true })
@ -172,9 +170,7 @@ export class MetadataSearchDto extends RandomSearchDto {
@Optional() @Optional()
encodedVideoPath?: string; encodedVideoPath?: string;
@IsEnum(AssetOrder) @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true, default: AssetOrder.DESC })
@Optional()
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder, default: AssetOrder.DESC })
order?: AssetOrder; order?: AssetOrder;
@IsInt() @IsInt()
@ -250,9 +246,7 @@ export enum SearchSuggestionType {
} }
export class SearchSuggestionRequestDto { export class SearchSuggestionRequestDto {
@IsEnum(SearchSuggestionType) @ValidateEnum({ enum: SearchSuggestionType, name: 'SearchSuggestionType' })
@IsNotEmpty()
@ApiProperty({ enumName: 'SearchSuggestionType', enum: SearchSuggestionType })
type!: SearchSuggestionType; type!: SearchSuggestionType;
@IsString() @IsString()

View File

@ -1,11 +1,11 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsString } from 'class-validator'; import { IsString } from 'class-validator';
import _ from 'lodash'; import _ from 'lodash';
import { SharedLink } from 'src/database'; import { SharedLink } from 'src/database';
import { AlbumResponseDto, mapAlbumWithoutAssets } from 'src/dtos/album.dto'; import { AlbumResponseDto, mapAlbumWithoutAssets } from 'src/dtos/album.dto';
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto'; import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
import { SharedLinkType } from 'src/enum'; import { SharedLinkType } from 'src/enum';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { Optional, ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
export class SharedLinkSearchDto { export class SharedLinkSearchDto {
@ValidateUUID({ optional: true }) @ValidateUUID({ optional: true })
@ -13,8 +13,7 @@ export class SharedLinkSearchDto {
} }
export class SharedLinkCreateDto { export class SharedLinkCreateDto {
@IsEnum(SharedLinkType) @ValidateEnum({ enum: SharedLinkType, name: 'SharedLinkType' })
@ApiProperty({ enum: SharedLinkType, enumName: 'SharedLinkType' })
type!: SharedLinkType; type!: SharedLinkType;
@ValidateUUID({ each: true, optional: true }) @ValidateUUID({ each: true, optional: true })
@ -90,7 +89,7 @@ export class SharedLinkResponseDto {
userId!: string; userId!: string;
key!: string; key!: string;
@ApiProperty({ enumName: 'SharedLinkType', enum: SharedLinkType }) @ValidateEnum({ enum: SharedLinkType, name: 'SharedLinkType' })
type!: SharedLinkType; type!: SharedLinkType;
createdAt!: Date; createdAt!: Date;
expiresAt!: Date | null; expiresAt!: Date | null;

View File

@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-unsafe-function-type */ /* eslint-disable @typescript-eslint/no-unsafe-function-type */
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { ArrayMaxSize, IsEnum, IsInt, IsPositive, IsString } from 'class-validator'; import { ArrayMaxSize, IsInt, IsPositive, IsString } from 'class-validator';
import { AssetResponseDto } from 'src/dtos/asset-response.dto'; import { AssetResponseDto } from 'src/dtos/asset-response.dto';
import { import {
AlbumUserRole, AlbumUserRole,
@ -13,7 +13,7 @@ import {
UserMetadataKey, UserMetadataKey,
} from 'src/enum'; } from 'src/enum';
import { UserMetadata } from 'src/types'; import { UserMetadata } from 'src/types';
import { Optional, ValidateBoolean, ValidateDate, ValidateUUID } from 'src/validation'; import { ValidateBoolean, ValidateDate, ValidateEnum, ValidateUUID } from 'src/validation';
export class AssetFullSyncDto { export class AssetFullSyncDto {
@ValidateUUID({ optional: true }) @ValidateUUID({ optional: true })
@ -90,11 +90,11 @@ export class SyncAssetV1 {
fileModifiedAt!: Date | null; fileModifiedAt!: Date | null;
localDateTime!: Date | null; localDateTime!: Date | null;
duration!: string | null; duration!: string | null;
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType }) @ValidateEnum({ enum: AssetType, name: 'AssetTypeEnum' })
type!: AssetType; type!: AssetType;
deletedAt!: Date | null; deletedAt!: Date | null;
isFavorite!: boolean; isFavorite!: boolean;
@ApiProperty({ enumName: 'AssetVisibility', enum: AssetVisibility }) @ValidateEnum({ enum: AssetVisibility, name: 'AssetVisibility' })
visibility!: AssetVisibility; visibility!: AssetVisibility;
livePhotoVideoId!: string | null; livePhotoVideoId!: string | null;
stackId!: string | null; stackId!: string | null;
@ -159,7 +159,7 @@ export class SyncAlbumUserDeleteV1 {
export class SyncAlbumUserV1 { export class SyncAlbumUserV1 {
albumId!: string; albumId!: string;
userId!: string; userId!: string;
@ApiProperty({ enumName: 'AlbumUserRole', enum: AlbumUserRole }) @ValidateEnum({ enum: AlbumUserRole, name: 'AlbumUserRole' })
role!: AlbumUserRole; role!: AlbumUserRole;
} }
@ -173,7 +173,7 @@ export class SyncAlbumV1 {
updatedAt!: Date; updatedAt!: Date;
thumbnailAssetId!: string | null; thumbnailAssetId!: string | null;
isActivityEnabled!: boolean; isActivityEnabled!: boolean;
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder }) @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' })
order!: AssetOrder; order!: AssetOrder;
} }
@ -196,7 +196,7 @@ export class SyncMemoryV1 {
updatedAt!: Date; updatedAt!: Date;
deletedAt!: Date | null; deletedAt!: Date | null;
ownerId!: string; ownerId!: string;
@ApiProperty({ enumName: 'MemoryType', enum: MemoryType }) @ValidateEnum({ enum: MemoryType, name: 'MemoryType' })
type!: MemoryType; type!: MemoryType;
data!: object; data!: object;
isSaved!: boolean; isSaved!: boolean;
@ -319,8 +319,7 @@ export type SyncItem = {
}; };
export class SyncStreamDto { export class SyncStreamDto {
@IsEnum(SyncRequestType, { each: true }) @ValidateEnum({ enum: SyncRequestType, name: 'SyncRequestType', each: true })
@ApiProperty({ enumName: 'SyncRequestType', enum: SyncRequestType, isArray: true })
types!: SyncRequestType[]; types!: SyncRequestType[];
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
@ -328,7 +327,7 @@ export class SyncStreamDto {
} }
export class SyncAckDto { export class SyncAckDto {
@ApiProperty({ enumName: 'SyncEntityType', enum: SyncEntityType }) @ValidateEnum({ enum: SyncEntityType, name: 'SyncEntityType' })
type!: SyncEntityType; type!: SyncEntityType;
ack!: string; ack!: string;
} }
@ -340,8 +339,6 @@ export class SyncAckSetDto {
} }
export class SyncAckDeleteDto { export class SyncAckDeleteDto {
@IsEnum(SyncEntityType, { each: true }) @ValidateEnum({ enum: SyncEntityType, name: 'SyncEntityType', optional: true, each: true })
@ApiProperty({ enumName: 'SyncEntityType', enum: SyncEntityType, isArray: true })
@Optional()
types?: SyncEntityType[]; types?: SyncEntityType[];
} }

View File

@ -2,8 +2,6 @@ import { ApiProperty } from '@nestjs/swagger';
import { Exclude, Transform, Type } from 'class-transformer'; import { Exclude, Transform, Type } from 'class-transformer';
import { import {
ArrayMinSize, ArrayMinSize,
IsBoolean,
IsEnum,
IsInt, IsInt,
IsNotEmpty, IsNotEmpty,
IsNumber, IsNumber,
@ -34,7 +32,7 @@ import {
VideoContainer, VideoContainer,
} from 'src/enum'; } from 'src/enum';
import { ConcurrentQueueName } from 'src/types'; import { ConcurrentQueueName } from 'src/types';
import { IsCronExpression, IsDateStringFormat, Optional, ValidateBoolean } from 'src/validation'; import { IsCronExpression, IsDateStringFormat, Optional, ValidateBoolean, ValidateEnum } from 'src/validation';
const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled; const isLibraryScanEnabled = (config: SystemConfigLibraryScanDto) => config.enabled;
const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled; const isOAuthEnabled = (config: SystemConfigOAuthDto) => config.enabled;
@ -82,24 +80,19 @@ export class SystemConfigFFmpegDto {
@IsString() @IsString()
preset!: string; preset!: string;
@IsEnum(VideoCodec) @ValidateEnum({ enum: VideoCodec, name: 'VideoCodec' })
@ApiProperty({ enumName: 'VideoCodec', enum: VideoCodec })
targetVideoCodec!: VideoCodec; targetVideoCodec!: VideoCodec;
@IsEnum(VideoCodec, { each: true }) @ValidateEnum({ enum: VideoCodec, name: 'VideoCodec', each: true })
@ApiProperty({ enumName: 'VideoCodec', enum: VideoCodec, isArray: true })
acceptedVideoCodecs!: VideoCodec[]; acceptedVideoCodecs!: VideoCodec[];
@IsEnum(AudioCodec) @ValidateEnum({ enum: AudioCodec, name: 'AudioCodec' })
@ApiProperty({ enumName: 'AudioCodec', enum: AudioCodec })
targetAudioCodec!: AudioCodec; targetAudioCodec!: AudioCodec;
@IsEnum(AudioCodec, { each: true }) @ValidateEnum({ enum: AudioCodec, name: 'AudioCodec', each: true })
@ApiProperty({ enumName: 'AudioCodec', enum: AudioCodec, isArray: true })
acceptedAudioCodecs!: AudioCodec[]; acceptedAudioCodecs!: AudioCodec[];
@IsEnum(VideoContainer, { each: true }) @ValidateEnum({ enum: VideoContainer, name: 'VideoContainer', each: true })
@ApiProperty({ enumName: 'VideoContainer', enum: VideoContainer, isArray: true })
acceptedContainers!: VideoContainer[]; acceptedContainers!: VideoContainer[];
@IsString() @IsString()
@ -131,8 +124,7 @@ export class SystemConfigFFmpegDto {
@ValidateBoolean() @ValidateBoolean()
temporalAQ!: boolean; temporalAQ!: boolean;
@IsEnum(CQMode) @ValidateEnum({ enum: CQMode, name: 'CQMode' })
@ApiProperty({ enumName: 'CQMode', enum: CQMode })
cqMode!: CQMode; cqMode!: CQMode;
@ValidateBoolean() @ValidateBoolean()
@ -141,19 +133,16 @@ export class SystemConfigFFmpegDto {
@IsString() @IsString()
preferredHwDevice!: string; preferredHwDevice!: string;
@IsEnum(TranscodePolicy) @ValidateEnum({ enum: TranscodePolicy, name: 'TranscodePolicy' })
@ApiProperty({ enumName: 'TranscodePolicy', enum: TranscodePolicy })
transcode!: TranscodePolicy; transcode!: TranscodePolicy;
@IsEnum(TranscodeHWAccel) @ValidateEnum({ enum: TranscodeHWAccel, name: 'TranscodeHWAccel' })
@ApiProperty({ enumName: 'TranscodeHWAccel', enum: TranscodeHWAccel })
accel!: TranscodeHWAccel; accel!: TranscodeHWAccel;
@ValidateBoolean() @ValidateBoolean()
accelDecode!: boolean; accelDecode!: boolean;
@IsEnum(ToneMapping) @ValidateEnum({ enum: ToneMapping, name: 'ToneMapping' })
@ApiProperty({ enumName: 'ToneMapping', enum: ToneMapping })
tonemap!: ToneMapping; tonemap!: ToneMapping;
} }
@ -264,8 +253,7 @@ class SystemConfigLoggingDto {
@ValidateBoolean() @ValidateBoolean()
enabled!: boolean; enabled!: boolean;
@ApiProperty({ enum: LogLevel, enumName: 'LogLevel' }) @ValidateEnum({ enum: LogLevel, name: 'LogLevel' })
@IsEnum(LogLevel)
level!: LogLevel; level!: LogLevel;
} }
@ -306,8 +294,7 @@ enum MapTheme {
} }
export class MapThemeDto { export class MapThemeDto {
@IsEnum(MapTheme) @ValidateEnum({ enum: MapTheme, name: 'MapTheme' })
@ApiProperty({ enum: MapTheme, enumName: 'MapTheme' })
theme!: MapTheme; theme!: MapTheme;
} }
@ -368,8 +355,7 @@ class SystemConfigOAuthDto {
@IsString() @IsString()
clientSecret!: string; clientSecret!: string;
@IsEnum(OAuthTokenEndpointAuthMethod) @ValidateEnum({ enum: OAuthTokenEndpointAuthMethod, name: 'OAuthTokenEndpointAuthMethod' })
@ApiProperty({ enum: OAuthTokenEndpointAuthMethod, enumName: 'OAuthTokenEndpointAuthMethod' })
tokenEndpointAuthMethod!: OAuthTokenEndpointAuthMethod; tokenEndpointAuthMethod!: OAuthTokenEndpointAuthMethod;
@IsInt() @IsInt()
@ -431,7 +417,7 @@ class SystemConfigReverseGeocodingDto {
} }
class SystemConfigFacesDto { class SystemConfigFacesDto {
@IsBoolean() @ValidateBoolean()
import!: boolean; import!: boolean;
} }
@ -450,12 +436,12 @@ class SystemConfigServerDto {
@IsString() @IsString()
loginPageMessage!: string; loginPageMessage!: string;
@IsBoolean() @ValidateBoolean()
publicUsers!: boolean; publicUsers!: boolean;
} }
class SystemConfigSmtpTransportDto { class SystemConfigSmtpTransportDto {
@IsBoolean() @ValidateBoolean()
ignoreCert!: boolean; ignoreCert!: boolean;
@IsNotEmpty() @IsNotEmpty()
@ -475,7 +461,7 @@ class SystemConfigSmtpTransportDto {
} }
export class SystemConfigSmtpDto { export class SystemConfigSmtpDto {
@IsBoolean() @ValidateBoolean()
enabled!: boolean; enabled!: boolean;
@ValidateIf(isEmailNotificationEnabled) @ValidateIf(isEmailNotificationEnabled)
@ -548,8 +534,7 @@ export class SystemConfigThemeDto {
} }
class SystemConfigGeneratedImageDto { class SystemConfigGeneratedImageDto {
@IsEnum(ImageFormat) @ValidateEnum({ enum: ImageFormat, name: 'ImageFormat' })
@ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat })
format!: ImageFormat; format!: ImageFormat;
@IsInt() @IsInt()
@ -567,13 +552,10 @@ class SystemConfigGeneratedImageDto {
} }
class SystemConfigGeneratedFullsizeImageDto { class SystemConfigGeneratedFullsizeImageDto {
@IsBoolean() @ValidateBoolean()
@Type(() => Boolean)
@ApiProperty({ type: 'boolean' })
enabled!: boolean; enabled!: boolean;
@IsEnum(ImageFormat) @ValidateEnum({ enum: ImageFormat, name: 'ImageFormat' })
@ApiProperty({ enumName: 'ImageFormat', enum: ImageFormat })
format!: ImageFormat; format!: ImageFormat;
@IsInt() @IsInt()
@ -600,8 +582,7 @@ export class SystemConfigImageDto {
@IsObject() @IsObject()
fullsize!: SystemConfigGeneratedFullsizeImageDto; fullsize!: SystemConfigGeneratedFullsizeImageDto;
@IsEnum(Colorspace) @ValidateEnum({ enum: Colorspace, name: 'Colorspace' })
@ApiProperty({ enumName: 'Colorspace', enum: Colorspace })
colorspace!: Colorspace; colorspace!: Colorspace;
@ValidateBoolean() @ValidateBoolean()

View File

@ -1,11 +1,12 @@
import { IsBoolean } from 'class-validator'; import { ValidateBoolean } from 'src/validation';
export class AdminOnboardingUpdateDto { export class AdminOnboardingUpdateDto {
@IsBoolean() @ValidateBoolean()
isOnboarded!: boolean; isOnboarded!: boolean;
} }
export class AdminOnboardingResponseDto { export class AdminOnboardingResponseDto {
@ValidateBoolean()
isOnboarded!: boolean; isOnboarded!: boolean;
} }

View File

@ -1,8 +1,8 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { IsEnum, IsString } from 'class-validator'; import { IsString } from 'class-validator';
import { AssetOrder, AssetVisibility } from 'src/enum'; import { AssetOrder, AssetVisibility } from 'src/enum';
import { Optional, ValidateAssetVisibility, ValidateBoolean, ValidateUUID } from 'src/validation'; import { ValidateBoolean, ValidateEnum, ValidateUUID } from 'src/validation';
export class TimeBucketDto { export class TimeBucketDto {
@ValidateUUID({ optional: true, description: 'Filter assets by specific user ID' }) @ValidateUUID({ optional: true, description: 'Filter assets by specific user ID' })
@ -38,16 +38,17 @@ export class TimeBucketDto {
@ValidateBoolean({ optional: true, description: 'Include assets shared by partners' }) @ValidateBoolean({ optional: true, description: 'Include assets shared by partners' })
withPartners?: boolean; withPartners?: boolean;
@IsEnum(AssetOrder) @ValidateEnum({
@Optional()
@ApiProperty({
enum: AssetOrder, enum: AssetOrder,
enumName: 'AssetOrder', name: 'AssetOrder',
description: 'Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)', description: 'Sort order for assets within time buckets (ASC for oldest first, DESC for newest first)',
optional: true,
}) })
order?: AssetOrder; order?: AssetOrder;
@ValidateAssetVisibility({ @ValidateEnum({
enum: AssetVisibility,
name: 'AssetVisibility',
optional: true, optional: true,
description: 'Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)', description: 'Filter by asset visibility status (ARCHIVE, TIMELINE, HIDDEN, LOCKED)',
}) })
@ -93,10 +94,10 @@ export class TimeBucketAssetResponseDto {
}) })
isFavorite!: boolean[]; isFavorite!: boolean[];
@ApiProperty({ @ValidateEnum({
enum: AssetVisibility, enum: AssetVisibility,
enumName: 'AssetVisibility', name: 'AssetVisibility',
isArray: true, each: true,
description: 'Array of visibility statuses for each asset (e.g., ARCHIVE, TIMELINE, HIDDEN, LOCKED)', description: 'Array of visibility statuses for each asset (e.g., ARCHIVE, TIMELINE, HIDDEN, LOCKED)',
}) })
visibility!: AssetVisibility[]; visibility!: AssetVisibility[];

View File

@ -1,14 +1,12 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Type } from 'class-transformer'; import { Type } from 'class-transformer';
import { IsDateString, IsEnum, IsInt, IsPositive, ValidateNested } from 'class-validator'; import { IsDateString, IsInt, IsPositive, ValidateNested } from 'class-validator';
import { AssetOrder, UserAvatarColor } from 'src/enum'; import { AssetOrder, UserAvatarColor } from 'src/enum';
import { UserPreferences } from 'src/types'; import { UserPreferences } from 'src/types';
import { Optional, ValidateBoolean } from 'src/validation'; import { Optional, ValidateBoolean, ValidateEnum } from 'src/validation';
class AvatarUpdate { class AvatarUpdate {
@Optional() @ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', optional: true })
@IsEnum(UserAvatarColor)
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
color?: UserAvatarColor; color?: UserAvatarColor;
} }
@ -23,8 +21,7 @@ class RatingsUpdate {
} }
class AlbumsUpdate { class AlbumsUpdate {
@IsEnum(AssetOrder) @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder', optional: true })
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder })
defaultAssetOrder?: AssetOrder; defaultAssetOrder?: AssetOrder;
} }
@ -159,8 +156,7 @@ export class UserPreferencesUpdateDto {
} }
class AlbumsResponse { class AlbumsResponse {
@IsEnum(AssetOrder) @ValidateEnum({ enum: AssetOrder, name: 'AssetOrder' })
@ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder })
defaultAssetOrder: AssetOrder = AssetOrder.DESC; defaultAssetOrder: AssetOrder = AssetOrder.DESC;
} }

View File

@ -1,10 +1,10 @@
import { ApiProperty } from '@nestjs/swagger'; import { ApiProperty } from '@nestjs/swagger';
import { Transform } from 'class-transformer'; import { Transform } from 'class-transformer';
import { IsBoolean, IsEmail, IsEnum, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator'; import { IsEmail, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator';
import { User, UserAdmin } from 'src/database'; import { User, UserAdmin } from 'src/database';
import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum'; import { UserAvatarColor, UserMetadataKey, UserStatus } from 'src/enum';
import { UserMetadataItem } from 'src/types'; import { UserMetadataItem } from 'src/types';
import { Optional, PinCode, ValidateBoolean, ValidateUUID, toEmail, toSanitized } from 'src/validation'; import { Optional, PinCode, ValidateBoolean, ValidateEnum, ValidateUUID, toEmail, toSanitized } from 'src/validation';
export class UserUpdateMeDto { export class UserUpdateMeDto {
@Optional() @Optional()
@ -23,9 +23,7 @@ export class UserUpdateMeDto {
@IsNotEmpty() @IsNotEmpty()
name?: string; name?: string;
@Optional({ nullable: true }) @ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', optional: true, nullable: true })
@IsEnum(UserAvatarColor)
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
avatarColor?: UserAvatarColor | null; avatarColor?: UserAvatarColor | null;
} }
@ -34,7 +32,7 @@ export class UserResponseDto {
name!: string; name!: string;
email!: string; email!: string;
profileImagePath!: string; profileImagePath!: string;
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor }) @ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor' })
avatarColor!: UserAvatarColor; avatarColor!: UserAvatarColor;
profileChangedAt!: Date; profileChangedAt!: Date;
} }
@ -84,9 +82,7 @@ export class UserAdminCreateDto {
@IsString() @IsString()
name!: string; name!: string;
@Optional({ nullable: true }) @ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', optional: true, nullable: true })
@IsEnum(UserAvatarColor)
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
avatarColor?: UserAvatarColor | null; avatarColor?: UserAvatarColor | null;
@Optional({ nullable: true }) @Optional({ nullable: true })
@ -103,12 +99,10 @@ export class UserAdminCreateDto {
@ValidateBoolean({ optional: true }) @ValidateBoolean({ optional: true })
shouldChangePassword?: boolean; shouldChangePassword?: boolean;
@Optional() @ValidateBoolean({ optional: true })
@IsBoolean()
notify?: boolean; notify?: boolean;
@Optional() @ValidateBoolean({ optional: true })
@IsBoolean()
isAdmin?: boolean; isAdmin?: boolean;
} }
@ -131,9 +125,7 @@ export class UserAdminUpdateDto {
@IsNotEmpty() @IsNotEmpty()
name?: string; name?: string;
@Optional({ nullable: true }) @ValidateEnum({ enum: UserAvatarColor, name: 'UserAvatarColor', optional: true, nullable: true })
@IsEnum(UserAvatarColor)
@ApiProperty({ enumName: 'UserAvatarColor', enum: UserAvatarColor })
avatarColor?: UserAvatarColor | null; avatarColor?: UserAvatarColor | null;
@Optional({ nullable: true }) @Optional({ nullable: true })
@ -150,8 +142,7 @@ export class UserAdminUpdateDto {
@ApiProperty({ type: 'integer', format: 'int64' }) @ApiProperty({ type: 'integer', format: 'int64' })
quotaSizeInBytes?: number | null; quotaSizeInBytes?: number | null;
@Optional() @ValidateBoolean({ optional: true })
@IsBoolean()
isAdmin?: boolean; isAdmin?: boolean;
} }
@ -172,7 +163,7 @@ export class UserAdminResponseDto extends UserResponseDto {
quotaSizeInBytes!: number | null; quotaSizeInBytes!: number | null;
@ApiProperty({ type: 'integer', format: 'int64' }) @ApiProperty({ type: 'integer', format: 'int64' })
quotaUsageInBytes!: number | null; quotaUsageInBytes!: number | null;
@ApiProperty({ enumName: 'UserStatus', enum: UserStatus }) @ValidateEnum({ enum: UserStatus, name: 'UserStatus' })
status!: string; status!: string;
license!: UserLicense | null; license!: UserLicense | null;
} }

View File

@ -31,7 +31,6 @@ import {
import { CronJob } from 'cron'; import { CronJob } from 'cron';
import { DateTime } from 'luxon'; import { DateTime } from 'luxon';
import sanitize from 'sanitize-filename'; import sanitize from 'sanitize-filename';
import { AssetVisibility } from 'src/enum';
import { isIP, isIPRange } from 'validator'; import { isIP, isIPRange } from 'validator';
@Injectable() @Injectable()
@ -181,23 +180,9 @@ export const ValidateDate = (options?: DateOptions & ApiPropertyOptions) => {
return applyDecorators(...decorators); return applyDecorators(...decorators);
}; };
type AssetVisibilityOptions = { optional?: boolean }; type BooleanOptions = { optional?: boolean; nullable?: boolean };
export const ValidateAssetVisibility = (options?: AssetVisibilityOptions & ApiPropertyOptions) => {
const { optional, ...apiPropertyOptions } = { optional: false, ...options };
const decorators = [
IsEnum(AssetVisibility),
ApiProperty({ enumName: 'AssetVisibility', enum: AssetVisibility, ...apiPropertyOptions }),
];
if (optional) {
decorators.push(Optional());
}
return applyDecorators(...decorators);
};
type BooleanOptions = { optional?: boolean };
export const ValidateBoolean = (options?: BooleanOptions & ApiPropertyOptions) => { export const ValidateBoolean = (options?: BooleanOptions & ApiPropertyOptions) => {
const { optional, ...apiPropertyOptions } = { optional: false, ...options }; const { optional, nullable, ...apiPropertyOptions } = options || {};
const decorators = [ const decorators = [
ApiProperty(apiPropertyOptions), ApiProperty(apiPropertyOptions),
IsBoolean(), IsBoolean(),
@ -209,15 +194,37 @@ export const ValidateBoolean = (options?: BooleanOptions & ApiPropertyOptions) =
} }
return value; return value;
}), }),
optional ? Optional({ nullable }) : IsNotEmpty(),
]; ];
if (optional) {
decorators.push(Optional());
}
return applyDecorators(...decorators); return applyDecorators(...decorators);
}; };
type EnumOptions<T> = {
enum: T;
name: string;
each?: boolean;
optional?: boolean;
nullable?: boolean;
default?: T[keyof T];
description?: string;
};
export const ValidateEnum = <T extends object>({
name,
enum: value,
each,
optional,
nullable,
default: defaultValue,
description,
}: EnumOptions<T>) => {
return applyDecorators(
optional ? Optional({ nullable }) : IsNotEmpty(),
IsEnum(value, { each }),
ApiProperty({ enumName: name, enum: value, isArray: each, default: defaultValue, description }),
);
};
@ValidatorConstraint({ name: 'cronValidator' }) @ValidatorConstraint({ name: 'cronValidator' })
class CronValidator implements ValidatorConstraintInterface { class CronValidator implements ValidatorConstraintInterface {
validate(expression: string): boolean { validate(expression: string): boolean {