mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feature(server): compute sha1 during upload (#1424)
* feature(server): compute sha1 during upload * fix: clean up stream on error
This commit is contained in:
		| @@ -19,7 +19,7 @@ import { | ||||
| import { Authenticated } from '../../decorators/authenticated.decorator'; | ||||
| import { AssetService } from './asset.service'; | ||||
| import { FileFieldsInterceptor } from '@nestjs/platform-express'; | ||||
| import { assetUploadOption } from '../../config/asset-upload.config'; | ||||
| import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config'; | ||||
| import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator'; | ||||
| import { ServeFileDto } from './dto/serve-file.dto'; | ||||
| import { Response as Res } from 'express'; | ||||
| @@ -80,7 +80,7 @@ export class AssetController { | ||||
|   }) | ||||
|   async uploadFile( | ||||
|     @GetAuthUser() authUser: AuthUserDto, | ||||
|     @UploadedFiles() files: { assetData: Express.Multer.File[]; livePhotoData?: Express.Multer.File[] }, | ||||
|     @UploadedFiles() files: { assetData: ImmichFile[]; livePhotoData?: ImmichFile[] }, | ||||
|     @Body(ValidationPipe) createAssetDto: CreateAssetDto, | ||||
|     @Response({ passthrough: true }) res: Res, | ||||
|   ): Promise<AssetFileUploadResponseDto> { | ||||
|   | ||||
| @@ -55,6 +55,7 @@ import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto'; | ||||
| import { mapSharedLink, SharedLinkResponseDto } from '@app/domain'; | ||||
| import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto'; | ||||
| import { AssetSearchDto } from './dto/asset-search.dto'; | ||||
| import { ImmichFile } from '../../config/asset-upload.config'; | ||||
|  | ||||
| const fileInfo = promisify(stat); | ||||
|  | ||||
| @@ -82,16 +83,16 @@ export class AssetService { | ||||
|     authUser: AuthUserDto, | ||||
|     createAssetDto: CreateAssetDto, | ||||
|     res: Res, | ||||
|     originalAssetData: Express.Multer.File, | ||||
|     livePhotoAssetData?: Express.Multer.File, | ||||
|     originalAssetData: ImmichFile, | ||||
|     livePhotoAssetData?: ImmichFile, | ||||
|   ) { | ||||
|     const checksum = await this.calculateChecksum(originalAssetData.path); | ||||
|     const checksum = originalAssetData.checksum; | ||||
|     const isLivePhoto = livePhotoAssetData !== undefined; | ||||
|     let livePhotoAssetEntity: AssetEntity | undefined; | ||||
|  | ||||
|     try { | ||||
|       if (isLivePhoto) { | ||||
|         const livePhotoChecksum = await this.calculateChecksum(livePhotoAssetData.path); | ||||
|         const livePhotoChecksum = livePhotoAssetData.checksum; | ||||
|         livePhotoAssetEntity = await this.createUserAsset( | ||||
|           authUser, | ||||
|           createAssetDto, | ||||
|   | ||||
| @@ -1,10 +1,10 @@ | ||||
| import { APP_UPLOAD_LOCATION } from '@app/common/constants'; | ||||
| import { BadRequestException, Logger, UnauthorizedException } from '@nestjs/common'; | ||||
| import { MulterOptions } from '@nestjs/platform-express/multer/interfaces/multer-options.interface'; | ||||
| import { randomUUID } from 'crypto'; | ||||
| import { createHash, randomUUID } from 'crypto'; | ||||
| import { Request } from 'express'; | ||||
| import { existsSync, mkdirSync } from 'fs'; | ||||
| import { diskStorage } from 'multer'; | ||||
| import { diskStorage, StorageEngine } from 'multer'; | ||||
| import { extname, join } from 'path'; | ||||
| import sanitize from 'sanitize-filename'; | ||||
| import { AuthUserDto } from '../decorators/auth-user.decorator'; | ||||
| @@ -12,14 +12,40 @@ import { patchFormData } from '../utils/path-form-data.util'; | ||||
|  | ||||
| const logger = new Logger('AssetUploadConfig'); | ||||
|  | ||||
| export interface ImmichFile extends Express.Multer.File { | ||||
|   /** sha1 hash of file */ | ||||
|   checksum: Buffer; | ||||
| } | ||||
|  | ||||
| export const assetUploadOption: MulterOptions = { | ||||
|   fileFilter, | ||||
|   storage: diskStorage({ | ||||
|     destination, | ||||
|     filename, | ||||
|   }), | ||||
|   storage: customStorage(), | ||||
| }; | ||||
|  | ||||
| export function customStorage(): StorageEngine { | ||||
|   const storage = diskStorage({ destination, filename }); | ||||
|  | ||||
|   return { | ||||
|     _handleFile(req, file, callback) { | ||||
|       const hash = createHash('sha1'); | ||||
|       file.stream.on('data', (chunk) => hash.update(chunk)); | ||||
|  | ||||
|       storage._handleFile(req, file, (error, response) => { | ||||
|         if (error) { | ||||
|           hash.destroy(); | ||||
|           callback(error); | ||||
|         } else { | ||||
|           callback(null, { ...response, checksum: hash.digest() } as ImmichFile); | ||||
|         } | ||||
|       }); | ||||
|     }, | ||||
|  | ||||
|     _removeFile(req, file, callback) { | ||||
|       storage._removeFile(req, file, callback); | ||||
|     }, | ||||
|   }; | ||||
| } | ||||
|  | ||||
| export const multerUtils = { fileFilter, filename, destination }; | ||||
|  | ||||
| function fileFilter(req: Request, file: any, cb: any) { | ||||
|   | ||||
		Reference in New Issue
	
	Block a user