fix(server): more asset upload validation and docs (#1720)

* fix(server): more asset upload validation and docs

* remove unused DTO

* changed Object.keys() to Object.values()

* apply patch to openapi generator for web

* revert CreateAssetDto assetType enum

* resolve merge conflict

* Revert "resolve merge conflict"

This reverts commit 0e0080518759a05d029f2f7a761537c58cb586d0.
This commit is contained in:
Michel Heusschen
2023-02-12 06:54:07 +01:00
committed by GitHub
parent 72c947cbaf
commit 05630776a0
15 changed files with 685 additions and 34 deletions

View File

@@ -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]);

View File

@@ -1,8 +0,0 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsNotEmpty } from 'class-validator';
export class AssetFileUploadDto {
@IsNotEmpty()
@ApiProperty({ type: 'string', format: 'binary' })
assetData!: any;
}

View File

@@ -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,6 +11,7 @@ export class CreateAssetDto {
deviceId!: string;
@IsNotEmpty()
@IsEnum(AssetType)
@ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType })
assetType!: AssetType;
@@ -32,6 +33,14 @@ export class CreateAssetDto {
@IsOptional()
duration?: string;
// The properties below are added to correctly generate the API docs
// and client SDKs. Validation should be handled in the controller.
@ApiProperty({ type: 'string', format: 'binary' })
assetData!: any;
@ApiProperty({ type: 'string', format: 'binary' })
livePhotoData?: any;
}
export interface UploadFile {

View File

@@ -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`;
}
}