diff --git a/server/src/middleware/file-upload.interceptor.ts b/server/src/middleware/file-upload.interceptor.ts index 59c28849e1..6dfd11ee4b 100644 --- a/server/src/middleware/file-upload.interceptor.ts +++ b/server/src/middleware/file-upload.interceptor.ts @@ -12,7 +12,7 @@ import { AuthRequest } from 'src/middleware/auth.guard'; import { LoggingRepository } from 'src/repositories/logging.repository'; import { AssetMediaService } from 'src/services/asset-media.service'; import { ImmichFile, UploadFile, UploadFiles } from 'src/types'; -import { asRequest, mapToUploadFile } from 'src/utils/asset.util'; +import { asUploadRequest, mapToUploadFile } from 'src/utils/asset.util'; export function getFile(files: UploadFiles, property: 'assetData' | 'sidecarData') { const file = files[property]?.[0]; @@ -99,18 +99,21 @@ export class FileUploadInterceptor implements NestInterceptor { } private fileFilter(request: AuthRequest, file: Express.Multer.File, callback: multer.FileFilterCallback) { - return callbackify(() => this.assetService.canUploadFile(asRequest(request, file)), callback); + return callbackify(() => this.assetService.canUploadFile(asUploadRequest(request, file)), callback); } private filename(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) { return callbackify( - () => this.assetService.getUploadFilename(asRequest(request, file)), + () => this.assetService.getUploadFilename(asUploadRequest(request, file)), callback as Callback, ); } private destination(request: AuthRequest, file: Express.Multer.File, callback: DiskStorageCallback) { - return callbackify(() => this.assetService.getUploadFolder(asRequest(request, file)), callback as Callback); + return callbackify( + () => this.assetService.getUploadFolder(asUploadRequest(request, file)), + callback as Callback, + ); } private handleFile(request: AuthRequest, file: Express.Multer.File, callback: Callback>) { diff --git a/server/src/services/asset-media.service.spec.ts b/server/src/services/asset-media.service.spec.ts index 91bddeb6e9..a338c30d78 100644 --- a/server/src/services/asset-media.service.spec.ts +++ b/server/src/services/asset-media.service.spec.ts @@ -25,6 +25,7 @@ const file1 = Buffer.from('d2947b871a706081be194569951b7db246907957', 'hex'); const uploadFile = { nullAuth: { auth: null, + body: {}, fieldName: UploadFieldName.ASSET_DATA, file: { uuid: 'random-uuid', @@ -37,6 +38,7 @@ const uploadFile = { filename: (fieldName: UploadFieldName, filename: string) => { return { auth: authStub.admin, + body: {}, fieldName, file: { uuid: 'random-uuid', @@ -897,7 +899,10 @@ describe(AssetMediaService.name, () => { describe('onUploadError', () => { it('should queue a job to delete the uploaded file', async () => { - const request = { user: authStub.user1 } as AuthRequest; + const request = { + body: {}, + user: authStub.user1, + } as AuthRequest; const file = { fieldname: UploadFieldName.ASSET_DATA, diff --git a/server/src/services/asset-media.service.ts b/server/src/services/asset-media.service.ts index 54bbedea9c..0747bd7b7b 100644 --- a/server/src/services/asset-media.service.ts +++ b/server/src/services/asset-media.service.ts @@ -24,20 +24,14 @@ import { AuthDto } from 'src/dtos/auth.dto'; import { AssetStatus, AssetType, AssetVisibility, CacheControl, JobName, Permission, StorageFolder } from 'src/enum'; import { AuthRequest } from 'src/middleware/auth.guard'; import { BaseService } from 'src/services/base.service'; -import { UploadFile } from 'src/types'; +import { UploadFile, UploadRequest } from 'src/types'; import { requireUploadAccess } from 'src/utils/access'; -import { asRequest, getAssetFiles, onBeforeLink } from 'src/utils/asset.util'; +import { asUploadRequest, getAssetFiles, onBeforeLink } from 'src/utils/asset.util'; import { isAssetChecksumConstraint } from 'src/utils/database'; import { getFilenameExtension, getFileNameWithoutExtension, ImmichFileResponse } from 'src/utils/file'; import { mimeTypes } from 'src/utils/mime-types'; import { fromChecksum } from 'src/utils/request'; -interface UploadRequest { - auth: AuthDto | null; - fieldName: UploadFieldName; - file: UploadFile; -} - export interface AssetMediaRedirectResponse { targetSize: AssetMediaSize | 'original'; } @@ -89,15 +83,15 @@ export class AssetMediaService extends BaseService { throw new BadRequestException(`Unsupported file type ${filename}`); } - getUploadFilename({ auth, fieldName, file }: UploadRequest): string { + getUploadFilename({ auth, fieldName, file, body }: UploadRequest): string { requireUploadAccess(auth); - const originalExtension = extname(file.originalName); + const extension = extname(body.filename || file.originalName); const lookup = { - [UploadFieldName.ASSET_DATA]: originalExtension, + [UploadFieldName.ASSET_DATA]: extension, [UploadFieldName.SIDECAR_DATA]: '.xmp', - [UploadFieldName.PROFILE_DATA]: originalExtension, + [UploadFieldName.PROFILE_DATA]: extension, }; return sanitize(`${file.uuid}${lookup[fieldName]}`); @@ -117,8 +111,8 @@ export class AssetMediaService extends BaseService { } async onUploadError(request: AuthRequest, file: Express.Multer.File) { - const uploadFilename = this.getUploadFilename(asRequest(request, file)); - const uploadFolder = this.getUploadFolder(asRequest(request, file)); + const uploadFilename = this.getUploadFilename(asUploadRequest(request, file)); + const uploadFolder = this.getUploadFolder(asUploadRequest(request, file)); const uploadPath = `${uploadFolder}/${uploadFilename}`; await this.jobRepository.queue({ name: JobName.FileDelete, data: { files: [uploadPath] } }); diff --git a/server/src/types.ts b/server/src/types.ts index b77dd4df6e..2b603aeec5 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -1,5 +1,7 @@ import { SystemConfig } from 'src/config'; import { VECTOR_EXTENSIONS } from 'src/constants'; +import { UploadFieldName } from 'src/dtos/asset-media.dto'; +import { AuthDto } from 'src/dtos/auth.dto'; import { AssetMetadataKey, AssetOrder, @@ -408,6 +410,16 @@ export interface UploadFile { size: number; } +export type UploadRequest = { + auth: AuthDto | null; + fieldName: UploadFieldName; + file: UploadFile; + body: { + filename?: string; + [key: string]: unknown; + }; +}; + export interface UploadFiles { assetData: ImmichFile[]; sidecarData: ImmichFile[]; diff --git a/server/src/utils/asset.util.ts b/server/src/utils/asset.util.ts index 1b9e12c1cd..629b3bf819 100644 --- a/server/src/utils/asset.util.ts +++ b/server/src/utils/asset.util.ts @@ -10,7 +10,7 @@ import { AccessRepository } from 'src/repositories/access.repository'; import { AssetRepository } from 'src/repositories/asset.repository'; import { EventRepository } from 'src/repositories/event.repository'; import { PartnerRepository } from 'src/repositories/partner.repository'; -import { IBulkAsset, ImmichFile, UploadFile } from 'src/types'; +import { IBulkAsset, ImmichFile, UploadFile, UploadRequest } from 'src/types'; import { checkAccess } from 'src/utils/access'; export const getAssetFile = (files: AssetFile[], type: AssetFileType | GeneratedImageType) => { @@ -190,9 +190,10 @@ export function mapToUploadFile(file: ImmichFile): UploadFile { }; } -export const asRequest = (request: AuthRequest, file: Express.Multer.File) => { +export const asUploadRequest = (request: AuthRequest, file: Express.Multer.File): UploadRequest => { return { auth: request.user || null, + body: request.body, fieldName: file.fieldname as UploadFieldName, file: mapToUploadFile(file as ImmichFile), };