fix(server): more asset upload validation and docs
This commit is contained in:
@@ -16,6 +16,7 @@ import {
|
||||
UploadedFiles,
|
||||
Patch,
|
||||
StreamableFile,
|
||||
ParseFilePipe,
|
||||
} from '@nestjs/common';
|
||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||
import { AssetService } from './asset.service';
|
||||
@@ -31,7 +32,6 @@ import { CuratedObjectsResponseDto } from './response-dto/curated-objects-respon
|
||||
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto';
|
||||
import { AssetResponseDto, ImmichReadStream } from '@app/domain';
|
||||
import { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto';
|
||||
import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
|
||||
import { CreateAssetDto, mapToUploadFile } from './dto/create-asset.dto';
|
||||
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
|
||||
import { DeleteAssetResponseDto } from './response-dto/delete-asset-response.dto';
|
||||
@@ -55,6 +55,7 @@ import { SharedLinkResponseDto } from '@app/domain';
|
||||
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
|
||||
import { AssetSearchDto } from './dto/asset-search.dto';
|
||||
import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config';
|
||||
import FileNotEmptyValidator from '../validation/file-not-empty-validator';
|
||||
|
||||
function asStreamableFile({ stream, type, length }: ImmichReadStream) {
|
||||
return new StreamableFile(stream, { type, length });
|
||||
@@ -80,12 +81,13 @@ export class AssetController {
|
||||
@ApiConsumes('multipart/form-data')
|
||||
@ApiBody({
|
||||
description: 'Asset Upload Information',
|
||||
type: AssetFileUploadDto,
|
||||
type: CreateAssetDto,
|
||||
})
|
||||
async uploadFile(
|
||||
@GetAuthUser() authUser: AuthUserDto,
|
||||
@UploadedFiles() files: { assetData: ImmichFile[]; livePhotoData?: ImmichFile[] },
|
||||
@Body(ValidationPipe) dto: CreateAssetDto,
|
||||
@UploadedFiles(new ParseFilePipe({ validators: [new FileNotEmptyValidator(['assetData'])] }))
|
||||
files: { assetData: ImmichFile[]; livePhotoData?: ImmichFile[] },
|
||||
@Body(new ValidationPipe()) dto: CreateAssetDto,
|
||||
@Response({ passthrough: true }) res: Res,
|
||||
): Promise<AssetFileUploadResponseDto> {
|
||||
const file = mapToUploadFile(files.assetData[0]);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { AssetType } from '@app/infra';
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsBoolean, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { IsBoolean, IsEnum, IsNotEmpty, IsOptional } from 'class-validator';
|
||||
import { ImmichFile } from '../../../config/asset-upload.config';
|
||||
|
||||
export class CreateAssetDto {
|
||||
@@ -11,7 +11,8 @@ export class CreateAssetDto {
|
||||
deviceId!: string;
|
||||
|
||||
@IsNotEmpty()
|
||||
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
|
||||
@IsEnum(AssetType)
|
||||
@ApiProperty({ enum: Object.keys(AssetType) })
|
||||
assetType!: AssetType;
|
||||
|
||||
@IsNotEmpty()
|
||||
@@ -32,6 +33,12 @@ export class CreateAssetDto {
|
||||
|
||||
@IsOptional()
|
||||
duration?: string;
|
||||
|
||||
@ApiProperty({ type: 'string', format: 'binary' })
|
||||
assetData!: any;
|
||||
|
||||
@ApiProperty({ type: 'string', format: 'binary' })
|
||||
livePhotoData?: any;
|
||||
}
|
||||
|
||||
export interface UploadFile {
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import { FileValidator, Injectable } from '@nestjs/common';
|
||||
|
||||
@Injectable()
|
||||
export default class FileNotEmptyValidator extends FileValidator {
|
||||
requiredFields: string[];
|
||||
|
||||
constructor(requiredFields: string[]) {
|
||||
super({});
|
||||
this.requiredFields = requiredFields;
|
||||
}
|
||||
|
||||
isValid(files?: any): boolean {
|
||||
if (!files) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return this.requiredFields.every((field) => {
|
||||
return files[field];
|
||||
});
|
||||
}
|
||||
|
||||
buildErrorMessage(): string {
|
||||
return `Field(s) ${this.requiredFields.join(', ')} should not be empty`;
|
||||
}
|
||||
}
|
||||
@@ -1077,7 +1077,7 @@
|
||||
"content": {
|
||||
"multipart/form-data": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/AssetFileUploadDto"
|
||||
"$ref": "#/components/schemas/CreateAssetDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3758,16 +3758,60 @@
|
||||
"profileImagePath"
|
||||
]
|
||||
},
|
||||
"AssetFileUploadDto": {
|
||||
"CreateAssetDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"assetType": {
|
||||
"enum": [
|
||||
"IMAGE",
|
||||
"VIDEO",
|
||||
"AUDIO",
|
||||
"OTHER"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"assetData": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
},
|
||||
"livePhotoData": {
|
||||
"type": "string",
|
||||
"format": "binary"
|
||||
},
|
||||
"deviceAssetId": {
|
||||
"type": "string"
|
||||
},
|
||||
"deviceId": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"modifiedAt": {
|
||||
"type": "string"
|
||||
},
|
||||
"isFavorite": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isVisible": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"fileExtension": {
|
||||
"type": "string"
|
||||
},
|
||||
"duration": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"assetData"
|
||||
"assetType",
|
||||
"assetData",
|
||||
"deviceAssetId",
|
||||
"deviceId",
|
||||
"createdAt",
|
||||
"modifiedAt",
|
||||
"isFavorite",
|
||||
"fileExtension"
|
||||
]
|
||||
},
|
||||
"AssetFileUploadResponseDto": {
|
||||
|
||||
Reference in New Issue
Block a user