forked from Cutlery/immich
		
	refactor(server): upload config (#3252)
This commit is contained in:
		
							parent
							
								
									382341f550
								
							
						
					
					
						commit
						1064128fde
					
				| @ -1,18 +1,21 @@ | ||||
| import { AssetType } from '@app/infra/entities'; | ||||
| import { BadRequestException } from '@nestjs/common'; | ||||
| import { BadRequestException, UnauthorizedException } from '@nestjs/common'; | ||||
| import { | ||||
|   assetEntityStub, | ||||
|   authStub, | ||||
|   IAccessRepositoryMock, | ||||
|   newAccessRepositoryMock, | ||||
|   newAssetRepositoryMock, | ||||
|   newCryptoRepositoryMock, | ||||
|   newStorageRepositoryMock, | ||||
| } from '@test'; | ||||
| import { when } from 'jest-when'; | ||||
| import { Readable } from 'stream'; | ||||
| import { ICryptoRepository } from '../crypto'; | ||||
| import { mimeTypes } from '../domain.constant'; | ||||
| import { IStorageRepository } from '../storage'; | ||||
| import { AssetStats, IAssetRepository } from './asset.repository'; | ||||
| import { AssetService } from './asset.service'; | ||||
| import { AssetService, UploadFieldName } from './asset.service'; | ||||
| import { AssetStatsResponseDto, DownloadResponseDto } from './dto'; | ||||
| import { mapAsset } from './response-dto'; | ||||
| 
 | ||||
| @ -39,10 +42,62 @@ const statResponse: AssetStatsResponseDto = { | ||||
|   total: 33, | ||||
| }; | ||||
| 
 | ||||
| const uploadFile = { | ||||
|   nullAuth: { | ||||
|     authUser: null, | ||||
|     fieldName: UploadFieldName.ASSET_DATA, | ||||
|     file: { | ||||
|       checksum: Buffer.from('checksum', 'utf8'), | ||||
|       originalPath: 'upload/admin/image.jpeg', | ||||
|       originalName: 'image.jpeg', | ||||
|     }, | ||||
|   }, | ||||
|   filename: (fieldName: UploadFieldName, filename: string) => { | ||||
|     return { | ||||
|       authUser: authStub.admin, | ||||
|       fieldName, | ||||
|       file: { | ||||
|         mimeType: 'image/jpeg', | ||||
|         checksum: Buffer.from('checksum', 'utf8'), | ||||
|         originalPath: `upload/admin/${filename}`, | ||||
|         originalName: filename, | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| const uploadTests = [ | ||||
|   { | ||||
|     label: 'asset', | ||||
|     fieldName: UploadFieldName.ASSET_DATA, | ||||
|     filetypes: Object.keys({ ...mimeTypes.image, ...mimeTypes.video }), | ||||
|     invalid: ['.xml', '.html'], | ||||
|   }, | ||||
|   { | ||||
|     label: 'live photo', | ||||
|     fieldName: UploadFieldName.LIVE_PHOTO_DATA, | ||||
|     filetypes: Object.keys(mimeTypes.video), | ||||
|     invalid: ['.xml', '.html', '.jpg', '.jpeg'], | ||||
|   }, | ||||
|   { | ||||
|     label: 'sidecar', | ||||
|     fieldName: UploadFieldName.SIDECAR_DATA, | ||||
|     filetypes: Object.keys(mimeTypes.sidecar), | ||||
|     invalid: ['.xml', '.html', '.jpg', '.jpeg', '.mov', '.mp4'], | ||||
|   }, | ||||
|   { | ||||
|     label: 'profile', | ||||
|     fieldName: UploadFieldName.PROFILE_DATA, | ||||
|     filetypes: Object.keys(mimeTypes.profile), | ||||
|     invalid: ['.xml', '.html', '.cr2', '.arf', '.mov', '.mp4'], | ||||
|   }, | ||||
| ]; | ||||
| 
 | ||||
| describe(AssetService.name, () => { | ||||
|   let sut: AssetService; | ||||
|   let accessMock: IAccessRepositoryMock; | ||||
|   let assetMock: jest.Mocked<IAssetRepository>; | ||||
|   let cryptoMock: jest.Mocked<ICryptoRepository>; | ||||
|   let storageMock: jest.Mocked<IStorageRepository>; | ||||
| 
 | ||||
|   it('should work', () => { | ||||
| @ -52,8 +107,83 @@ describe(AssetService.name, () => { | ||||
|   beforeEach(async () => { | ||||
|     accessMock = newAccessRepositoryMock(); | ||||
|     assetMock = newAssetRepositoryMock(); | ||||
|     cryptoMock = newCryptoRepositoryMock(); | ||||
|     storageMock = newStorageRepositoryMock(); | ||||
|     sut = new AssetService(accessMock, assetMock, storageMock); | ||||
|     sut = new AssetService(accessMock, assetMock, cryptoMock, storageMock); | ||||
|   }); | ||||
| 
 | ||||
|   describe('canUpload', () => { | ||||
|     it('should require an authenticated user', () => { | ||||
|       expect(() => sut.canUploadFile(uploadFile.nullAuth)).toThrowError(UnauthorizedException); | ||||
|     }); | ||||
| 
 | ||||
|     for (const { fieldName, filetypes, invalid } of uploadTests) { | ||||
|       describe(`${fieldName}`, () => { | ||||
|         for (const filetype of filetypes) { | ||||
|           it(`should accept ${filetype}`, () => { | ||||
|             expect(sut.canUploadFile(uploadFile.filename(fieldName, `asset${filetype}`))).toEqual(true); | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         for (const filetype of invalid) { | ||||
|           it(`should reject ${filetype}`, () => { | ||||
|             expect(() => sut.canUploadFile(uploadFile.filename(fieldName, `asset${filetype}`))).toThrowError( | ||||
|               BadRequestException, | ||||
|             ); | ||||
|           }); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   describe('getUploadFilename', () => { | ||||
|     it('should require authentication', () => { | ||||
|       expect(() => sut.getUploadFilename(uploadFile.nullAuth)).toThrowError(UnauthorizedException); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be the original extension for asset upload', () => { | ||||
|       expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.ASSET_DATA, 'image.jpg'))).toEqual( | ||||
|         'random-uuid.jpg', | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be the mov extension for live photo upload', () => { | ||||
|       expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.LIVE_PHOTO_DATA, 'image.mp4'))).toEqual( | ||||
|         'random-uuid.mov', | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be the xmp extension for sidecar upload', () => { | ||||
|       expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.SIDECAR_DATA, 'image.html'))).toEqual( | ||||
|         'random-uuid.xmp', | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be the original extension for profile upload', () => { | ||||
|       expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.PROFILE_DATA, 'image.jpg'))).toEqual( | ||||
|         'random-uuid.jpg', | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('getUploadFolder', () => { | ||||
|     it('should require authentication', () => { | ||||
|       expect(() => sut.getUploadFolder(uploadFile.nullAuth)).toThrowError(UnauthorizedException); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return profile for profile uploads', () => { | ||||
|       expect(sut.getUploadFolder(uploadFile.filename(UploadFieldName.PROFILE_DATA, 'image.jpg'))).toEqual( | ||||
|         'upload/profile/admin_id', | ||||
|       ); | ||||
|       expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/profile/admin_id'); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return upload for everything else', () => { | ||||
|       expect(sut.getUploadFolder(uploadFile.filename(UploadFieldName.ASSET_DATA, 'image.jpg'))).toEqual( | ||||
|         'upload/upload/admin_id', | ||||
|       ); | ||||
|       expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/upload/admin_id'); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('getMapMarkers', () => { | ||||
|  | ||||
| @ -1,12 +1,14 @@ | ||||
| import { AssetEntity } from '@app/infra/entities'; | ||||
| import { BadRequestException, Inject } from '@nestjs/common'; | ||||
| import { BadRequestException, Inject, Logger } from '@nestjs/common'; | ||||
| import { DateTime } from 'luxon'; | ||||
| import { extname } from 'path'; | ||||
| import sanitize from 'sanitize-filename'; | ||||
| import { AccessCore, IAccessRepository, Permission } from '../access'; | ||||
| import { AuthUserDto } from '../auth'; | ||||
| import { ICryptoRepository } from '../crypto'; | ||||
| import { mimeTypes } from '../domain.constant'; | ||||
| import { HumanReadableSize, usePagination } from '../domain.util'; | ||||
| import { ImmichReadStream, IStorageRepository } from '../storage'; | ||||
| import { ImmichReadStream, IStorageRepository, StorageCore, StorageFolder } from '../storage'; | ||||
| import { IAssetRepository } from './asset.repository'; | ||||
| import { AssetIdsDto, DownloadArchiveInfo, DownloadDto, DownloadResponseDto, MemoryLaneDto } from './dto'; | ||||
| import { AssetStatsDto, mapStats } from './dto/asset-statistics.dto'; | ||||
| @ -21,6 +23,12 @@ export enum UploadFieldName { | ||||
|   PROFILE_DATA = 'file', | ||||
| } | ||||
| 
 | ||||
| export interface UploadRequest { | ||||
|   authUser: AuthUserDto | null; | ||||
|   fieldName: UploadFieldName; | ||||
|   file: UploadFile; | ||||
| } | ||||
| 
 | ||||
| export interface UploadFile { | ||||
|   checksum: Buffer; | ||||
|   originalPath: string; | ||||
| @ -28,16 +36,82 @@ export interface UploadFile { | ||||
| } | ||||
| 
 | ||||
| export class AssetService { | ||||
|   private logger = new Logger(AssetService.name); | ||||
|   private access: AccessCore; | ||||
|   private storageCore = new StorageCore(); | ||||
| 
 | ||||
|   constructor( | ||||
|     @Inject(IAccessRepository) accessRepository: IAccessRepository, | ||||
|     @Inject(IAssetRepository) private assetRepository: IAssetRepository, | ||||
|     @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, | ||||
|     @Inject(IStorageRepository) private storageRepository: IStorageRepository, | ||||
|   ) { | ||||
|     this.access = new AccessCore(accessRepository); | ||||
|   } | ||||
| 
 | ||||
|   canUploadFile({ authUser, fieldName, file }: UploadRequest): true { | ||||
|     this.access.requireUploadAccess(authUser); | ||||
| 
 | ||||
|     const filename = file.originalName; | ||||
| 
 | ||||
|     switch (fieldName) { | ||||
|       case UploadFieldName.ASSET_DATA: | ||||
|         if (mimeTypes.isAsset(filename)) { | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|       case UploadFieldName.LIVE_PHOTO_DATA: | ||||
|         if (mimeTypes.isVideo(filename)) { | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|       case UploadFieldName.SIDECAR_DATA: | ||||
|         if (mimeTypes.isSidecar(filename)) { | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|       case UploadFieldName.PROFILE_DATA: | ||||
|         if (mimeTypes.isProfile(filename)) { | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     this.logger.error(`Unsupported file type ${filename}`); | ||||
|     throw new BadRequestException(`Unsupported file type ${filename}`); | ||||
|   } | ||||
| 
 | ||||
|   getUploadFilename({ authUser, fieldName, file }: UploadRequest): string { | ||||
|     this.access.requireUploadAccess(authUser); | ||||
| 
 | ||||
|     const originalExt = extname(file.originalName); | ||||
| 
 | ||||
|     const lookup = { | ||||
|       [UploadFieldName.ASSET_DATA]: originalExt, | ||||
|       [UploadFieldName.LIVE_PHOTO_DATA]: '.mov', | ||||
|       [UploadFieldName.SIDECAR_DATA]: '.xmp', | ||||
|       [UploadFieldName.PROFILE_DATA]: originalExt, | ||||
|     }; | ||||
| 
 | ||||
|     return sanitize(`${this.cryptoRepository.randomUUID()}${lookup[fieldName]}`); | ||||
|   } | ||||
| 
 | ||||
|   getUploadFolder({ authUser, fieldName }: UploadRequest): string { | ||||
|     authUser = this.access.requireUploadAccess(authUser); | ||||
| 
 | ||||
|     let folder = this.storageCore.getFolderLocation(StorageFolder.UPLOAD, authUser.id); | ||||
|     if (fieldName === UploadFieldName.PROFILE_DATA) { | ||||
|       folder = this.storageCore.getFolderLocation(StorageFolder.PROFILE, authUser.id); | ||||
|     } | ||||
| 
 | ||||
|     this.storageRepository.mkdirSync(folder); | ||||
| 
 | ||||
|     return folder; | ||||
|   } | ||||
| 
 | ||||
|   getMapMarkers(authUser: AuthUserDto, options: MapMarkerDto): Promise<MapMarkerResponseDto[]> { | ||||
|     return this.assetRepository.getMapMarkers(authUser.id, options); | ||||
|   } | ||||
|  | ||||
| @ -1,13 +1,6 @@ | ||||
| import { | ||||
|   ICryptoRepository, | ||||
|   IJobRepository, | ||||
|   IStorageRepository, | ||||
|   JobName, | ||||
|   mimeTypes, | ||||
|   UploadFieldName, | ||||
| } from '@app/domain'; | ||||
| import { ICryptoRepository, IJobRepository, IStorageRepository, JobName, mimeTypes } from '@app/domain'; | ||||
| import { AssetEntity, AssetType, ExifEntity } from '@app/infra/entities'; | ||||
| import { BadRequestException, UnauthorizedException } from '@nestjs/common'; | ||||
| import { BadRequestException } from '@nestjs/common'; | ||||
| import { | ||||
|   assetEntityStub, | ||||
|   authStub, | ||||
| @ -102,57 +95,6 @@ const _getAssetCountByTimeBucket = (): AssetCountByTimeBucket[] => { | ||||
|   return [result1, result2]; | ||||
| }; | ||||
| 
 | ||||
| const uploadFile = { | ||||
|   nullAuth: { | ||||
|     authUser: null, | ||||
|     fieldName: UploadFieldName.ASSET_DATA, | ||||
|     file: { | ||||
|       checksum: Buffer.from('checksum', 'utf8'), | ||||
|       originalPath: 'upload/admin/image.jpeg', | ||||
|       originalName: 'image.jpeg', | ||||
|     }, | ||||
|   }, | ||||
|   filename: (fieldName: UploadFieldName, filename: string) => { | ||||
|     return { | ||||
|       authUser: authStub.admin, | ||||
|       fieldName, | ||||
|       file: { | ||||
|         mimeType: 'image/jpeg', | ||||
|         checksum: Buffer.from('checksum', 'utf8'), | ||||
|         originalPath: `upload/admin/${filename}`, | ||||
|         originalName: filename, | ||||
|       }, | ||||
|     }; | ||||
|   }, | ||||
| }; | ||||
| 
 | ||||
| const uploadTests = [ | ||||
|   { | ||||
|     label: 'asset', | ||||
|     fieldName: UploadFieldName.ASSET_DATA, | ||||
|     filetypes: Object.keys({ ...mimeTypes.image, ...mimeTypes.video }), | ||||
|     invalid: ['.xml', '.html'], | ||||
|   }, | ||||
|   { | ||||
|     label: 'live photo', | ||||
|     fieldName: UploadFieldName.LIVE_PHOTO_DATA, | ||||
|     filetypes: Object.keys(mimeTypes.video), | ||||
|     invalid: ['.xml', '.html', '.jpg', '.jpeg'], | ||||
|   }, | ||||
|   { | ||||
|     label: 'sidecar', | ||||
|     fieldName: UploadFieldName.SIDECAR_DATA, | ||||
|     filetypes: Object.keys(mimeTypes.sidecar), | ||||
|     invalid: ['.xml', '.html', '.jpg', '.jpeg', '.mov', '.mp4'], | ||||
|   }, | ||||
|   { | ||||
|     label: 'profile', | ||||
|     fieldName: UploadFieldName.PROFILE_DATA, | ||||
|     filetypes: Object.keys(mimeTypes.profile), | ||||
|     invalid: ['.xml', '.html', '.cr2', '.arf', '.mov', '.mp4'], | ||||
|   }, | ||||
| ]; | ||||
| 
 | ||||
| describe('AssetService', () => { | ||||
|   let sut: AssetService; | ||||
|   let a: Repository<AssetEntity>; // TO BE DELETED AFTER FINISHED REFACTORING
 | ||||
| @ -275,80 +217,6 @@ describe('AssetService', () => { | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('canUpload', () => { | ||||
|     it('should require an authenticated user', () => { | ||||
|       expect(() => sut.canUploadFile(uploadFile.nullAuth)).toThrowError(UnauthorizedException); | ||||
|     }); | ||||
| 
 | ||||
|     for (const { fieldName, filetypes, invalid } of uploadTests) { | ||||
|       describe(`${fieldName}`, () => { | ||||
|         for (const filetype of filetypes) { | ||||
|           it(`should accept ${filetype}`, () => { | ||||
|             expect(sut.canUploadFile(uploadFile.filename(fieldName, `asset${filetype}`))).toEqual(true); | ||||
|           }); | ||||
|         } | ||||
| 
 | ||||
|         for (const filetype of invalid) { | ||||
|           it(`should reject ${filetype}`, () => { | ||||
|             expect(() => sut.canUploadFile(uploadFile.filename(fieldName, `asset${filetype}`))).toThrowError( | ||||
|               BadRequestException, | ||||
|             ); | ||||
|           }); | ||||
|         } | ||||
|       }); | ||||
|     } | ||||
|   }); | ||||
| 
 | ||||
|   describe('getUploadFilename', () => { | ||||
|     it('should require authentication', () => { | ||||
|       expect(() => sut.getUploadFilename(uploadFile.nullAuth)).toThrowError(UnauthorizedException); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be the original extension for asset upload', () => { | ||||
|       expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.ASSET_DATA, 'image.jpg'))).toEqual( | ||||
|         'random-uuid.jpg', | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be the mov extension for live photo upload', () => { | ||||
|       expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.LIVE_PHOTO_DATA, 'image.mp4'))).toEqual( | ||||
|         'random-uuid.mov', | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be the xmp extension for sidecar upload', () => { | ||||
|       expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.SIDECAR_DATA, 'image.html'))).toEqual( | ||||
|         'random-uuid.xmp', | ||||
|       ); | ||||
|     }); | ||||
| 
 | ||||
|     it('should be the original extension for profile upload', () => { | ||||
|       expect(sut.getUploadFilename(uploadFile.filename(UploadFieldName.PROFILE_DATA, 'image.jpg'))).toEqual( | ||||
|         'random-uuid.jpg', | ||||
|       ); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('getUploadFolder', () => { | ||||
|     it('should require authentication', () => { | ||||
|       expect(() => sut.getUploadFolder(uploadFile.nullAuth)).toThrowError(UnauthorizedException); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return profile for profile uploads', () => { | ||||
|       expect(sut.getUploadFolder(uploadFile.filename(UploadFieldName.PROFILE_DATA, 'image.jpg'))).toEqual( | ||||
|         'upload/profile/admin_id', | ||||
|       ); | ||||
|       expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/profile/admin_id'); | ||||
|     }); | ||||
| 
 | ||||
|     it('should return upload for everything else', () => { | ||||
|       expect(sut.getUploadFolder(uploadFile.filename(UploadFieldName.ASSET_DATA, 'image.jpg'))).toEqual( | ||||
|         'upload/upload/admin_id', | ||||
|       ); | ||||
|       expect(storageMock.mkdirSync).toHaveBeenCalledWith('upload/upload/admin_id'); | ||||
|     }); | ||||
|   }); | ||||
| 
 | ||||
|   describe('uploadFile', () => { | ||||
|     it('should handle a file upload', async () => { | ||||
|       const assetEntity = _getAsset_1(); | ||||
|  | ||||
| @ -12,9 +12,6 @@ import { | ||||
|   mapAssetWithoutExif, | ||||
|   mimeTypes, | ||||
|   Permission, | ||||
|   StorageCore, | ||||
|   StorageFolder, | ||||
|   UploadFieldName, | ||||
|   UploadFile, | ||||
| } from '@app/domain'; | ||||
| import { AssetEntity, AssetType } from '@app/infra/entities'; | ||||
| @ -30,10 +27,8 @@ import { InjectRepository } from '@nestjs/typeorm'; | ||||
| import { Response as Res } from 'express'; | ||||
| import { constants } from 'fs'; | ||||
| import fs from 'fs/promises'; | ||||
| import path, { extname } from 'path'; | ||||
| import sanitize from 'sanitize-filename'; | ||||
| import path from 'path'; | ||||
| import { QueryFailedError, Repository } from 'typeorm'; | ||||
| import { UploadRequest } from '../../app.interceptor'; | ||||
| import { IAssetRepository } from './asset-repository'; | ||||
| import { AssetCore } from './asset.core'; | ||||
| import { AssetBulkUploadCheckDto } from './dto/asset-check.dto'; | ||||
| @ -70,7 +65,6 @@ export class AssetService { | ||||
|   readonly logger = new Logger(AssetService.name); | ||||
|   private assetCore: AssetCore; | ||||
|   private access: AccessCore; | ||||
|   private storageCore = new StorageCore(); | ||||
| 
 | ||||
|   constructor( | ||||
|     @Inject(IAccessRepository) accessRepository: IAccessRepository, | ||||
| @ -84,69 +78,6 @@ export class AssetService { | ||||
|     this.access = new AccessCore(accessRepository); | ||||
|   } | ||||
| 
 | ||||
|   canUploadFile({ authUser, fieldName, file }: UploadRequest): true { | ||||
|     this.access.requireUploadAccess(authUser); | ||||
| 
 | ||||
|     const filename = file.originalName; | ||||
| 
 | ||||
|     switch (fieldName) { | ||||
|       case UploadFieldName.ASSET_DATA: | ||||
|         if (mimeTypes.isAsset(filename)) { | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|       case UploadFieldName.LIVE_PHOTO_DATA: | ||||
|         if (mimeTypes.isVideo(filename)) { | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|       case UploadFieldName.SIDECAR_DATA: | ||||
|         if (mimeTypes.isSidecar(filename)) { | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
| 
 | ||||
|       case UploadFieldName.PROFILE_DATA: | ||||
|         if (mimeTypes.isProfile(filename)) { | ||||
|           return true; | ||||
|         } | ||||
|         break; | ||||
|     } | ||||
| 
 | ||||
|     this.logger.error(`Unsupported file type ${filename}`); | ||||
|     throw new BadRequestException(`Unsupported file type ${filename}`); | ||||
|   } | ||||
| 
 | ||||
|   getUploadFilename({ authUser, fieldName, file }: UploadRequest): string { | ||||
|     this.access.requireUploadAccess(authUser); | ||||
| 
 | ||||
|     const originalExt = extname(file.originalName); | ||||
| 
 | ||||
|     const lookup = { | ||||
|       [UploadFieldName.ASSET_DATA]: originalExt, | ||||
|       [UploadFieldName.LIVE_PHOTO_DATA]: '.mov', | ||||
|       [UploadFieldName.SIDECAR_DATA]: '.xmp', | ||||
|       [UploadFieldName.PROFILE_DATA]: originalExt, | ||||
|     }; | ||||
| 
 | ||||
|     return sanitize(`${this.cryptoRepository.randomUUID()}${lookup[fieldName]}`); | ||||
|   } | ||||
| 
 | ||||
|   getUploadFolder({ authUser, fieldName }: UploadRequest): string { | ||||
|     authUser = this.access.requireUploadAccess(authUser); | ||||
| 
 | ||||
|     let folder = this.storageCore.getFolderLocation(StorageFolder.UPLOAD, authUser.id); | ||||
|     if (fieldName === UploadFieldName.PROFILE_DATA) { | ||||
|       folder = this.storageCore.getFolderLocation(StorageFolder.PROFILE, authUser.id); | ||||
|     } | ||||
| 
 | ||||
|     this.storageRepository.mkdirSync(folder); | ||||
| 
 | ||||
|     return folder; | ||||
|   } | ||||
| 
 | ||||
|   public async uploadFile( | ||||
|     authUser: AuthUserDto, | ||||
|     dto: CreateAssetDto, | ||||
|  | ||||
| @ -1,4 +1,4 @@ | ||||
| import { AuthUserDto, UploadFieldName, UploadFile } from '@app/domain'; | ||||
| import { AssetService, UploadFieldName, UploadFile } from '@app/domain'; | ||||
| import { CallHandler, ExecutionContext, Injectable, Logger, NestInterceptor } from '@nestjs/common'; | ||||
| import { PATH_METADATA } from '@nestjs/common/constants'; | ||||
| import { Reflector } from '@nestjs/core'; | ||||
| @ -7,7 +7,6 @@ import { createHash } from 'crypto'; | ||||
| import { NextFunction, RequestHandler } from 'express'; | ||||
| import multer, { diskStorage, StorageEngine } from 'multer'; | ||||
| import { Observable } from 'rxjs'; | ||||
| import { AssetService } from './api-v1/asset/asset.service'; | ||||
| import { AuthRequest } from './app.guard'; | ||||
| 
 | ||||
| export enum Route { | ||||
| @ -43,12 +42,6 @@ const callbackify = async <T>(fn: (...args: any[]) => T, callback: Callback<T>) | ||||
|   } | ||||
| }; | ||||
| 
 | ||||
| export interface UploadRequest { | ||||
|   authUser: AuthUserDto | null; | ||||
|   fieldName: UploadFieldName; | ||||
|   file: UploadFile; | ||||
| } | ||||
| 
 | ||||
| const asRequest = (req: AuthRequest, file: Express.Multer.File) => { | ||||
|   return { | ||||
|     authUser: req.user || null, | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user