mirror of
https://github.com/immich-app/immich.git
synced 2025-03-01 15:11:21 +01: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:
parent
e39507552f
commit
2b0b2bb1ae
29 changed files with 210 additions and 291 deletions
|
@ -26,14 +26,10 @@ class ImageViewerService {
|
||||||
if (asset.type == AssetTypeEnum.IMAGE && asset.livePhotoVideoId != null) {
|
if (asset.type == AssetTypeEnum.IMAGE && asset.livePhotoVideoId != null) {
|
||||||
var imageResponse = await _apiService.assetApi.downloadFileWithHttpInfo(
|
var imageResponse = await _apiService.assetApi.downloadFileWithHttpInfo(
|
||||||
asset.id,
|
asset.id,
|
||||||
isThumb: false,
|
|
||||||
isWeb: false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
var motionReponse = await _apiService.assetApi.downloadFileWithHttpInfo(
|
var motionReponse = await _apiService.assetApi.downloadFileWithHttpInfo(
|
||||||
asset.livePhotoVideoId!,
|
asset.livePhotoVideoId!,
|
||||||
isThumb: false,
|
|
||||||
isWeb: false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final AssetEntity? entity;
|
final AssetEntity? entity;
|
||||||
|
@ -54,8 +50,6 @@ class ImageViewerService {
|
||||||
} else {
|
} else {
|
||||||
var res = await _apiService.assetApi.downloadFileWithHttpInfo(
|
var res = await _apiService.assetApi.downloadFileWithHttpInfo(
|
||||||
asset.id,
|
asset.id,
|
||||||
isThumb: false,
|
|
||||||
isWeb: false,
|
|
||||||
);
|
);
|
||||||
|
|
||||||
final AssetEntity? entity;
|
final AssetEntity? entity;
|
||||||
|
|
|
@ -29,8 +29,6 @@ class ShareService {
|
||||||
final tempFile = await File('${tempDir.path}/$fileName').create();
|
final tempFile = await File('${tempDir.path}/$fileName').create();
|
||||||
final res = await _apiService.assetApi.downloadFileWithHttpInfo(
|
final res = await _apiService.assetApi.downloadFileWithHttpInfo(
|
||||||
asset.remote!.id,
|
asset.remote!.id,
|
||||||
isThumb: false,
|
|
||||||
isWeb: false,
|
|
||||||
);
|
);
|
||||||
tempFile.writeAsBytesSync(res.bodyBytes);
|
tempFile.writeAsBytesSync(res.bodyBytes);
|
||||||
return XFile(tempFile.path);
|
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:
|
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
|
- Build package: org.openapitools.codegen.languages.DartClientCodegen
|
||||||
|
|
||||||
## Requirements
|
## 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)
|
[[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**
|
# **downloadFile**
|
||||||
> Object downloadFile(assetId, isThumb, isWeb)
|
> Object downloadFile(assetId)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -248,11 +248,9 @@ import 'package:openapi/api.dart';
|
||||||
|
|
||||||
final api_instance = AssetApi();
|
final api_instance = AssetApi();
|
||||||
final assetId = assetId_example; // String |
|
final assetId = assetId_example; // String |
|
||||||
final isThumb = true; // bool |
|
|
||||||
final isWeb = true; // bool |
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
final result = api_instance.downloadFile(assetId, isThumb, isWeb);
|
final result = api_instance.downloadFile(assetId);
|
||||||
print(result);
|
print(result);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
print('Exception when calling AssetApi->downloadFile: $e\n');
|
print('Exception when calling AssetApi->downloadFile: $e\n');
|
||||||
|
@ -264,8 +262,6 @@ try {
|
||||||
Name | Type | Description | Notes
|
Name | Type | Description | Notes
|
||||||
------------- | ------------- | ------------- | -------------
|
------------- | ------------- | ------------- | -------------
|
||||||
**assetId** | **String**| |
|
**assetId** | **String**| |
|
||||||
**isThumb** | **bool**| | [optional]
|
|
||||||
**isWeb** | **bool**| | [optional]
|
|
||||||
|
|
||||||
### Return type
|
### 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:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [String] assetId (required):
|
/// * [String] assetId (required):
|
||||||
///
|
Future<Response> downloadFileWithHttpInfo(String assetId,) async {
|
||||||
/// * [bool] isThumb:
|
|
||||||
///
|
|
||||||
/// * [bool] isWeb:
|
|
||||||
Future<Response> downloadFileWithHttpInfo(String assetId, { bool? isThumb, bool? isWeb, }) async {
|
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final path = r'/asset/download/{assetId}'
|
final path = r'/asset/download/{assetId}'
|
||||||
.replaceAll('{assetId}', assetId);
|
.replaceAll('{assetId}', assetId);
|
||||||
|
@ -250,13 +246,6 @@ class AssetApi {
|
||||||
final headerParams = <String, String>{};
|
final headerParams = <String, String>{};
|
||||||
final formParams = <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>[];
|
const contentTypes = <String>[];
|
||||||
|
|
||||||
|
|
||||||
|
@ -276,12 +265,8 @@ class AssetApi {
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [String] assetId (required):
|
/// * [String] assetId (required):
|
||||||
///
|
Future<Object?> downloadFile(String assetId,) async {
|
||||||
/// * [bool] isThumb:
|
final response = await downloadFileWithHttpInfo(assetId,);
|
||||||
///
|
|
||||||
/// * [bool] isWeb:
|
|
||||||
Future<Object?> downloadFile(String assetId, { bool? isThumb, bool? isWeb, }) async {
|
|
||||||
final response = await downloadFileWithHttpInfo(assetId, isThumb: isThumb, isWeb: isWeb, );
|
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
|
|
94
mobile/openapi/lib/model/album_response_dto.dart
generated
94
mobile/openapi/lib/model/album_response_dto.dart
generated
|
@ -43,51 +43,48 @@ class AlbumResponseDto {
|
||||||
List<AssetResponseDto> assets;
|
List<AssetResponseDto> assets;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) => identical(this, other) || other is AlbumResponseDto &&
|
||||||
identical(this, other) ||
|
other.assetCount == assetCount &&
|
||||||
other is AlbumResponseDto &&
|
other.id == id &&
|
||||||
other.assetCount == assetCount &&
|
other.ownerId == ownerId &&
|
||||||
other.id == id &&
|
other.albumName == albumName &&
|
||||||
other.ownerId == ownerId &&
|
other.createdAt == createdAt &&
|
||||||
other.albumName == albumName &&
|
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
||||||
other.createdAt == createdAt &&
|
other.shared == shared &&
|
||||||
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
other.sharedUsers == sharedUsers &&
|
||||||
other.shared == shared &&
|
other.assets == assets;
|
||||||
other.sharedUsers == sharedUsers &&
|
|
||||||
other.assets == assets;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(assetCount.hashCode) +
|
(assetCount.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(ownerId.hashCode) +
|
(ownerId.hashCode) +
|
||||||
(albumName.hashCode) +
|
(albumName.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
||||||
(shared.hashCode) +
|
(shared.hashCode) +
|
||||||
(sharedUsers.hashCode) +
|
(sharedUsers.hashCode) +
|
||||||
(assets.hashCode);
|
(assets.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
String toString() => 'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
|
||||||
'AlbumResponseDto[assetCount=$assetCount, id=$id, ownerId=$ownerId, albumName=$albumName, createdAt=$createdAt, albumThumbnailAssetId=$albumThumbnailAssetId, shared=$shared, sharedUsers=$sharedUsers, assets=$assets]';
|
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'assetCount'] = this.assetCount;
|
json[r'assetCount'] = this.assetCount;
|
||||||
json[r'id'] = this.id;
|
json[r'id'] = this.id;
|
||||||
json[r'ownerId'] = this.ownerId;
|
json[r'ownerId'] = this.ownerId;
|
||||||
json[r'albumName'] = this.albumName;
|
json[r'albumName'] = this.albumName;
|
||||||
json[r'createdAt'] = this.createdAt;
|
json[r'createdAt'] = this.createdAt;
|
||||||
if (this.albumThumbnailAssetId != null) {
|
if (this.albumThumbnailAssetId != null) {
|
||||||
json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId;
|
json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId;
|
||||||
} else {
|
} else {
|
||||||
// json[r'albumThumbnailAssetId'] = null;
|
// json[r'albumThumbnailAssetId'] = null;
|
||||||
}
|
}
|
||||||
json[r'shared'] = this.shared;
|
json[r'shared'] = this.shared;
|
||||||
json[r'sharedUsers'] = this.sharedUsers;
|
json[r'sharedUsers'] = this.sharedUsers;
|
||||||
json[r'assets'] = this.assets;
|
json[r'assets'] = this.assets;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,13 +98,13 @@ class AlbumResponseDto {
|
||||||
// Ensure that the map contains the required keys.
|
// Ensure that the map contains the required keys.
|
||||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
// Note 2: this code is stripped in release mode!
|
// Note 2: this code is stripped in release mode!
|
||||||
// assert(() {
|
assert(() {
|
||||||
// requiredKeys.forEach((key) {
|
requiredKeys.forEach((key) {
|
||||||
// assert(json.containsKey(key), 'Required key "AlbumResponseDto[$key]" is missing from JSON.');
|
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.');
|
assert(json[key] != null, 'Required key "AlbumResponseDto[$key]" has a null value in JSON.');
|
||||||
// });
|
});
|
||||||
// return true;
|
return true;
|
||||||
// }());
|
}());
|
||||||
|
|
||||||
return AlbumResponseDto(
|
return AlbumResponseDto(
|
||||||
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
||||||
|
@ -115,8 +112,7 @@ class AlbumResponseDto {
|
||||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||||
albumName: mapValueOfType<String>(json, r'albumName')!,
|
albumName: mapValueOfType<String>(json, r'albumName')!,
|
||||||
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
createdAt: mapValueOfType<String>(json, r'createdAt')!,
|
||||||
albumThumbnailAssetId:
|
albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
||||||
mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
|
||||||
shared: mapValueOfType<bool>(json, r'shared')!,
|
shared: mapValueOfType<bool>(json, r'shared')!,
|
||||||
sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
|
sharedUsers: UserResponseDto.listFromJson(json[r'sharedUsers'])!,
|
||||||
assets: AssetResponseDto.listFromJson(json[r'assets'])!,
|
assets: AssetResponseDto.listFromJson(json[r'assets'])!,
|
||||||
|
@ -125,10 +121,7 @@ class AlbumResponseDto {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AlbumResponseDto>? listFromJson(
|
static List<AlbumResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final result = <AlbumResponseDto>[];
|
final result = <AlbumResponseDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
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
|
// maps a json object with a list of AlbumResponseDto-objects as value to a dart map
|
||||||
static Map<String, List<AlbumResponseDto>> mapListFromJson(
|
static Map<String, List<AlbumResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final map = <String, List<AlbumResponseDto>>{};
|
final map = <String, List<AlbumResponseDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = AlbumResponseDto.listFromJson(
|
final value = AlbumResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
entry.value,
|
|
||||||
growable: growable,
|
|
||||||
);
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
|
@ -189,3 +176,4 @@ class AlbumResponseDto {
|
||||||
'assets',
|
'assets',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
133
mobile/openapi/lib/model/asset_response_dto.dart
generated
133
mobile/openapi/lib/model/asset_response_dto.dart
generated
|
@ -82,76 +82,73 @@ class AssetResponseDto {
|
||||||
List<TagResponseDto> tags;
|
List<TagResponseDto> tags;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) =>
|
bool operator ==(Object other) => identical(this, other) || other is AssetResponseDto &&
|
||||||
identical(this, other) ||
|
other.type == type &&
|
||||||
other is AssetResponseDto &&
|
other.id == id &&
|
||||||
other.type == type &&
|
other.deviceAssetId == deviceAssetId &&
|
||||||
other.id == id &&
|
other.ownerId == ownerId &&
|
||||||
other.deviceAssetId == deviceAssetId &&
|
other.deviceId == deviceId &&
|
||||||
other.ownerId == ownerId &&
|
other.originalPath == originalPath &&
|
||||||
other.deviceId == deviceId &&
|
other.resizePath == resizePath &&
|
||||||
other.originalPath == originalPath &&
|
other.createdAt == createdAt &&
|
||||||
other.resizePath == resizePath &&
|
other.modifiedAt == modifiedAt &&
|
||||||
other.createdAt == createdAt &&
|
other.isFavorite == isFavorite &&
|
||||||
other.modifiedAt == modifiedAt &&
|
other.mimeType == mimeType &&
|
||||||
other.isFavorite == isFavorite &&
|
other.duration == duration &&
|
||||||
other.mimeType == mimeType &&
|
other.webpPath == webpPath &&
|
||||||
other.duration == duration &&
|
other.encodedVideoPath == encodedVideoPath &&
|
||||||
other.webpPath == webpPath &&
|
other.exifInfo == exifInfo &&
|
||||||
other.encodedVideoPath == encodedVideoPath &&
|
other.smartInfo == smartInfo &&
|
||||||
other.exifInfo == exifInfo &&
|
other.livePhotoVideoId == livePhotoVideoId &&
|
||||||
other.smartInfo == smartInfo &&
|
other.tags == tags;
|
||||||
other.livePhotoVideoId == livePhotoVideoId &&
|
|
||||||
other.tags == tags;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(type.hashCode) +
|
(type.hashCode) +
|
||||||
(id.hashCode) +
|
(id.hashCode) +
|
||||||
(deviceAssetId.hashCode) +
|
(deviceAssetId.hashCode) +
|
||||||
(ownerId.hashCode) +
|
(ownerId.hashCode) +
|
||||||
(deviceId.hashCode) +
|
(deviceId.hashCode) +
|
||||||
(originalPath.hashCode) +
|
(originalPath.hashCode) +
|
||||||
(resizePath == null ? 0 : resizePath!.hashCode) +
|
(resizePath == null ? 0 : resizePath!.hashCode) +
|
||||||
(createdAt.hashCode) +
|
(createdAt.hashCode) +
|
||||||
(modifiedAt.hashCode) +
|
(modifiedAt.hashCode) +
|
||||||
(isFavorite.hashCode) +
|
(isFavorite.hashCode) +
|
||||||
(mimeType == null ? 0 : mimeType!.hashCode) +
|
(mimeType == null ? 0 : mimeType!.hashCode) +
|
||||||
(duration.hashCode) +
|
(duration.hashCode) +
|
||||||
(webpPath == null ? 0 : webpPath!.hashCode) +
|
(webpPath == null ? 0 : webpPath!.hashCode) +
|
||||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||||
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
||||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||||
(tags.hashCode);
|
(tags.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() =>
|
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]';
|
||||||
'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() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'type'] = this.type;
|
json[r'type'] = this.type;
|
||||||
json[r'id'] = this.id;
|
json[r'id'] = this.id;
|
||||||
json[r'deviceAssetId'] = this.deviceAssetId;
|
json[r'deviceAssetId'] = this.deviceAssetId;
|
||||||
json[r'ownerId'] = this.ownerId;
|
json[r'ownerId'] = this.ownerId;
|
||||||
json[r'deviceId'] = this.deviceId;
|
json[r'deviceId'] = this.deviceId;
|
||||||
json[r'originalPath'] = this.originalPath;
|
json[r'originalPath'] = this.originalPath;
|
||||||
if (this.resizePath != null) {
|
if (this.resizePath != null) {
|
||||||
json[r'resizePath'] = this.resizePath;
|
json[r'resizePath'] = this.resizePath;
|
||||||
} else {
|
} else {
|
||||||
// json[r'resizePath'] = null;
|
// json[r'resizePath'] = null;
|
||||||
}
|
}
|
||||||
json[r'createdAt'] = this.createdAt;
|
json[r'createdAt'] = this.createdAt;
|
||||||
json[r'modifiedAt'] = this.modifiedAt;
|
json[r'modifiedAt'] = this.modifiedAt;
|
||||||
json[r'isFavorite'] = this.isFavorite;
|
json[r'isFavorite'] = this.isFavorite;
|
||||||
if (this.mimeType != null) {
|
if (this.mimeType != null) {
|
||||||
json[r'mimeType'] = this.mimeType;
|
json[r'mimeType'] = this.mimeType;
|
||||||
} else {
|
} else {
|
||||||
// json[r'mimeType'] = null;
|
// json[r'mimeType'] = null;
|
||||||
}
|
}
|
||||||
json[r'duration'] = this.duration;
|
json[r'duration'] = this.duration;
|
||||||
if (this.webpPath != null) {
|
if (this.webpPath != null) {
|
||||||
json[r'webpPath'] = this.webpPath;
|
json[r'webpPath'] = this.webpPath;
|
||||||
} else {
|
} else {
|
||||||
|
@ -177,7 +174,7 @@ class AssetResponseDto {
|
||||||
} else {
|
} else {
|
||||||
// json[r'livePhotoVideoId'] = null;
|
// json[r'livePhotoVideoId'] = null;
|
||||||
}
|
}
|
||||||
json[r'tags'] = this.tags;
|
json[r'tags'] = this.tags;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -191,13 +188,13 @@ class AssetResponseDto {
|
||||||
// Ensure that the map contains the required keys.
|
// Ensure that the map contains the required keys.
|
||||||
// Note 1: the values aren't checked for validity beyond being non-null.
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
// Note 2: this code is stripped in release mode!
|
// Note 2: this code is stripped in release mode!
|
||||||
// assert(() {
|
assert(() {
|
||||||
// requiredKeys.forEach((key) {
|
requiredKeys.forEach((key) {
|
||||||
// assert(json.containsKey(key), 'Required key "AssetResponseDto[$key]" is missing from JSON.');
|
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.');
|
assert(json[key] != null, 'Required key "AssetResponseDto[$key]" has a null value in JSON.');
|
||||||
// });
|
});
|
||||||
// return true;
|
return true;
|
||||||
// }());
|
}());
|
||||||
|
|
||||||
return AssetResponseDto(
|
return AssetResponseDto(
|
||||||
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
||||||
|
@ -223,10 +220,7 @@ class AssetResponseDto {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AssetResponseDto>? listFromJson(
|
static List<AssetResponseDto>? listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final result = <AssetResponseDto>[];
|
final result = <AssetResponseDto>[];
|
||||||
if (json is List && json.isNotEmpty) {
|
if (json is List && json.isNotEmpty) {
|
||||||
for (final row in json) {
|
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
|
// maps a json object with a list of AssetResponseDto-objects as value to a dart map
|
||||||
static Map<String, List<AssetResponseDto>> mapListFromJson(
|
static Map<String, List<AssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
dynamic json, {
|
|
||||||
bool growable = false,
|
|
||||||
}) {
|
|
||||||
final map = <String, List<AssetResponseDto>>{};
|
final map = <String, List<AssetResponseDto>>{};
|
||||||
if (json is Map && json.isNotEmpty) {
|
if (json is Map && json.isNotEmpty) {
|
||||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
for (final entry in json.entries) {
|
for (final entry in json.entries) {
|
||||||
final value = AssetResponseDto.listFromJson(
|
final value = AssetResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
entry.value,
|
|
||||||
growable: growable,
|
|
||||||
);
|
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
map[entry.key] = value;
|
map[entry.key] = value;
|
||||||
}
|
}
|
||||||
|
@ -292,3 +280,4 @@ class AssetResponseDto {
|
||||||
'tags',
|
'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 {
|
test('test downloadFile', () async {
|
||||||
// TODO
|
// TODO
|
||||||
});
|
});
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
Put,
|
Put,
|
||||||
UploadedFiles,
|
UploadedFiles,
|
||||||
Patch,
|
Patch,
|
||||||
|
StreamableFile,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { Authenticated } from '../../decorators/authenticated.decorator';
|
import { Authenticated } from '../../decorators/authenticated.decorator';
|
||||||
import { AssetService } from './asset.service';
|
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 { ApiBearerAuth, ApiBody, ApiConsumes, ApiHeader, ApiTags } from '@nestjs/swagger';
|
||||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.dto';
|
||||||
import { CuratedLocationsResponseDto } from './response-dto/curated-locations-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 { CheckDuplicateAssetResponseDto } from './response-dto/check-duplicate-asset-response.dto';
|
||||||
import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
|
import { AssetFileUploadDto } from './dto/asset-file-upload.dto';
|
||||||
import { CreateAssetDto, mapToUploadFile } from './dto/create-asset.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 { AssetSearchDto } from './dto/asset-search.dto';
|
||||||
import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config';
|
import { assetUploadOption, ImmichFile } from '../../config/asset-upload.config';
|
||||||
|
|
||||||
|
function asStreamableFile({ stream, type, length }: ImmichReadStream) {
|
||||||
|
return new StreamableFile(stream, { type, length });
|
||||||
|
}
|
||||||
|
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiTags('Asset')
|
@ApiTags('Asset')
|
||||||
@Controller('asset')
|
@Controller('asset')
|
||||||
|
@ -103,12 +108,9 @@ export class AssetController {
|
||||||
async downloadFile(
|
async downloadFile(
|
||||||
@GetAuthUser() authUser: AuthUserDto,
|
@GetAuthUser() authUser: AuthUserDto,
|
||||||
@Response({ passthrough: true }) res: Res,
|
@Response({ passthrough: true }) res: Res,
|
||||||
@Query(new ValidationPipe({ transform: true })) query: ServeFileDto,
|
|
||||||
@Param('assetId') assetId: string,
|
@Param('assetId') assetId: string,
|
||||||
): Promise<any> {
|
): Promise<any> {
|
||||||
this.assetService.checkDownloadAccess(authUser);
|
return this.assetService.downloadFile(authUser, assetId).then(asStreamableFile);
|
||||||
await this.assetService.checkAssetsAccess(authUser, [assetId]);
|
|
||||||
return this.assetService.downloadFile(query, assetId, res);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Authenticated({ isShared: true })
|
@Authenticated({ isShared: true })
|
||||||
|
|
|
@ -9,12 +9,13 @@ import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-use
|
||||||
import { DownloadService } from '../../modules/download/download.service';
|
import { DownloadService } from '../../modules/download/download.service';
|
||||||
import { AlbumRepository, IAlbumRepository } from '../album/album-repository';
|
import { AlbumRepository, IAlbumRepository } from '../album/album-repository';
|
||||||
import { StorageService } from '@app/storage';
|
import { StorageService } from '@app/storage';
|
||||||
import { ICryptoRepository, IJobRepository, ISharedLinkRepository, JobName } from '@app/domain';
|
import { ICryptoRepository, IJobRepository, ISharedLinkRepository, IStorageRepository, JobName } from '@app/domain';
|
||||||
import {
|
import {
|
||||||
authStub,
|
authStub,
|
||||||
newCryptoRepositoryMock,
|
newCryptoRepositoryMock,
|
||||||
newJobRepositoryMock,
|
newJobRepositoryMock,
|
||||||
newSharedLinkRepositoryMock,
|
newSharedLinkRepositoryMock,
|
||||||
|
newStorageRepositoryMock,
|
||||||
sharedLinkResponseStub,
|
sharedLinkResponseStub,
|
||||||
sharedLinkStub,
|
sharedLinkStub,
|
||||||
} from '@app/domain/../test';
|
} from '@app/domain/../test';
|
||||||
|
@ -110,6 +111,7 @@ describe('AssetService', () => {
|
||||||
let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
|
let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
|
||||||
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
let cryptoMock: jest.Mocked<ICryptoRepository>;
|
||||||
let jobMock: jest.Mocked<IJobRepository>;
|
let jobMock: jest.Mocked<IJobRepository>;
|
||||||
|
let storageMock: jest.Mocked<IStorageRepository>;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
assetRepositoryMock = {
|
assetRepositoryMock = {
|
||||||
|
@ -154,6 +156,7 @@ describe('AssetService', () => {
|
||||||
sharedLinkRepositoryMock = newSharedLinkRepositoryMock();
|
sharedLinkRepositoryMock = newSharedLinkRepositoryMock();
|
||||||
jobMock = newJobRepositoryMock();
|
jobMock = newJobRepositoryMock();
|
||||||
cryptoMock = newCryptoRepositoryMock();
|
cryptoMock = newCryptoRepositoryMock();
|
||||||
|
storageMock = newStorageRepositoryMock();
|
||||||
|
|
||||||
sut = new AssetService(
|
sut = new AssetService(
|
||||||
assetRepositoryMock,
|
assetRepositoryMock,
|
||||||
|
@ -164,6 +167,7 @@ describe('AssetService', () => {
|
||||||
sharedLinkRepositoryMock,
|
sharedLinkRepositoryMock,
|
||||||
jobMock,
|
jobMock,
|
||||||
cryptoMock,
|
cryptoMock,
|
||||||
|
storageMock,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -413,4 +417,15 @@ describe('AssetService', () => {
|
||||||
expect(() => sut.checkDownloadAccess(authStub.readonlySharedLink)).toThrow(ForbiddenException);
|
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,
|
StreamableFile,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { createHash } from 'node:crypto';
|
|
||||||
import { QueryFailedError, Repository } from 'typeorm';
|
import { QueryFailedError, Repository } from 'typeorm';
|
||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
import { AuthUserDto } from '../../decorators/auth-user.decorator';
|
||||||
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra';
|
import { AssetEntity, AssetType, SharedLinkType } from '@app/infra';
|
||||||
|
@ -23,7 +22,14 @@ import { SearchAssetDto } from './dto/search-asset.dto';
|
||||||
import fs from 'fs/promises';
|
import fs from 'fs/promises';
|
||||||
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
import { CheckDuplicateAssetDto } from './dto/check-duplicate-asset.dto';
|
||||||
import { CuratedObjectsResponseDto } from './response-dto/curated-objects-response.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 { CreateAssetDto, UploadFile } from './dto/create-asset.dto';
|
||||||
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
import { DeleteAssetResponseDto, DeleteAssetStatusEnum } from './response-dto/delete-asset-response.dto';
|
||||||
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
import { GetAssetThumbnailDto, GetAssetThumbnailFormatEnum } from './dto/get-asset-thumbnail.dto';
|
||||||
|
@ -73,6 +79,7 @@ export class AssetService {
|
||||||
@Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
|
@Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
|
||||||
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
@Inject(IJobRepository) private jobRepository: IJobRepository,
|
||||||
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
@Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
|
||||||
|
@Inject(IStorageRepository) private storage: IStorageRepository,
|
||||||
) {
|
) {
|
||||||
this.assetCore = new AssetCore(_assetRepository, jobRepository, storageService);
|
this.assetCore = new AssetCore(_assetRepository, jobRepository, storageService);
|
||||||
this.shareCore = new ShareCore(sharedLinkRepository, cryptoRepository);
|
this.shareCore = new ShareCore(sharedLinkRepository, cryptoRepository);
|
||||||
|
@ -189,62 +196,21 @@ export class AssetService {
|
||||||
return this.downloadService.downloadArchive(`immich-${now}`, assetToDownload);
|
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 {
|
try {
|
||||||
let fileReadStream = null;
|
const asset = await this._assetRepository.get(assetId);
|
||||||
const asset = await this._assetRepository.getById(assetId);
|
if (asset && asset.originalPath && asset.mimeType) {
|
||||||
|
return this.storage.createReadStream(asset.originalPath, asset.mimeType);
|
||||||
// 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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return new StreamableFile(fileReadStream);
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
Logger.error(`Error download asset ${e}`, 'downloadFile');
|
Logger.error(`Error download asset ${e}`, 'downloadFile');
|
||||||
throw new InternalServerErrorException(`Failed to download asset ${e}`, 'DownloadFile');
|
throw new InternalServerErrorException(`Failed to download asset ${e}`, 'DownloadFile');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throw new NotFoundException();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async getAssetThumbnail(
|
public async getAssetThumbnail(
|
||||||
|
@ -255,8 +221,7 @@ export class AssetService {
|
||||||
) {
|
) {
|
||||||
let fileReadStream: ReadStream;
|
let fileReadStream: ReadStream;
|
||||||
|
|
||||||
const asset = await this.assetRepository.findOne({ where: { id: assetId } });
|
const asset = await this._assetRepository.get(assetId);
|
||||||
|
|
||||||
if (!asset) {
|
if (!asset) {
|
||||||
throw new NotFoundException('Asset not found');
|
throw new NotFoundException('Asset not found');
|
||||||
}
|
}
|
||||||
|
@ -584,18 +549,6 @@ export class AssetService {
|
||||||
return this._assetRepository.getAssetByChecksum(userId, checksum);
|
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> {
|
getAssetCountByUserId(authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> {
|
||||||
return this._assetRepository.getAssetCountByUserId(authUser.id);
|
return this._assetRepository.getAssetCountByUserId(authUser.id);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1109,24 +1109,6 @@
|
||||||
"operationId": "downloadFile",
|
"operationId": "downloadFile",
|
||||||
"description": "",
|
"description": "",
|
||||||
"parameters": [
|
"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",
|
"name": "assetId",
|
||||||
"required": true,
|
"required": true,
|
||||||
|
|
|
@ -6,11 +6,6 @@ import { ICryptoRepository } from '../crypto/crypto.repository';
|
||||||
import { LoginResponseDto, mapLoginResponse } from './response-dto';
|
import { LoginResponseDto, mapLoginResponse } from './response-dto';
|
||||||
import { IUserTokenRepository, UserTokenCore } from '../user-token';
|
import { IUserTokenRepository, UserTokenCore } from '../user-token';
|
||||||
|
|
||||||
export type JwtValidationResult = {
|
|
||||||
status: boolean;
|
|
||||||
userId: string | null;
|
|
||||||
};
|
|
||||||
|
|
||||||
export class AuthCore {
|
export class AuthCore {
|
||||||
private userTokenCore: UserTokenCore;
|
private userTokenCore: UserTokenCore;
|
||||||
constructor(
|
constructor(
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
export * from './auth-user.dto';
|
export * from './auth-user.dto';
|
||||||
export * from './change-password.dto';
|
export * from './change-password.dto';
|
||||||
export * from './jwt-payload.dto';
|
|
||||||
export * from './login-credential.dto';
|
export * from './login-credential.dto';
|
||||||
export * from './sign-up.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 './job';
|
||||||
export * from './oauth';
|
export * from './oauth';
|
||||||
export * from './share';
|
export * from './share';
|
||||||
|
export * from './storage';
|
||||||
export * from './system-config';
|
export * from './system-config';
|
||||||
export * from './tag';
|
export * from './tag';
|
||||||
export * from './user';
|
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 './fixtures';
|
||||||
export * from './job.repository.mock';
|
export * from './job.repository.mock';
|
||||||
export * from './shared-link.repository.mock';
|
export * from './shared-link.repository.mock';
|
||||||
|
export * from './storage.repository.mock';
|
||||||
export * from './system-config.repository.mock';
|
export * from './system-config.repository.mock';
|
||||||
export * from './user-token.repository.mock';
|
export * from './user-token.repository.mock';
|
||||||
export * from './user.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,
|
IJobRepository,
|
||||||
IKeyRepository,
|
IKeyRepository,
|
||||||
ISharedLinkRepository,
|
ISharedLinkRepository,
|
||||||
|
IStorageRepository,
|
||||||
ISystemConfigRepository,
|
ISystemConfigRepository,
|
||||||
IUserRepository,
|
IUserRepository,
|
||||||
QueueName,
|
QueueName,
|
||||||
|
@ -29,6 +30,7 @@ import {
|
||||||
UserTokenEntity,
|
UserTokenEntity,
|
||||||
} from './db';
|
} from './db';
|
||||||
import { JobRepository } from './job';
|
import { JobRepository } from './job';
|
||||||
|
import { FilesystemProvider } from './storage';
|
||||||
|
|
||||||
const providers: Provider[] = [
|
const providers: Provider[] = [
|
||||||
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
{ provide: ICryptoRepository, useClass: CryptoRepository },
|
||||||
|
@ -36,6 +38,7 @@ const providers: Provider[] = [
|
||||||
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
{ provide: IKeyRepository, useClass: APIKeyRepository },
|
||||||
{ provide: IJobRepository, useClass: JobRepository },
|
{ provide: IJobRepository, useClass: JobRepository },
|
||||||
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository },
|
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository },
|
||||||
|
{ provide: IStorageRepository, useClass: FilesystemProvider },
|
||||||
{ provide: ISystemConfigRepository, useClass: SystemConfigRepository },
|
{ provide: ISystemConfigRepository, useClass: SystemConfigRepository },
|
||||||
{ provide: IUserRepository, useClass: UserRepository },
|
{ provide: IUserRepository, useClass: UserRepository },
|
||||||
{ provide: IUserTokenRepository, useClass: UserTokenRepository },
|
{ 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
|
||||||
* Immich API
|
* 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).
|
* 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 {string} assetId
|
||||||
* @param {boolean} [isThumb]
|
|
||||||
* @param {boolean} [isWeb]
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @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
|
// verify required parameter 'assetId' is not null or undefined
|
||||||
assertParamExists('downloadFile', 'assetId', assetId)
|
assertParamExists('downloadFile', 'assetId', assetId)
|
||||||
const localVarPath = `/asset/download/{assetId}`
|
const localVarPath = `/asset/download/{assetId}`
|
||||||
|
@ -3754,14 +3752,6 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration
|
||||||
// http bearer authentication required
|
// http bearer authentication required
|
||||||
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
await setBearerAuthToObject(localVarHeaderParameter, configuration)
|
||||||
|
|
||||||
if (isThumb !== undefined) {
|
|
||||||
localVarQueryParameter['isThumb'] = isThumb;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (isWeb !== undefined) {
|
|
||||||
localVarQueryParameter['isWeb'] = isWeb;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
setSearchParams(localVarUrlObj, localVarQueryParameter);
|
||||||
|
@ -4489,13 +4479,11 @@ export const AssetApiFp = function(configuration?: Configuration) {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} assetId
|
* @param {string} assetId
|
||||||
* @param {boolean} [isThumb]
|
|
||||||
* @param {boolean} [isWeb]
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
async downloadFile(assetId: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
async downloadFile(assetId: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<object>> {
|
||||||
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(assetId, isThumb, isWeb, options);
|
const localVarAxiosArgs = await localVarAxiosParamCreator.downloadFile(assetId, options);
|
||||||
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
|
@ -4719,13 +4707,11 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} assetId
|
* @param {string} assetId
|
||||||
* @param {boolean} [isThumb]
|
|
||||||
* @param {boolean} [isWeb]
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
*/
|
*/
|
||||||
downloadFile(assetId: string, isThumb?: boolean, isWeb?: boolean, options?: any): AxiosPromise<object> {
|
downloadFile(assetId: string, options?: any): AxiosPromise<object> {
|
||||||
return localVarFp.downloadFile(assetId, isThumb, isWeb, options).then((request) => request(axios, basePath));
|
return localVarFp.downloadFile(assetId, options).then((request) => request(axios, basePath));
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
|
@ -4939,14 +4925,12 @@ export class AssetApi extends BaseAPI {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param {string} assetId
|
* @param {string} assetId
|
||||||
* @param {boolean} [isThumb]
|
|
||||||
* @param {boolean} [isWeb]
|
|
||||||
* @param {*} [options] Override http request option.
|
* @param {*} [options] Override http request option.
|
||||||
* @throws {RequiredError}
|
* @throws {RequiredError}
|
||||||
* @memberof AssetApi
|
* @memberof AssetApi
|
||||||
*/
|
*/
|
||||||
public downloadFile(assetId: string, isThumb?: boolean, isWeb?: boolean, options?: AxiosRequestConfig) {
|
public downloadFile(assetId: string, options?: AxiosRequestConfig) {
|
||||||
return AssetApiFp(this.configuration).downloadFile(assetId, isThumb, isWeb, options).then((request) => request(this.axios, this.basePath));
|
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
|
||||||
* Immich API
|
* 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).
|
* 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
|
||||||
* Immich API
|
* 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).
|
* 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
|
||||||
* Immich API
|
* 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).
|
* 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
|
||||||
* Immich API
|
* 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).
|
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
|
||||||
|
|
|
@ -136,10 +136,8 @@
|
||||||
|
|
||||||
$downloadAssets[imageFileName] = 0;
|
$downloadAssets[imageFileName] = 0;
|
||||||
|
|
||||||
const { data, status } = await api.assetApi.downloadFile(assetId, false, false, {
|
const { data, status } = await api.assetApi.downloadFile(assetId, {
|
||||||
params: {
|
params: { key },
|
||||||
key
|
|
||||||
},
|
|
||||||
responseType: 'blob',
|
responseType: 'blob',
|
||||||
onDownloadProgress: (progressEvent) => {
|
onDownloadProgress: (progressEvent) => {
|
||||||
if (progressEvent.lengthComputable) {
|
if (progressEvent.lengthComputable) {
|
||||||
|
|
Loading…
Add table
Reference in a new issue