diff --git a/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart b/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart index 8f90f2d77..0d2e72d60 100644 --- a/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart +++ b/mobile/lib/modules/asset_viewer/services/image_viewer.service.dart @@ -26,14 +26,10 @@ class ImageViewerService { if (asset.type == AssetTypeEnum.IMAGE && asset.livePhotoVideoId != null) { var imageResponse = await _apiService.assetApi.downloadFileWithHttpInfo( asset.id, - isThumb: false, - isWeb: false, ); var motionReponse = await _apiService.assetApi.downloadFileWithHttpInfo( asset.livePhotoVideoId!, - isThumb: false, - isWeb: false, ); final AssetEntity? entity; @@ -54,8 +50,6 @@ class ImageViewerService { } else { var res = await _apiService.assetApi.downloadFileWithHttpInfo( asset.id, - isThumb: false, - isWeb: false, ); final AssetEntity? entity; diff --git a/mobile/lib/shared/services/share.service.dart b/mobile/lib/shared/services/share.service.dart index ebf1d9f0f..84d9674ad 100644 --- a/mobile/lib/shared/services/share.service.dart +++ b/mobile/lib/shared/services/share.service.dart @@ -29,8 +29,6 @@ class ShareService { final tempFile = await File('${tempDir.path}/$fileName').create(); final res = await _apiService.assetApi.downloadFileWithHttpInfo( asset.remote!.id, - isThumb: false, - isWeb: false, ); tempFile.writeAsBytesSync(res.bodyBytes); return XFile(tempFile.path); diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 53b96215f..341b88421 100644 Binary files a/mobile/openapi/README.md and b/mobile/openapi/README.md differ diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index 4f21fe41d..4695c961a 100644 Binary files a/mobile/openapi/doc/AssetApi.md and b/mobile/openapi/doc/AssetApi.md differ diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index e6fa7b0d5..861c9dc15 100644 Binary files a/mobile/openapi/lib/api/asset_api.dart and b/mobile/openapi/lib/api/asset_api.dart differ diff --git a/mobile/openapi/lib/model/album_response_dto.dart b/mobile/openapi/lib/model/album_response_dto.dart index 7858e857d..282344a16 100644 Binary files a/mobile/openapi/lib/model/album_response_dto.dart and b/mobile/openapi/lib/model/album_response_dto.dart differ diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index 08c7e6cc3..2f516e2bd 100644 Binary files a/mobile/openapi/lib/model/asset_response_dto.dart and b/mobile/openapi/lib/model/asset_response_dto.dart differ diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index 086bbca01..3bf405b8f 100644 Binary files a/mobile/openapi/test/asset_api_test.dart and b/mobile/openapi/test/asset_api_test.dart differ diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index ae924dfa3..d0cf3af6a 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -15,6 +15,7 @@ import { Put, UploadedFiles, Patch, + StreamableFile, } from '@nestjs/common'; import { Authenticated } from '../../decorators/authenticated.decorator'; import { AssetService } from './asset.service'; @@ -28,7 +29,7 @@ import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto'; import { ApiBearerAuth, ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger'; import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; import { CuratedLocationsResponseDto } from './response-dto/curated-locations-response.dto'; -import { AssetResponseDto } from '@app/domain'; +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'; @@ -55,6 +56,10 @@ 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'; +function asStreamableFile({ stream, type, length }: ImmichReadStream) { + return new StreamableFile(stream, { type, length }); +} + @ApiBearerAuth() @ApiTags('Asset') @Controller('asset') @@ -103,12 +108,9 @@ export class AssetController { async downloadFile( @GetAuthUser() authUser: AuthUserDto, @Response({ passthrough: true }) res: Res, - @Query(new ValidationPipe({ transform: true })) query: ServeFileDto, @Param('assetId') assetId: string, ): Promise { - this.assetService.checkDownloadAccess(authUser); - await this.assetService.checkAssetsAccess(authUser, [assetId]); - return this.assetService.downloadFile(query, assetId, res); + return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile); } @Authenticated({ isShared: true }) diff --git a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts index c215e22fe..08c4ed1ac 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts @@ -9,12 +9,13 @@ import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-use import { DownloadService } from '../../modules/download/download.service'; import { AlbumRepository, IAlbumRepository } from '../album/album-repository'; import { StorageService } from '@app/storage'; -import { ICryptoRepository, IJobRepository, ISharedLinkRepository, JobName } from '@app/domain'; +import { ICryptoRepository, IJobRepository, ISharedLinkRepository, IStorageRepository, JobName } from '@app/domain'; import { authStub, newCryptoRepositoryMock, newJobRepositoryMock, newSharedLinkRepositoryMock, + newStorageRepositoryMock, sharedLinkResponseStub, sharedLinkStub, } from '@app/domain/../test'; @@ -110,6 +111,7 @@ describe('AssetService', () => { let sharedLinkRepositoryMock: jest.Mocked; let cryptoMock: jest.Mocked; let jobMock: jest.Mocked; + let storageMock: jest.Mocked; beforeEach(() => { assetRepositoryMock = { @@ -154,6 +156,7 @@ describe('AssetService', () => { sharedLinkRepositoryMock = newSharedLinkRepositoryMock(); jobMock = newJobRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); + storageMock = newStorageRepositoryMock(); sut = new AssetService( assetRepositoryMock, @@ -164,6 +167,7 @@ describe('AssetService', () => { sharedLinkRepositoryMock, jobMock, cryptoMock, + storageMock, ); }); @@ -413,4 +417,15 @@ describe('AssetService', () => { expect(() => sut.checkDownloadAccess(authStub.readonlySharedLink)).toThrow(ForbiddenException); }); }); + + describe('downloadFile', () => { + it('should download a single file', async () => { + assetRepositoryMock.countByIdAndUser.mockResolvedValue(1); + assetRepositoryMock.get.mockResolvedValue(_getAsset_1()); + + await sut.downloadFile(authStub.admin, 'id_1'); + + expect(storageMock.createReadStream).toHaveBeenCalledWith('fake_path/asset_1.jpeg', 'image/jpeg'); + }); + }); }); diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index e9ee8abbb..35c14e703 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -10,7 +10,6 @@ import { StreamableFile, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import { createHash } from 'node:crypto'; import { QueryFailedError, Repository } from 'typeorm'; import { AuthUserDto } from '../../decorators/auth-user.decorator'; import { AssetEntity, AssetType, SharedLinkType } from '@app/infra'; @@ -23,7 +22,14 @@ import { SearchAssetDto } from './dto/search-asset.dto'; import fs from 'fs/promises'; import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto'; import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto'; -import { AssetResponseDto, JobName, mapAsset, mapAssetWithoutExif } from '@app/domain'; +import { + AssetResponseDto, + ImmichReadStream, + IStorageRepository, + JobName, + mapAsset, + mapAssetWithoutExif, +} from '@app/domain'; import { CreateAssetDto, UploadFile } from './dto/create-asset.dto'; import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto'; import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto'; @@ -73,6 +79,7 @@ export class AssetService { @Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository, + @Inject(IStorageRepository) private storage: IStorageRepository, ) { this.assetCore = new AssetCore(_assetRepository, jobRepository, storageService); this.shareCore = new ShareCore(sharedLinkRepository, cryptoRepository); @@ -189,62 +196,21 @@ export class AssetService { return this.downloadService.downloadArchive(`immich-${now}`, assetToDownload); } - public async downloadFile(query: ServeFileDto, assetId: string, res: Res) { + public async downloadFile(authUser: AuthUserDto, assetId: string): Promise { + this.checkDownloadAccess(authUser); + await this.checkAssetsAccess(authUser, [assetId]); + try { - let fileReadStream = null; - const asset = await this._assetRepository.getById(assetId); - - // Download Video - if (asset.type === AssetType.VIDEO) { - const { size } = await fileInfo(asset.originalPath); - - res.set({ - 'Content-Type': asset.mimeType, - 'Content-Length': size, - }); - - await fs.access(asset.originalPath, constants.R_OK | constants.W_OK); - fileReadStream = createReadStream(asset.originalPath); - } else { - // Download Image - if (!query.isThumb) { - /** - * Download Image Original File - */ - const { size } = await fileInfo(asset.originalPath); - - res.set({ - 'Content-Type': asset.mimeType, - 'Content-Length': size, - }); - - await fs.access(asset.originalPath, constants.R_OK | constants.W_OK); - fileReadStream = createReadStream(asset.originalPath); - } else { - /** - * Download Image Resize File - */ - if (!asset.resizePath) { - throw new NotFoundException('resizePath not set'); - } - - const { size } = await fileInfo(asset.resizePath); - - res.set({ - 'Content-Type': 'image/jpeg', - 'Content-Length': size, - }); - - await fs.access(asset.resizePath, constants.R_OK | constants.W_OK); - fileReadStream = createReadStream(asset.resizePath); - } + const asset = await this._assetRepository.get(assetId); + if (asset && asset.originalPath && asset.mimeType) { + return this.storage.createReadStream(asset.originalPath, asset.mimeType); } - - return new StreamableFile(fileReadStream); } catch (e) { Logger.error(`Error download asset ${e}`, 'downloadFile'); throw new InternalServerErrorException(`Failed to download asset ${e}`, 'DownloadFile'); } + + throw new NotFoundException(); } public async getAssetThumbnail( @@ -255,8 +221,7 @@ export class AssetService { ) { let fileReadStream: ReadStream; - const asset = await this.assetRepository.findOne({ where: { id: assetId } }); - + const asset = await this._assetRepository.get(assetId); if (!asset) { throw new NotFoundException('Asset not found'); } @@ -584,18 +549,6 @@ export class AssetService { return this._assetRepository.getAssetByChecksum(userId, checksum); } - calculateChecksum(filePath: string): Promise { - const fileReadStream = createReadStream(filePath); - const sha1Hash = createHash('sha1'); - const deferred = new Promise((resolve, reject) => { - sha1Hash.once('error', (err) => reject(err)); - sha1Hash.once('finish', () => resolve(sha1Hash.read())); - }); - - fileReadStream.pipe(sha1Hash); - return deferred; - } - getAssetCountByUserId(authUser: AuthUserDto): Promise { return this._assetRepository.getAssetCountByUserId(authUser.id); } diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index 31b73bfeb..c31e7ee28 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -1109,24 +1109,6 @@ "operationId": "downloadFile", "description": "", "parameters": [ - { - "name": "isThumb", - "required": false, - "in": "query", - "schema": { - "title": "Is serve thumbnail (resize) file", - "type": "boolean" - } - }, - { - "name": "isWeb", - "required": false, - "in": "query", - "schema": { - "title": "Is request made from web", - "type": "boolean" - } - }, { "name": "assetId", "required": true, diff --git a/server/libs/domain/src/auth/auth.core.ts b/server/libs/domain/src/auth/auth.core.ts index 9b4d54ac5..d330baad7 100644 --- a/server/libs/domain/src/auth/auth.core.ts +++ b/server/libs/domain/src/auth/auth.core.ts @@ -6,11 +6,6 @@ import { ICryptoRepository } from '../crypto/crypto.repository'; import { LoginResponseDto, mapLoginResponse } from './response-dto'; import { IUserTokenRepository, UserTokenCore } from '../user-token'; -export type JwtValidationResult = { - status: boolean; - userId: string | null; -}; - export class AuthCore { private userTokenCore: UserTokenCore; constructor( diff --git a/server/libs/domain/src/auth/dto/index.ts b/server/libs/domain/src/auth/dto/index.ts index 87c5cc370..323d12f8f 100644 --- a/server/libs/domain/src/auth/dto/index.ts +++ b/server/libs/domain/src/auth/dto/index.ts @@ -1,5 +1,4 @@ export * from './auth-user.dto'; export * from './change-password.dto'; -export * from './jwt-payload.dto'; export * from './login-credential.dto'; export * from './sign-up.dto'; diff --git a/server/libs/domain/src/auth/dto/jwt-payload.dto.ts b/server/libs/domain/src/auth/dto/jwt-payload.dto.ts deleted file mode 100644 index 4f3b7993f..000000000 --- a/server/libs/domain/src/auth/dto/jwt-payload.dto.ts +++ /dev/null @@ -1,4 +0,0 @@ -export class JwtPayloadDto { - userId!: string; - email!: string; -} diff --git a/server/libs/domain/src/index.ts b/server/libs/domain/src/index.ts index 3113aa1dd..99456733e 100644 --- a/server/libs/domain/src/index.ts +++ b/server/libs/domain/src/index.ts @@ -8,6 +8,7 @@ export * from './domain.module'; export * from './job'; export * from './oauth'; export * from './share'; +export * from './storage'; export * from './system-config'; export * from './tag'; export * from './user'; diff --git a/server/libs/domain/src/storage/index.ts b/server/libs/domain/src/storage/index.ts new file mode 100644 index 000000000..b6109ead5 --- /dev/null +++ b/server/libs/domain/src/storage/index.ts @@ -0,0 +1 @@ +export * from './storage.repository'; diff --git a/server/libs/domain/src/storage/storage.repository.ts b/server/libs/domain/src/storage/storage.repository.ts new file mode 100644 index 000000000..53d452f31 --- /dev/null +++ b/server/libs/domain/src/storage/storage.repository.ts @@ -0,0 +1,13 @@ +import { ReadStream } from 'fs'; + +export interface ImmichReadStream { + stream: ReadStream; + type: string; + length: number; +} + +export const IStorageRepository = 'IStorageRepository'; + +export interface IStorageRepository { + createReadStream(filepath: string, mimeType: string): Promise; +} diff --git a/server/libs/domain/test/index.ts b/server/libs/domain/test/index.ts index dfec3ffa4..94c4f2750 100644 --- a/server/libs/domain/test/index.ts +++ b/server/libs/domain/test/index.ts @@ -4,6 +4,7 @@ export * from './device-info.repository.mock'; export * from './fixtures'; export * from './job.repository.mock'; export * from './shared-link.repository.mock'; +export * from './storage.repository.mock'; export * from './system-config.repository.mock'; export * from './user-token.repository.mock'; export * from './user.repository.mock'; diff --git a/server/libs/domain/test/storage.repository.mock.ts b/server/libs/domain/test/storage.repository.mock.ts new file mode 100644 index 000000000..6cad33869 --- /dev/null +++ b/server/libs/domain/test/storage.repository.mock.ts @@ -0,0 +1,7 @@ +import { IStorageRepository } from '../src'; + +export const newStorageRepositoryMock = (): jest.Mocked => { + return { + createReadStream: jest.fn(), + }; +}; diff --git a/server/libs/infra/src/infra.module.ts b/server/libs/infra/src/infra.module.ts index df16c1dfd..b71373b1b 100644 --- a/server/libs/infra/src/infra.module.ts +++ b/server/libs/infra/src/infra.module.ts @@ -4,6 +4,7 @@ import { IJobRepository, IKeyRepository, ISharedLinkRepository, + IStorageRepository, ISystemConfigRepository, IUserRepository, QueueName, @@ -29,6 +30,7 @@ import { UserTokenEntity, } from './db'; import { JobRepository } from './job'; +import { FilesystemProvider } from './storage'; const providers: Provider[] = [ { provide: ICryptoRepository, useClass: CryptoRepository }, @@ -36,6 +38,7 @@ const providers: Provider[] = [ { provide: IKeyRepository, useClass: APIKeyRepository }, { provide: IJobRepository, useClass: JobRepository }, { provide: ISharedLinkRepository, useClass: SharedLinkRepository }, + { provide: IStorageRepository, useClass: FilesystemProvider }, { provide: ISystemConfigRepository, useClass: SystemConfigRepository }, { provide: IUserRepository, useClass: UserRepository }, { provide: IUserTokenRepository, useClass: UserTokenRepository }, diff --git a/server/libs/infra/src/storage/filesystem.provider.ts b/server/libs/infra/src/storage/filesystem.provider.ts new file mode 100644 index 000000000..ca2d847f1 --- /dev/null +++ b/server/libs/infra/src/storage/filesystem.provider.ts @@ -0,0 +1,18 @@ +import { ImmichReadStream, IStorageRepository } from '@app/domain'; +import { constants, createReadStream, stat } from 'fs'; +import fs from 'fs/promises'; +import { promisify } from 'util'; + +const fileInfo = promisify(stat); + +export class FilesystemProvider implements IStorageRepository { + async createReadStream(filepath: string, mimeType: string): Promise { + const { size } = await fileInfo(filepath); + await fs.access(filepath, constants.R_OK | constants.W_OK); + return { + stream: createReadStream(filepath), + length: size, + type: mimeType, + }; + } +} diff --git a/server/libs/infra/src/storage/index.ts b/server/libs/infra/src/storage/index.ts new file mode 100644 index 000000000..ae8187f1f --- /dev/null +++ b/server/libs/infra/src/storage/index.ts @@ -0,0 +1 @@ +export * from './filesystem.provider'; diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index 8eb39ea7c..6d0ac7036 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.43.0 + * The version of the OpenAPI document: 1.43.1 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). @@ -3729,12 +3729,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration /** * * @param {string} assetId - * @param {boolean} [isThumb] - * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - downloadFile: async (assetId: string, isThumb?: boolean, isWeb?: boolean, options: AxiosRequestConfig = {}): Promise => { + downloadFile: async (assetId: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'assetId' is not null or undefined assertParamExists('downloadFile', 'assetId', assetId) const localVarPath = `/asset/download/{assetId}` @@ -3754,14 +3752,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration // http bearer authentication required await setBearerAuthToObject(localVarHeaderParameter, configuration) - if (isThumb !== undefined) { - localVarQueryParameter['isThumb'] = isThumb; - } - - if (isWeb !== undefined) { - localVarQueryParameter['isWeb'] = isWeb; - } - setSearchParams(localVarUrlObj, localVarQueryParameter); @@ -4489,13 +4479,11 @@ export const AssetApiFp = function(configuration?: Configuration) { /** * * @param {string} assetId - * @param {boolean} [isThumb] - * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async downloadFile(assetId: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(assetId, isThumb, isWeb, options); + async downloadFile(assetId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(assetId, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -4719,13 +4707,11 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath /** * * @param {string} assetId - * @param {boolean} [isThumb] - * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - downloadFile(assetId: string, isThumb?: boolean, isWeb?: boolean, options?: any): AxiosPromise { - return localVarFp.downloadFile(assetId, isThumb, isWeb, options).then((request) => request(axios, basePath)); + downloadFile(assetId: string, options?: any): AxiosPromise { + return localVarFp.downloadFile(assetId, options).then((request) => request(axios, basePath)); }, /** * @@ -4939,14 +4925,12 @@ export class AssetApi extends BaseAPI { /** * * @param {string} assetId - * @param {boolean} [isThumb] - * @param {boolean} [isWeb] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AssetApi */ - public downloadFile(assetId: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).downloadFile(assetId, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath)); + public downloadFile(assetId: string, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).downloadFile(assetId, options).then((request) => request(this.axios, this.basePath)); } /** diff --git a/web/src/api/open-api/base.ts b/web/src/api/open-api/base.ts index 58f76152d..f022bd6e3 100644 --- a/web/src/api/open-api/base.ts +++ b/web/src/api/open-api/base.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.43.0 + * The version of the OpenAPI document: 1.43.1 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/common.ts b/web/src/api/open-api/common.ts index 2df54adf6..d41e01f32 100644 --- a/web/src/api/open-api/common.ts +++ b/web/src/api/open-api/common.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.43.0 + * The version of the OpenAPI document: 1.43.1 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/configuration.ts b/web/src/api/open-api/configuration.ts index bb49e3c84..b12628461 100644 --- a/web/src/api/open-api/configuration.ts +++ b/web/src/api/open-api/configuration.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.43.0 + * The version of the OpenAPI document: 1.43.1 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/api/open-api/index.ts b/web/src/api/open-api/index.ts index 9b6a04331..052d483f1 100644 --- a/web/src/api/open-api/index.ts +++ b/web/src/api/open-api/index.ts @@ -4,7 +4,7 @@ * Immich * Immich API * - * The version of the OpenAPI document: 1.43.0 + * The version of the OpenAPI document: 1.43.1 * * * NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech). diff --git a/web/src/lib/components/asset-viewer/asset-viewer.svelte b/web/src/lib/components/asset-viewer/asset-viewer.svelte index 36819c747..219935943 100644 --- a/web/src/lib/components/asset-viewer/asset-viewer.svelte +++ b/web/src/lib/components/asset-viewer/asset-viewer.svelte @@ -136,10 +136,8 @@ $downloadAssets[imageFileName] = 0; - const { data, status } = await api.assetApi.downloadFile(assetId, false, false, { - params: { - key - }, + const { data, status } = await api.assetApi.downloadFile(assetId, { + params: { key }, responseType: 'blob', onDownloadProgress: (progressEvent) => { if (progressEvent.lengthComputable) {