mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	refactor(server): download file (#1512)
* refactor(server): download file * chore: generate open-api and remove unused refs * chore(server): tests * chore: remove unused code
This commit is contained in:
		@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/README.md
									
									
									
										generated
									
									
									
								
							@@ -3,7 +3,7 @@ Immich API
 | 
			
		||||
 | 
			
		||||
This Dart package is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
 | 
			
		||||
 | 
			
		||||
- API version: 1.43.0
 | 
			
		||||
- API version: 1.43.1
 | 
			
		||||
- Build package: org.openapitools.codegen.languages.DartClientCodegen
 | 
			
		||||
 | 
			
		||||
## Requirements
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										8
									
								
								mobile/openapi/doc/AssetApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										8
									
								
								mobile/openapi/doc/AssetApi.md
									
									
									
										generated
									
									
									
								
							@@ -230,7 +230,7 @@ Name | Type | Description  | Notes
 | 
			
		||||
[[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md)
 | 
			
		||||
 | 
			
		||||
# **downloadFile**
 | 
			
		||||
> Object downloadFile(assetId, isThumb, isWeb)
 | 
			
		||||
> Object downloadFile(assetId)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -248,11 +248,9 @@ import 'package:openapi/api.dart';
 | 
			
		||||
 | 
			
		||||
final api_instance = AssetApi();
 | 
			
		||||
final assetId = assetId_example; // String | 
 | 
			
		||||
final isThumb = true; // bool | 
 | 
			
		||||
final isWeb = true; // bool | 
 | 
			
		||||
 | 
			
		||||
try {
 | 
			
		||||
    final result = api_instance.downloadFile(assetId, isThumb, isWeb);
 | 
			
		||||
    final result = api_instance.downloadFile(assetId);
 | 
			
		||||
    print(result);
 | 
			
		||||
} catch (e) {
 | 
			
		||||
    print('Exception when calling AssetApi->downloadFile: $e\n');
 | 
			
		||||
@@ -264,8 +262,6 @@ try {
 | 
			
		||||
Name | Type | Description  | Notes
 | 
			
		||||
------------- | ------------- | ------------- | -------------
 | 
			
		||||
 **assetId** | **String**|  | 
 | 
			
		||||
 **isThumb** | **bool**|  | [optional] 
 | 
			
		||||
 **isWeb** | **bool**|  | [optional] 
 | 
			
		||||
 | 
			
		||||
### Return type
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										21
									
								
								mobile/openapi/lib/api/asset_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										21
									
								
								mobile/openapi/lib/api/asset_api.dart
									
									
									
										generated
									
									
									
								
							@@ -234,11 +234,7 @@ class AssetApi {
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] assetId (required):
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [bool] isThumb:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [bool] isWeb:
 | 
			
		||||
  Future<Response> downloadFileWithHttpInfo(String assetId, { bool? isThumb, bool? isWeb, }) async {
 | 
			
		||||
  Future<Response> downloadFileWithHttpInfo(String assetId,) async {
 | 
			
		||||
    // ignore: prefer_const_declarations
 | 
			
		||||
    final path = r'/asset/download/{assetId}'
 | 
			
		||||
      .replaceAll('{assetId}', assetId);
 | 
			
		||||
@@ -250,13 +246,6 @@ class AssetApi {
 | 
			
		||||
    final headerParams = <String, String>{};
 | 
			
		||||
    final formParams = <String, String>{};
 | 
			
		||||
 | 
			
		||||
    if (isThumb != null) {
 | 
			
		||||
      queryParams.addAll(_queryParams('', 'isThumb', isThumb));
 | 
			
		||||
    }
 | 
			
		||||
    if (isWeb != null) {
 | 
			
		||||
      queryParams.addAll(_queryParams('', 'isWeb', isWeb));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const contentTypes = <String>[];
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@@ -276,12 +265,8 @@ class AssetApi {
 | 
			
		||||
  /// Parameters:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [String] assetId (required):
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [bool] isThumb:
 | 
			
		||||
  ///
 | 
			
		||||
  /// * [bool] isWeb:
 | 
			
		||||
  Future<Object?> downloadFile(String assetId, { bool? isThumb, bool? isWeb, }) async {
 | 
			
		||||
    final response = await downloadFileWithHttpInfo(assetId,  isThumb: isThumb, isWeb: isWeb, );
 | 
			
		||||
  Future<Object?> downloadFile(String assetId,) async {
 | 
			
		||||
    final response = await downloadFileWithHttpInfo(assetId,);
 | 
			
		||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
			
		||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										40
									
								
								mobile/openapi/lib/model/album_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										40
									
								
								mobile/openapi/lib/model/album_response_dto.dart
									
									
									
										generated
									
									
									
								
							@@ -43,9 +43,7 @@ class AlbumResponseDto {
 | 
			
		||||
  List<AssetResponseDto> assets;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) =>
 | 
			
		||||
      identical(this, other) ||
 | 
			
		||||
      other is AlbumResponseDto &&
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
 | 
			
		||||
     other.assetCount == assetCount &&
 | 
			
		||||
     other.id == id &&
 | 
			
		||||
     other.ownerId == ownerId &&
 | 
			
		||||
@@ -70,8 +68,7 @@ class AlbumResponseDto {
 | 
			
		||||
    (assets.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() =>
 | 
			
		||||
      'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
 | 
			
		||||
  String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
@@ -101,13 +98,13 @@ class AlbumResponseDto {
 | 
			
		||||
      // Ensure that the map contains the required keys.
 | 
			
		||||
      // Note 1: the values aren't checked for validity beyond being non-null.
 | 
			
		||||
      // Note 2: this code is stripped in release mode!
 | 
			
		||||
      // assert(() {
 | 
			
		||||
      //   requiredKeys.forEach((key) {
 | 
			
		||||
      //     assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
 | 
			
		||||
      //     assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
 | 
			
		||||
      //   });
 | 
			
		||||
      //   return true;
 | 
			
		||||
      // }());
 | 
			
		||||
      assert(() {
 | 
			
		||||
        requiredKeys.forEach((key) {
 | 
			
		||||
          assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
 | 
			
		||||
          assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
 | 
			
		||||
        });
 | 
			
		||||
        return true;
 | 
			
		||||
      }());
 | 
			
		||||
 | 
			
		||||
      return AlbumResponseDto(
 | 
			
		||||
        assetCount: mapValueOfType<int>(json, r'assetCount')!,
 | 
			
		||||
@@ -115,8 +112,7 @@ class AlbumResponseDto {
 | 
			
		||||
        ownerId: mapValueOfType<String>(json, r'ownerId')!,
 | 
			
		||||
        albumName: mapValueOfType<String>(json, r'albumName')!,
 | 
			
		||||
        createdAt: mapValueOfType<String>(json, r'createdAt')!,
 | 
			
		||||
        albumThumbnailAssetId:
 | 
			
		||||
            mapValueOfType<String>(json, r'albumThumbnailAssetId'),
 | 
			
		||||
        albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
 | 
			
		||||
        shared: mapValueOfType<bool>(json, r'shared')!,
 | 
			
		||||
        sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
 | 
			
		||||
        assets: AssetResponseDto.listFromJson(json[r'assets'])!,
 | 
			
		||||
@@ -125,10 +121,7 @@ class AlbumResponseDto {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<AlbumResponseDto>? listFromJson(
 | 
			
		||||
    dynamic json, {
 | 
			
		||||
    bool growable = false,
 | 
			
		||||
  }) {
 | 
			
		||||
  static List<AlbumResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <AlbumResponseDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
@@ -156,18 +149,12 @@ class AlbumResponseDto {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of AlbumResponseDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<AlbumResponseDto>> mapListFromJson(
 | 
			
		||||
    dynamic json, {
 | 
			
		||||
    bool growable = false,
 | 
			
		||||
  }) {
 | 
			
		||||
  static Map<String, List<AlbumResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<AlbumResponseDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = AlbumResponseDto.listFromJson(
 | 
			
		||||
          entry.value,
 | 
			
		||||
          growable: growable,
 | 
			
		||||
        );
 | 
			
		||||
        final value = AlbumResponseDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
@@ -189,3 +176,4 @@ class AlbumResponseDto {
 | 
			
		||||
    'assets',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										37
									
								
								mobile/openapi/lib/model/asset_response_dto.dart
									
									
									
										generated
									
									
									
								
							@@ -82,9 +82,7 @@ class AssetResponseDto {
 | 
			
		||||
  List<TagResponseDto> tags;
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  bool operator ==(Object other) =>
 | 
			
		||||
      identical(this, other) ||
 | 
			
		||||
      other is AssetResponseDto &&
 | 
			
		||||
  bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
 | 
			
		||||
     other.type == type &&
 | 
			
		||||
     other.id == id &&
 | 
			
		||||
     other.deviceAssetId == deviceAssetId &&
 | 
			
		||||
@@ -127,8 +125,7 @@ class AssetResponseDto {
 | 
			
		||||
    (tags.hashCode);
 | 
			
		||||
 | 
			
		||||
  @override
 | 
			
		||||
  String toString() =>
 | 
			
		||||
      'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]';
 | 
			
		||||
  String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, resizePath=$resizePath, createdAt=$createdAt, modifiedAt=$modifiedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]';
 | 
			
		||||
 | 
			
		||||
  Map<String, dynamic> toJson() {
 | 
			
		||||
    final json = <String, dynamic>{};
 | 
			
		||||
@@ -191,13 +188,13 @@ class AssetResponseDto {
 | 
			
		||||
      // Ensure that the map contains the required keys.
 | 
			
		||||
      // Note 1: the values aren't checked for validity beyond being non-null.
 | 
			
		||||
      // Note 2: this code is stripped in release mode!
 | 
			
		||||
      // assert(() {
 | 
			
		||||
      //   requiredKeys.forEach((key) {
 | 
			
		||||
      //     assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
 | 
			
		||||
      //     assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
 | 
			
		||||
      //   });
 | 
			
		||||
      //   return true;
 | 
			
		||||
      // }());
 | 
			
		||||
      assert(() {
 | 
			
		||||
        requiredKeys.forEach((key) {
 | 
			
		||||
          assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
 | 
			
		||||
          assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
 | 
			
		||||
        });
 | 
			
		||||
        return true;
 | 
			
		||||
      }());
 | 
			
		||||
 | 
			
		||||
      return AssetResponseDto(
 | 
			
		||||
        type: AssetTypeEnum.fromJson(json[r'type'])!,
 | 
			
		||||
@@ -223,10 +220,7 @@ class AssetResponseDto {
 | 
			
		||||
    return null;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  static List<AssetResponseDto>? listFromJson(
 | 
			
		||||
    dynamic json, {
 | 
			
		||||
    bool growable = false,
 | 
			
		||||
  }) {
 | 
			
		||||
  static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final result = <AssetResponseDto>[];
 | 
			
		||||
    if (json is List && json.isNotEmpty) {
 | 
			
		||||
      for (final row in json) {
 | 
			
		||||
@@ -254,18 +248,12 @@ class AssetResponseDto {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  // maps a json object with a list of AssetResponseDto-objects as value to a dart map
 | 
			
		||||
  static Map<String, List<AssetResponseDto>> mapListFromJson(
 | 
			
		||||
    dynamic json, {
 | 
			
		||||
    bool growable = false,
 | 
			
		||||
  }) {
 | 
			
		||||
  static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
 | 
			
		||||
    final map = <String, List<AssetResponseDto>>{};
 | 
			
		||||
    if (json is Map && json.isNotEmpty) {
 | 
			
		||||
      json = json.cast<String, dynamic>(); // ignore: parameter_assignments
 | 
			
		||||
      for (final entry in json.entries) {
 | 
			
		||||
        final value = AssetResponseDto.listFromJson(
 | 
			
		||||
          entry.value,
 | 
			
		||||
          growable: growable,
 | 
			
		||||
        );
 | 
			
		||||
        final value = AssetResponseDto.listFromJson(entry.value, growable: growable,);
 | 
			
		||||
        if (value != null) {
 | 
			
		||||
          map[entry.key] = value;
 | 
			
		||||
        }
 | 
			
		||||
@@ -292,3 +280,4 @@ class AssetResponseDto {
 | 
			
		||||
    'tags',
 | 
			
		||||
  };
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								mobile/openapi/test/asset_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/test/asset_api_test.dart
									
									
									
										generated
									
									
									
								
							@@ -47,7 +47,7 @@ void main() {
 | 
			
		||||
 | 
			
		||||
    // 
 | 
			
		||||
    //
 | 
			
		||||
    //Future<Object> downloadFile(String assetId, { bool isThumb, bool isWeb }) async
 | 
			
		||||
    //Future<Object> downloadFile(String assetId) async
 | 
			
		||||
    test('test downloadFile', () async {
 | 
			
		||||
      // TODO
 | 
			
		||||
    });
 | 
			
		||||
 
 | 
			
		||||
@@ -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<any> {
 | 
			
		||||
    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 })
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ISharedLinkRepository>;
 | 
			
		||||
  let cryptoMock: jest.Mocked<ICryptoRepository>;
 | 
			
		||||
  let jobMock: jest.Mocked<IJobRepository>;
 | 
			
		||||
  let storageMock: jest.Mocked<IStorageRepository>;
 | 
			
		||||
 | 
			
		||||
  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');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -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<ImmichReadStream> {
 | 
			
		||||
    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 asset = await this._assetRepository.get(assetId);
 | 
			
		||||
      if (asset && asset.originalPath && asset.mimeType) {
 | 
			
		||||
        return this.storage.createReadStream(asset.originalPath, asset.mimeType);
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
          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);
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      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<Buffer> {
 | 
			
		||||
    const fileReadStream = createReadStream(filePath);
 | 
			
		||||
    const sha1Hash = createHash('sha1');
 | 
			
		||||
    const deferred = new Promise<Buffer>((resolve, reject) => {
 | 
			
		||||
      sha1Hash.once('error', (err) => reject(err));
 | 
			
		||||
      sha1Hash.once('finish', () => resolve(sha1Hash.read()));
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    fileReadStream.pipe(sha1Hash);
 | 
			
		||||
    return deferred;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  getAssetCountByUserId(authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
 | 
			
		||||
    return this._assetRepository.getAssetCountByUserId(authUser.id);
 | 
			
		||||
  }
 | 
			
		||||
 
 | 
			
		||||
@@ -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,
 | 
			
		||||
 
 | 
			
		||||
@@ -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(
 | 
			
		||||
 
 | 
			
		||||
@@ -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';
 | 
			
		||||
 
 | 
			
		||||
@@ -1,4 +0,0 @@
 | 
			
		||||
export class JwtPayloadDto {
 | 
			
		||||
  userId!: string;
 | 
			
		||||
  email!: string;
 | 
			
		||||
}
 | 
			
		||||
@@ -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';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										1
									
								
								server/libs/domain/src/storage/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/libs/domain/src/storage/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export * from './storage.repository';
 | 
			
		||||
							
								
								
									
										13
									
								
								server/libs/domain/src/storage/storage.repository.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								server/libs/domain/src/storage/storage.repository.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<ImmichReadStream>;
 | 
			
		||||
}
 | 
			
		||||
@@ -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';
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								server/libs/domain/test/storage.repository.mock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								server/libs/domain/test/storage.repository.mock.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
			
		||||
import { IStorageRepository } from '../src';
 | 
			
		||||
 | 
			
		||||
export const newStorageRepositoryMock = (): jest.Mocked<IStorageRepository> => {
 | 
			
		||||
  return {
 | 
			
		||||
    createReadStream: jest.fn(),
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@@ -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 },
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										18
									
								
								server/libs/infra/src/storage/filesystem.provider.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								server/libs/infra/src/storage/filesystem.provider.ts
									
									
									
									
									
										Normal file
									
								
							@@ -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<ImmichReadStream> {
 | 
			
		||||
    const { size } = await fileInfo(filepath);
 | 
			
		||||
    await fs.access(filepath, constants.R_OK | constants.W_OK);
 | 
			
		||||
    return {
 | 
			
		||||
      stream: createReadStream(filepath),
 | 
			
		||||
      length: size,
 | 
			
		||||
      type: mimeType,
 | 
			
		||||
    };
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										1
									
								
								server/libs/infra/src/storage/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/libs/infra/src/storage/index.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
export * from './filesystem.provider';
 | 
			
		||||
							
								
								
									
										32
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										32
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							@@ -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<RequestArgs> => {
 | 
			
		||||
        downloadFile: async (assetId: string, options: AxiosRequestConfig = {}): Promise<RequestArgs> => {
 | 
			
		||||
            // 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<object>> {
 | 
			
		||||
            const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(assetId, isThumb, isWeb, options);
 | 
			
		||||
        async downloadFile(assetId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
 | 
			
		||||
            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<object> {
 | 
			
		||||
            return localVarFp.downloadFile(assetId, isThumb, isWeb, options).then((request) => request(axios, basePath));
 | 
			
		||||
        downloadFile(assetId: string, options?: any): AxiosPromise<object> {
 | 
			
		||||
            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));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								web/src/api/open-api/base.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								web/src/api/open-api/base.ts
									
									
									
										generated
									
									
									
								
							@@ -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).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								web/src/api/open-api/common.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								web/src/api/open-api/common.ts
									
									
									
										generated
									
									
									
								
							@@ -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).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								web/src/api/open-api/configuration.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								web/src/api/open-api/configuration.ts
									
									
									
										generated
									
									
									
								
							@@ -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).
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								web/src/api/open-api/index.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								web/src/api/open-api/index.ts
									
									
									
										generated
									
									
									
								
							@@ -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).
 | 
			
		||||
 
 | 
			
		||||
@@ -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) {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user