mirror of
				https://github.com/immich-app/immich.git
				synced 2025-11-04 03:27:09 -05:00 
			
		
		
		
	Merge branch 'main' of github.com:immich-app/immich
This commit is contained in:
		
						commit
						bddba4bd96
					
				
							
								
								
									
										2
									
								
								mobile/openapi/doc/CreateAlbumShareLinkDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/doc/CreateAlbumShareLinkDto.md
									
									
									
										generated
									
									
									
								
							@ -9,7 +9,7 @@ import 'package:openapi/api.dart';
 | 
				
			|||||||
Name | Type | Description | Notes
 | 
					Name | Type | Description | Notes
 | 
				
			||||||
------------ | ------------- | ------------- | -------------
 | 
					------------ | ------------- | ------------- | -------------
 | 
				
			||||||
**albumId** | **String** |  | 
 | 
					**albumId** | **String** |  | 
 | 
				
			||||||
**expiredAt** | **String** |  | [optional] 
 | 
					**expiresAt** | **String** |  | [optional] 
 | 
				
			||||||
**allowUpload** | **bool** |  | [optional] 
 | 
					**allowUpload** | **bool** |  | [optional] 
 | 
				
			||||||
**allowDownload** | **bool** |  | [optional] 
 | 
					**allowDownload** | **bool** |  | [optional] 
 | 
				
			||||||
**showExif** | **bool** |  | [optional] 
 | 
					**showExif** | **bool** |  | [optional] 
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								mobile/openapi/doc/CreateAssetsShareLinkDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/doc/CreateAssetsShareLinkDto.md
									
									
									
										generated
									
									
									
								
							@ -9,7 +9,7 @@ import 'package:openapi/api.dart';
 | 
				
			|||||||
Name | Type | Description | Notes
 | 
					Name | Type | Description | Notes
 | 
				
			||||||
------------ | ------------- | ------------- | -------------
 | 
					------------ | ------------- | ------------- | -------------
 | 
				
			||||||
**assetIds** | **List<String>** |  | [default to const []]
 | 
					**assetIds** | **List<String>** |  | [default to const []]
 | 
				
			||||||
**expiredAt** | **String** |  | [optional] 
 | 
					**expiresAt** | **String** |  | [optional] 
 | 
				
			||||||
**allowUpload** | **bool** |  | [optional] 
 | 
					**allowUpload** | **bool** |  | [optional] 
 | 
				
			||||||
**allowDownload** | **bool** |  | [optional] 
 | 
					**allowDownload** | **bool** |  | [optional] 
 | 
				
			||||||
**showExif** | **bool** |  | [optional] 
 | 
					**showExif** | **bool** |  | [optional] 
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										3
									
								
								mobile/openapi/doc/EditSharedLinkDto.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										3
									
								
								mobile/openapi/doc/EditSharedLinkDto.md
									
									
									
										generated
									
									
									
								
							@ -9,11 +9,10 @@ import 'package:openapi/api.dart';
 | 
				
			|||||||
Name | Type | Description | Notes
 | 
					Name | Type | Description | Notes
 | 
				
			||||||
------------ | ------------- | ------------- | -------------
 | 
					------------ | ------------- | ------------- | -------------
 | 
				
			||||||
**description** | **String** |  | [optional] 
 | 
					**description** | **String** |  | [optional] 
 | 
				
			||||||
**expiredAt** | **String** |  | [optional] 
 | 
					**expiresAt** | **String** |  | [optional] 
 | 
				
			||||||
**allowUpload** | **bool** |  | [optional] 
 | 
					**allowUpload** | **bool** |  | [optional] 
 | 
				
			||||||
**allowDownload** | **bool** |  | [optional] 
 | 
					**allowDownload** | **bool** |  | [optional] 
 | 
				
			||||||
**showExif** | **bool** |  | [optional] 
 | 
					**showExif** | **bool** |  | [optional] 
 | 
				
			||||||
**isEditExpireTime** | **bool** |  | [optional] 
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 | 
					[[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										9
									
								
								mobile/openapi/doc/ShareApi.md
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										9
									
								
								mobile/openapi/doc/ShareApi.md
									
									
									
										generated
									
									
									
								
							@ -183,7 +183,7 @@ No authorization required
 | 
				
			|||||||
[[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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
# **removeSharedLink**
 | 
					# **removeSharedLink**
 | 
				
			||||||
> String removeSharedLink(id)
 | 
					> removeSharedLink(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -197,8 +197,7 @@ final api_instance = ShareApi();
 | 
				
			|||||||
final id = id_example; // String | 
 | 
					final id = id_example; // String | 
 | 
				
			||||||
 | 
					
 | 
				
			||||||
try {
 | 
					try {
 | 
				
			||||||
    final result = api_instance.removeSharedLink(id);
 | 
					    api_instance.removeSharedLink(id);
 | 
				
			||||||
    print(result);
 | 
					 | 
				
			||||||
} catch (e) {
 | 
					} catch (e) {
 | 
				
			||||||
    print('Exception when calling ShareApi->removeSharedLink: $e\n');
 | 
					    print('Exception when calling ShareApi->removeSharedLink: $e\n');
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -212,7 +211,7 @@ Name | Type | Description  | Notes
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### Return type
 | 
					### Return type
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**String**
 | 
					void (empty response body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### Authorization
 | 
					### Authorization
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -221,7 +220,7 @@ No authorization required
 | 
				
			|||||||
### HTTP request headers
 | 
					### HTTP request headers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 - **Content-Type**: Not defined
 | 
					 - **Content-Type**: Not defined
 | 
				
			||||||
 - **Accept**: application/json
 | 
					 - **Accept**: Not defined
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[[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)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										10
									
								
								mobile/openapi/lib/api/share_api.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										10
									
								
								mobile/openapi/lib/api/share_api.dart
									
									
									
										generated
									
									
									
								
							@ -255,18 +255,10 @@ class ShareApi {
 | 
				
			|||||||
  /// Parameters:
 | 
					  /// Parameters:
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  /// * [String] id (required):
 | 
					  /// * [String] id (required):
 | 
				
			||||||
  Future<String?> removeSharedLink(String id,) async {
 | 
					  Future<void> removeSharedLink(String id,) async {
 | 
				
			||||||
    final response = await removeSharedLinkWithHttpInfo(id,);
 | 
					    final response = await removeSharedLinkWithHttpInfo(id,);
 | 
				
			||||||
    if (response.statusCode >= HttpStatus.badRequest) {
 | 
					    if (response.statusCode >= HttpStatus.badRequest) {
 | 
				
			||||||
      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
					      throw ApiException(response.statusCode, await _decodeBodyBytes(response));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    // When a remote server returns no body with a status of 204, we shall not decode it.
 | 
					 | 
				
			||||||
    // At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
 | 
					 | 
				
			||||||
    // FormatException when trying to decode an empty string.
 | 
					 | 
				
			||||||
    if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
 | 
					 | 
				
			||||||
      return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'String',) as String;
 | 
					 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return null;
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@ class CreateAlbumShareLinkDto {
 | 
				
			|||||||
  /// Returns a new [CreateAlbumShareLinkDto] instance.
 | 
					  /// Returns a new [CreateAlbumShareLinkDto] instance.
 | 
				
			||||||
  CreateAlbumShareLinkDto({
 | 
					  CreateAlbumShareLinkDto({
 | 
				
			||||||
    required this.albumId,
 | 
					    required this.albumId,
 | 
				
			||||||
    this.expiredAt,
 | 
					    this.expiresAt,
 | 
				
			||||||
    this.allowUpload,
 | 
					    this.allowUpload,
 | 
				
			||||||
    this.allowDownload,
 | 
					    this.allowDownload,
 | 
				
			||||||
    this.showExif,
 | 
					    this.showExif,
 | 
				
			||||||
@ -29,7 +29,7 @@ class CreateAlbumShareLinkDto {
 | 
				
			|||||||
  /// source code must fall back to having a nullable type.
 | 
					  /// source code must fall back to having a nullable type.
 | 
				
			||||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
					  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  String? expiredAt;
 | 
					  String? expiresAt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
					  /// Please note: This property should have been non-nullable! Since the specification file
 | 
				
			||||||
@ -66,7 +66,7 @@ class CreateAlbumShareLinkDto {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  bool operator ==(Object other) => identical(this, other) || other is CreateAlbumShareLinkDto &&
 | 
					  bool operator ==(Object other) => identical(this, other) || other is CreateAlbumShareLinkDto &&
 | 
				
			||||||
     other.albumId == albumId &&
 | 
					     other.albumId == albumId &&
 | 
				
			||||||
     other.expiredAt == expiredAt &&
 | 
					     other.expiresAt == expiresAt &&
 | 
				
			||||||
     other.allowUpload == allowUpload &&
 | 
					     other.allowUpload == allowUpload &&
 | 
				
			||||||
     other.allowDownload == allowDownload &&
 | 
					     other.allowDownload == allowDownload &&
 | 
				
			||||||
     other.showExif == showExif &&
 | 
					     other.showExif == showExif &&
 | 
				
			||||||
@ -76,22 +76,22 @@ class CreateAlbumShareLinkDto {
 | 
				
			|||||||
  int get hashCode =>
 | 
					  int get hashCode =>
 | 
				
			||||||
    // ignore: unnecessary_parenthesis
 | 
					    // ignore: unnecessary_parenthesis
 | 
				
			||||||
    (albumId.hashCode) +
 | 
					    (albumId.hashCode) +
 | 
				
			||||||
    (expiredAt == null ? 0 : expiredAt!.hashCode) +
 | 
					    (expiresAt == null ? 0 : expiresAt!.hashCode) +
 | 
				
			||||||
    (allowUpload == null ? 0 : allowUpload!.hashCode) +
 | 
					    (allowUpload == null ? 0 : allowUpload!.hashCode) +
 | 
				
			||||||
    (allowDownload == null ? 0 : allowDownload!.hashCode) +
 | 
					    (allowDownload == null ? 0 : allowDownload!.hashCode) +
 | 
				
			||||||
    (showExif == null ? 0 : showExif!.hashCode) +
 | 
					    (showExif == null ? 0 : showExif!.hashCode) +
 | 
				
			||||||
    (description == null ? 0 : description!.hashCode);
 | 
					    (description == null ? 0 : description!.hashCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  String toString() => 'CreateAlbumShareLinkDto[albumId=$albumId, expiredAt=$expiredAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif, description=$description]';
 | 
					  String toString() => 'CreateAlbumShareLinkDto[albumId=$albumId, expiresAt=$expiresAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif, description=$description]';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Map<String, dynamic> toJson() {
 | 
					  Map<String, dynamic> toJson() {
 | 
				
			||||||
    final json = <String, dynamic>{};
 | 
					    final json = <String, dynamic>{};
 | 
				
			||||||
      json[r'albumId'] = this.albumId;
 | 
					      json[r'albumId'] = this.albumId;
 | 
				
			||||||
    if (this.expiredAt != null) {
 | 
					    if (this.expiresAt != null) {
 | 
				
			||||||
      json[r'expiredAt'] = this.expiredAt;
 | 
					      json[r'expiresAt'] = this.expiresAt;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // json[r'expiredAt'] = null;
 | 
					      // json[r'expiresAt'] = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.allowUpload != null) {
 | 
					    if (this.allowUpload != null) {
 | 
				
			||||||
      json[r'allowUpload'] = this.allowUpload;
 | 
					      json[r'allowUpload'] = this.allowUpload;
 | 
				
			||||||
@ -136,7 +136,7 @@ class CreateAlbumShareLinkDto {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      return CreateAlbumShareLinkDto(
 | 
					      return CreateAlbumShareLinkDto(
 | 
				
			||||||
        albumId: mapValueOfType<String>(json, r'albumId')!,
 | 
					        albumId: mapValueOfType<String>(json, r'albumId')!,
 | 
				
			||||||
        expiredAt: mapValueOfType<String>(json, r'expiredAt'),
 | 
					        expiresAt: mapValueOfType<String>(json, r'expiresAt'),
 | 
				
			||||||
        allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
 | 
					        allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
 | 
				
			||||||
        allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
 | 
					        allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
 | 
				
			||||||
        showExif: mapValueOfType<bool>(json, r'showExif'),
 | 
					        showExif: mapValueOfType<bool>(json, r'showExif'),
 | 
				
			||||||
 | 
				
			|||||||
@ -14,7 +14,7 @@ class CreateAssetsShareLinkDto {
 | 
				
			|||||||
  /// Returns a new [CreateAssetsShareLinkDto] instance.
 | 
					  /// Returns a new [CreateAssetsShareLinkDto] instance.
 | 
				
			||||||
  CreateAssetsShareLinkDto({
 | 
					  CreateAssetsShareLinkDto({
 | 
				
			||||||
    this.assetIds = const [],
 | 
					    this.assetIds = const [],
 | 
				
			||||||
    this.expiredAt,
 | 
					    this.expiresAt,
 | 
				
			||||||
    this.allowUpload,
 | 
					    this.allowUpload,
 | 
				
			||||||
    this.allowDownload,
 | 
					    this.allowDownload,
 | 
				
			||||||
    this.showExif,
 | 
					    this.showExif,
 | 
				
			||||||
@ -29,7 +29,7 @@ class CreateAssetsShareLinkDto {
 | 
				
			|||||||
  /// source code must fall back to having a nullable type.
 | 
					  /// source code must fall back to having a nullable type.
 | 
				
			||||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
					  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  String? expiredAt;
 | 
					  String? expiresAt;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
					  /// Please note: This property should have been non-nullable! Since the specification file
 | 
				
			||||||
@ -66,7 +66,7 @@ class CreateAssetsShareLinkDto {
 | 
				
			|||||||
  @override
 | 
					  @override
 | 
				
			||||||
  bool operator ==(Object other) => identical(this, other) || other is CreateAssetsShareLinkDto &&
 | 
					  bool operator ==(Object other) => identical(this, other) || other is CreateAssetsShareLinkDto &&
 | 
				
			||||||
     other.assetIds == assetIds &&
 | 
					     other.assetIds == assetIds &&
 | 
				
			||||||
     other.expiredAt == expiredAt &&
 | 
					     other.expiresAt == expiresAt &&
 | 
				
			||||||
     other.allowUpload == allowUpload &&
 | 
					     other.allowUpload == allowUpload &&
 | 
				
			||||||
     other.allowDownload == allowDownload &&
 | 
					     other.allowDownload == allowDownload &&
 | 
				
			||||||
     other.showExif == showExif &&
 | 
					     other.showExif == showExif &&
 | 
				
			||||||
@ -76,22 +76,22 @@ class CreateAssetsShareLinkDto {
 | 
				
			|||||||
  int get hashCode =>
 | 
					  int get hashCode =>
 | 
				
			||||||
    // ignore: unnecessary_parenthesis
 | 
					    // ignore: unnecessary_parenthesis
 | 
				
			||||||
    (assetIds.hashCode) +
 | 
					    (assetIds.hashCode) +
 | 
				
			||||||
    (expiredAt == null ? 0 : expiredAt!.hashCode) +
 | 
					    (expiresAt == null ? 0 : expiresAt!.hashCode) +
 | 
				
			||||||
    (allowUpload == null ? 0 : allowUpload!.hashCode) +
 | 
					    (allowUpload == null ? 0 : allowUpload!.hashCode) +
 | 
				
			||||||
    (allowDownload == null ? 0 : allowDownload!.hashCode) +
 | 
					    (allowDownload == null ? 0 : allowDownload!.hashCode) +
 | 
				
			||||||
    (showExif == null ? 0 : showExif!.hashCode) +
 | 
					    (showExif == null ? 0 : showExif!.hashCode) +
 | 
				
			||||||
    (description == null ? 0 : description!.hashCode);
 | 
					    (description == null ? 0 : description!.hashCode);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  String toString() => 'CreateAssetsShareLinkDto[assetIds=$assetIds, expiredAt=$expiredAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif, description=$description]';
 | 
					  String toString() => 'CreateAssetsShareLinkDto[assetIds=$assetIds, expiresAt=$expiresAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif, description=$description]';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Map<String, dynamic> toJson() {
 | 
					  Map<String, dynamic> toJson() {
 | 
				
			||||||
    final json = <String, dynamic>{};
 | 
					    final json = <String, dynamic>{};
 | 
				
			||||||
      json[r'assetIds'] = this.assetIds;
 | 
					      json[r'assetIds'] = this.assetIds;
 | 
				
			||||||
    if (this.expiredAt != null) {
 | 
					    if (this.expiresAt != null) {
 | 
				
			||||||
      json[r'expiredAt'] = this.expiredAt;
 | 
					      json[r'expiresAt'] = this.expiresAt;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // json[r'expiredAt'] = null;
 | 
					      // json[r'expiresAt'] = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.allowUpload != null) {
 | 
					    if (this.allowUpload != null) {
 | 
				
			||||||
      json[r'allowUpload'] = this.allowUpload;
 | 
					      json[r'allowUpload'] = this.allowUpload;
 | 
				
			||||||
@ -138,7 +138,7 @@ class CreateAssetsShareLinkDto {
 | 
				
			|||||||
        assetIds: json[r'assetIds'] is List
 | 
					        assetIds: json[r'assetIds'] is List
 | 
				
			||||||
            ? (json[r'assetIds'] as List).cast<String>()
 | 
					            ? (json[r'assetIds'] as List).cast<String>()
 | 
				
			||||||
            : const [],
 | 
					            : const [],
 | 
				
			||||||
        expiredAt: mapValueOfType<String>(json, r'expiredAt'),
 | 
					        expiresAt: mapValueOfType<String>(json, r'expiresAt'),
 | 
				
			||||||
        allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
 | 
					        allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
 | 
				
			||||||
        allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
 | 
					        allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
 | 
				
			||||||
        showExif: mapValueOfType<bool>(json, r'showExif'),
 | 
					        showExif: mapValueOfType<bool>(json, r'showExif'),
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										45
									
								
								mobile/openapi/lib/model/edit_shared_link_dto.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										45
									
								
								mobile/openapi/lib/model/edit_shared_link_dto.dart
									
									
									
										generated
									
									
									
								
							@ -14,11 +14,10 @@ class EditSharedLinkDto {
 | 
				
			|||||||
  /// Returns a new [EditSharedLinkDto] instance.
 | 
					  /// Returns a new [EditSharedLinkDto] instance.
 | 
				
			||||||
  EditSharedLinkDto({
 | 
					  EditSharedLinkDto({
 | 
				
			||||||
    this.description,
 | 
					    this.description,
 | 
				
			||||||
    this.expiredAt,
 | 
					    this.expiresAt,
 | 
				
			||||||
    this.allowUpload,
 | 
					    this.allowUpload,
 | 
				
			||||||
    this.allowDownload,
 | 
					    this.allowDownload,
 | 
				
			||||||
    this.showExif,
 | 
					    this.showExif,
 | 
				
			||||||
    this.isEditExpireTime,
 | 
					 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
@ -29,13 +28,7 @@ class EditSharedLinkDto {
 | 
				
			|||||||
  ///
 | 
					  ///
 | 
				
			||||||
  String? description;
 | 
					  String? description;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ///
 | 
					  String? expiresAt;
 | 
				
			||||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
					 | 
				
			||||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
					 | 
				
			||||||
  /// source code must fall back to having a nullable type.
 | 
					 | 
				
			||||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
					 | 
				
			||||||
  ///
 | 
					 | 
				
			||||||
  String? expiredAt;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ///
 | 
					  ///
 | 
				
			||||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
					  /// Please note: This property should have been non-nullable! Since the specification file
 | 
				
			||||||
@ -61,35 +54,25 @@ class EditSharedLinkDto {
 | 
				
			|||||||
  ///
 | 
					  ///
 | 
				
			||||||
  bool? showExif;
 | 
					  bool? showExif;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  ///
 | 
					 | 
				
			||||||
  /// Please note: This property should have been non-nullable! Since the specification file
 | 
					 | 
				
			||||||
  /// does not include a default value (using the "default:" property), however, the generated
 | 
					 | 
				
			||||||
  /// source code must fall back to having a nullable type.
 | 
					 | 
				
			||||||
  /// Consider adding a "default:" property in the specification file to hide this note.
 | 
					 | 
				
			||||||
  ///
 | 
					 | 
				
			||||||
  bool? isEditExpireTime;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  bool operator ==(Object other) => identical(this, other) || other is EditSharedLinkDto &&
 | 
					  bool operator ==(Object other) => identical(this, other) || other is EditSharedLinkDto &&
 | 
				
			||||||
     other.description == description &&
 | 
					     other.description == description &&
 | 
				
			||||||
     other.expiredAt == expiredAt &&
 | 
					     other.expiresAt == expiresAt &&
 | 
				
			||||||
     other.allowUpload == allowUpload &&
 | 
					     other.allowUpload == allowUpload &&
 | 
				
			||||||
     other.allowDownload == allowDownload &&
 | 
					     other.allowDownload == allowDownload &&
 | 
				
			||||||
     other.showExif == showExif &&
 | 
					     other.showExif == showExif;
 | 
				
			||||||
     other.isEditExpireTime == isEditExpireTime;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  int get hashCode =>
 | 
					  int get hashCode =>
 | 
				
			||||||
    // ignore: unnecessary_parenthesis
 | 
					    // ignore: unnecessary_parenthesis
 | 
				
			||||||
    (description == null ? 0 : description!.hashCode) +
 | 
					    (description == null ? 0 : description!.hashCode) +
 | 
				
			||||||
    (expiredAt == null ? 0 : expiredAt!.hashCode) +
 | 
					    (expiresAt == null ? 0 : expiresAt!.hashCode) +
 | 
				
			||||||
    (allowUpload == null ? 0 : allowUpload!.hashCode) +
 | 
					    (allowUpload == null ? 0 : allowUpload!.hashCode) +
 | 
				
			||||||
    (allowDownload == null ? 0 : allowDownload!.hashCode) +
 | 
					    (allowDownload == null ? 0 : allowDownload!.hashCode) +
 | 
				
			||||||
    (showExif == null ? 0 : showExif!.hashCode) +
 | 
					    (showExif == null ? 0 : showExif!.hashCode);
 | 
				
			||||||
    (isEditExpireTime == null ? 0 : isEditExpireTime!.hashCode);
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @override
 | 
					  @override
 | 
				
			||||||
  String toString() => 'EditSharedLinkDto[description=$description, expiredAt=$expiredAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif, isEditExpireTime=$isEditExpireTime]';
 | 
					  String toString() => 'EditSharedLinkDto[description=$description, expiresAt=$expiresAt, allowUpload=$allowUpload, allowDownload=$allowDownload, showExif=$showExif]';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Map<String, dynamic> toJson() {
 | 
					  Map<String, dynamic> toJson() {
 | 
				
			||||||
    final json = <String, dynamic>{};
 | 
					    final json = <String, dynamic>{};
 | 
				
			||||||
@ -98,10 +81,10 @@ class EditSharedLinkDto {
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // json[r'description'] = null;
 | 
					      // json[r'description'] = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.expiredAt != null) {
 | 
					    if (this.expiresAt != null) {
 | 
				
			||||||
      json[r'expiredAt'] = this.expiredAt;
 | 
					      json[r'expiresAt'] = this.expiresAt;
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // json[r'expiredAt'] = null;
 | 
					      // json[r'expiresAt'] = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.allowUpload != null) {
 | 
					    if (this.allowUpload != null) {
 | 
				
			||||||
      json[r'allowUpload'] = this.allowUpload;
 | 
					      json[r'allowUpload'] = this.allowUpload;
 | 
				
			||||||
@ -118,11 +101,6 @@ class EditSharedLinkDto {
 | 
				
			|||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // json[r'showExif'] = null;
 | 
					      // json[r'showExif'] = null;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    if (this.isEditExpireTime != null) {
 | 
					 | 
				
			||||||
      json[r'isEditExpireTime'] = this.isEditExpireTime;
 | 
					 | 
				
			||||||
    } else {
 | 
					 | 
				
			||||||
      // json[r'isEditExpireTime'] = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
    return json;
 | 
					    return json;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -146,11 +124,10 @@ class EditSharedLinkDto {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
      return EditSharedLinkDto(
 | 
					      return EditSharedLinkDto(
 | 
				
			||||||
        description: mapValueOfType<String>(json, r'description'),
 | 
					        description: mapValueOfType<String>(json, r'description'),
 | 
				
			||||||
        expiredAt: mapValueOfType<String>(json, r'expiredAt'),
 | 
					        expiresAt: mapValueOfType<String>(json, r'expiresAt'),
 | 
				
			||||||
        allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
 | 
					        allowUpload: mapValueOfType<bool>(json, r'allowUpload'),
 | 
				
			||||||
        allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
 | 
					        allowDownload: mapValueOfType<bool>(json, r'allowDownload'),
 | 
				
			||||||
        showExif: mapValueOfType<bool>(json, r'showExif'),
 | 
					        showExif: mapValueOfType<bool>(json, r'showExif'),
 | 
				
			||||||
        isEditExpireTime: mapValueOfType<bool>(json, r'isEditExpireTime'),
 | 
					 | 
				
			||||||
      );
 | 
					      );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return null;
 | 
					    return null;
 | 
				
			||||||
 | 
				
			|||||||
@ -21,8 +21,8 @@ void main() {
 | 
				
			|||||||
      // TODO
 | 
					      // TODO
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // String expiredAt
 | 
					    // String expiresAt
 | 
				
			||||||
    test('to test the property `expiredAt`', () async {
 | 
					    test('to test the property `expiresAt`', () async {
 | 
				
			||||||
      // TODO
 | 
					      // TODO
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -21,8 +21,8 @@ void main() {
 | 
				
			|||||||
      // TODO
 | 
					      // TODO
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // String expiredAt
 | 
					    // String expiresAt
 | 
				
			||||||
    test('to test the property `expiredAt`', () async {
 | 
					    test('to test the property `expiresAt`', () async {
 | 
				
			||||||
      // TODO
 | 
					      // TODO
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -21,8 +21,8 @@ void main() {
 | 
				
			|||||||
      // TODO
 | 
					      // TODO
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // String expiredAt
 | 
					    // String expiresAt
 | 
				
			||||||
    test('to test the property `expiredAt`', () async {
 | 
					    test('to test the property `expiresAt`', () async {
 | 
				
			||||||
      // TODO
 | 
					      // TODO
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -41,11 +41,6 @@ void main() {
 | 
				
			|||||||
      // TODO
 | 
					      // TODO
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    // bool isEditExpireTime
 | 
					 | 
				
			||||||
    test('to test the property `isEditExpireTime`', () async {
 | 
					 | 
				
			||||||
      // TODO
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										2
									
								
								mobile/openapi/test/share_api_test.dart
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										2
									
								
								mobile/openapi/test/share_api_test.dart
									
									
									
										generated
									
									
									
								
							@ -47,7 +47,7 @@ void main() {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    // 
 | 
					    // 
 | 
				
			||||||
    //
 | 
					    //
 | 
				
			||||||
    //Future<String> removeSharedLink(String id) async
 | 
					    //Future removeSharedLink(String id) async
 | 
				
			||||||
    test('test removeSharedLink', () async {
 | 
					    test('test removeSharedLink', () async {
 | 
				
			||||||
      // TODO
 | 
					      // TODO
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ import { RemoveAssetsDto } from './dto/remove-assets.dto';
 | 
				
			|||||||
import { UpdateAlbumDto } from './dto/update-album.dto';
 | 
					import { UpdateAlbumDto } from './dto/update-album.dto';
 | 
				
			||||||
import { GetAlbumsDto } from './dto/get-albums.dto';
 | 
					import { GetAlbumsDto } from './dto/get-albums.dto';
 | 
				
			||||||
import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
 | 
					import { ApiBearerAuth, ApiTags } from '@nestjs/swagger';
 | 
				
			||||||
import { AlbumResponseDto } from './response-dto/album-response.dto';
 | 
					import { AlbumResponseDto } from '@app/domain';
 | 
				
			||||||
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
 | 
					import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
 | 
				
			||||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
 | 
					import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
 | 
				
			||||||
import { Response as Res } from 'express';
 | 
					import { Response as Res } from 'express';
 | 
				
			||||||
 | 
				
			|||||||
@ -6,7 +6,6 @@ import { AlbumEntity, AssetAlbumEntity, UserAlbumEntity } from '@app/infra';
 | 
				
			|||||||
import { AlbumRepository, IAlbumRepository } from './album-repository';
 | 
					import { AlbumRepository, IAlbumRepository } from './album-repository';
 | 
				
			||||||
import { DownloadModule } from '../../modules/download/download.module';
 | 
					import { DownloadModule } from '../../modules/download/download.module';
 | 
				
			||||||
import { AssetModule } from '../asset/asset.module';
 | 
					import { AssetModule } from '../asset/asset.module';
 | 
				
			||||||
import { ShareModule } from '../share/share.module';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ALBUM_REPOSITORY_PROVIDER = {
 | 
					const ALBUM_REPOSITORY_PROVIDER = {
 | 
				
			||||||
  provide: IAlbumRepository,
 | 
					  provide: IAlbumRepository,
 | 
				
			||||||
@ -18,7 +17,6 @@ const ALBUM_REPOSITORY_PROVIDER = {
 | 
				
			|||||||
    TypeOrmModule.forFeature([AlbumEntity, AssetAlbumEntity, UserAlbumEntity]),
 | 
					    TypeOrmModule.forFeature([AlbumEntity, AssetAlbumEntity, UserAlbumEntity]),
 | 
				
			||||||
    DownloadModule,
 | 
					    DownloadModule,
 | 
				
			||||||
    forwardRef(() => AssetModule),
 | 
					    forwardRef(() => AssetModule),
 | 
				
			||||||
    ShareModule,
 | 
					 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  controllers: [AlbumController],
 | 
					  controllers: [AlbumController],
 | 
				
			||||||
  providers: [AlbumService, ALBUM_REPOSITORY_PROVIDER],
 | 
					  providers: [AlbumService, ALBUM_REPOSITORY_PROVIDER],
 | 
				
			||||||
 | 
				
			|||||||
@ -2,17 +2,19 @@ import { AlbumService } from './album.service';
 | 
				
			|||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
 | 
					import { AuthUserDto } from '../../decorators/auth-user.decorator';
 | 
				
			||||||
import { BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
 | 
					import { BadRequestException, NotFoundException, ForbiddenException } from '@nestjs/common';
 | 
				
			||||||
import { AlbumEntity } from '@app/infra';
 | 
					import { AlbumEntity } from '@app/infra';
 | 
				
			||||||
import { AlbumResponseDto } from './response-dto/album-response.dto';
 | 
					import { AlbumResponseDto, ICryptoRepository } from '@app/domain';
 | 
				
			||||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
 | 
					import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
 | 
				
			||||||
import { IAlbumRepository } from './album-repository';
 | 
					import { IAlbumRepository } from './album-repository';
 | 
				
			||||||
import { DownloadService } from '../../modules/download/download.service';
 | 
					import { DownloadService } from '../../modules/download/download.service';
 | 
				
			||||||
import { ISharedLinkRepository } from '../share/shared-link.repository';
 | 
					import { ISharedLinkRepository } from '@app/domain';
 | 
				
			||||||
 | 
					import { newCryptoRepositoryMock, newSharedLinkRepositoryMock } from '@app/domain/../test';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('Album service', () => {
 | 
					describe('Album service', () => {
 | 
				
			||||||
  let sut: AlbumService;
 | 
					  let sut: AlbumService;
 | 
				
			||||||
  let albumRepositoryMock: jest.Mocked<IAlbumRepository>;
 | 
					  let albumRepositoryMock: jest.Mocked<IAlbumRepository>;
 | 
				
			||||||
  let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
 | 
					  let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
 | 
				
			||||||
  let downloadServiceMock: jest.Mocked<Partial<DownloadService>>;
 | 
					  let downloadServiceMock: jest.Mocked<Partial<DownloadService>>;
 | 
				
			||||||
 | 
					  let cryptoMock: jest.Mocked<ICryptoRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const authUser: AuthUserDto = Object.freeze({
 | 
					  const authUser: AuthUserDto = Object.freeze({
 | 
				
			||||||
    id: '1111',
 | 
					    id: '1111',
 | 
				
			||||||
@ -129,22 +131,20 @@ describe('Album service', () => {
 | 
				
			|||||||
      getSharedWithUserAlbumCount: jest.fn(),
 | 
					      getSharedWithUserAlbumCount: jest.fn(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sharedLinkRepositoryMock = {
 | 
					    sharedLinkRepositoryMock = newSharedLinkRepositoryMock();
 | 
				
			||||||
      create: jest.fn(),
 | 
					 | 
				
			||||||
      remove: jest.fn(),
 | 
					 | 
				
			||||||
      get: jest.fn(),
 | 
					 | 
				
			||||||
      getById: jest.fn(),
 | 
					 | 
				
			||||||
      getByKey: jest.fn(),
 | 
					 | 
				
			||||||
      save: jest.fn(),
 | 
					 | 
				
			||||||
      hasAssetAccess: jest.fn(),
 | 
					 | 
				
			||||||
      getByIdAndUserId: jest.fn(),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    downloadServiceMock = {
 | 
					    downloadServiceMock = {
 | 
				
			||||||
      downloadArchive: jest.fn(),
 | 
					      downloadArchive: jest.fn(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sut = new AlbumService(albumRepositoryMock, sharedLinkRepositoryMock, downloadServiceMock as DownloadService);
 | 
					    cryptoMock = newCryptoRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sut = new AlbumService(
 | 
				
			||||||
 | 
					      albumRepositoryMock,
 | 
				
			||||||
 | 
					      sharedLinkRepositoryMock,
 | 
				
			||||||
 | 
					      downloadServiceMock as DownloadService,
 | 
				
			||||||
 | 
					      cryptoMock,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  it('creates album', async () => {
 | 
					  it('creates album', async () => {
 | 
				
			||||||
 | 
				
			|||||||
@ -6,16 +6,14 @@ import { AddUsersDto } from './dto/add-users.dto';
 | 
				
			|||||||
import { RemoveAssetsDto } from './dto/remove-assets.dto';
 | 
					import { RemoveAssetsDto } from './dto/remove-assets.dto';
 | 
				
			||||||
import { UpdateAlbumDto } from './dto/update-album.dto';
 | 
					import { UpdateAlbumDto } from './dto/update-album.dto';
 | 
				
			||||||
import { GetAlbumsDto } from './dto/get-albums.dto';
 | 
					import { GetAlbumsDto } from './dto/get-albums.dto';
 | 
				
			||||||
import { AlbumResponseDto, mapAlbum, mapAlbumExcludeAssetInfo } from './response-dto/album-response.dto';
 | 
					import { AlbumResponseDto, mapAlbum, mapAlbumExcludeAssetInfo } from '@app/domain';
 | 
				
			||||||
import { IAlbumRepository } from './album-repository';
 | 
					import { IAlbumRepository } from './album-repository';
 | 
				
			||||||
import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
 | 
					import { AlbumCountResponseDto } from './response-dto/album-count-response.dto';
 | 
				
			||||||
import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
 | 
					import { AddAssetsResponseDto } from './response-dto/add-assets-response.dto';
 | 
				
			||||||
import { AddAssetsDto } from './dto/add-assets.dto';
 | 
					import { AddAssetsDto } from './dto/add-assets.dto';
 | 
				
			||||||
import { DownloadService } from '../../modules/download/download.service';
 | 
					import { DownloadService } from '../../modules/download/download.service';
 | 
				
			||||||
import { DownloadDto } from '../asset/dto/download-library.dto';
 | 
					import { DownloadDto } from '../asset/dto/download-library.dto';
 | 
				
			||||||
import { ShareCore } from '../share/share.core';
 | 
					import { ShareCore, ISharedLinkRepository, mapSharedLink, SharedLinkResponseDto, ICryptoRepository } from '@app/domain';
 | 
				
			||||||
import { ISharedLinkRepository } from '../share/shared-link.repository';
 | 
					 | 
				
			||||||
import { mapSharedLink, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
 | 
					 | 
				
			||||||
import { CreateAlbumShareLinkDto } from './dto/create-album-shared-link.dto';
 | 
					import { CreateAlbumShareLinkDto } from './dto/create-album-shared-link.dto';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -26,10 +24,11 @@ export class AlbumService {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(IAlbumRepository) private _albumRepository: IAlbumRepository,
 | 
					    @Inject(IAlbumRepository) private _albumRepository: IAlbumRepository,
 | 
				
			||||||
    @Inject(ISharedLinkRepository) private sharedLinkRepository: ISharedLinkRepository,
 | 
					    @Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
 | 
				
			||||||
    private downloadService: DownloadService,
 | 
					    private downloadService: DownloadService,
 | 
				
			||||||
 | 
					    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.shareCore = new ShareCore(sharedLinkRepository);
 | 
					    this.shareCore = new ShareCore(sharedLinkRepository, cryptoRepository);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private async _getAlbum({
 | 
					  private async _getAlbum({
 | 
				
			||||||
@ -102,7 +101,7 @@ export class AlbumService {
 | 
				
			|||||||
    const album = await this._getAlbum({ authUser, albumId });
 | 
					    const album = await this._getAlbum({ authUser, albumId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    for (const sharedLink of album.sharedLinks) {
 | 
					    for (const sharedLink of album.sharedLinks) {
 | 
				
			||||||
      await this.shareCore.removeSharedLink(sharedLink.id, authUser.id);
 | 
					      await this.shareCore.remove(sharedLink.id, authUser.id);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    await this._albumRepository.delete(album);
 | 
					    await this._albumRepository.delete(album);
 | 
				
			||||||
@ -203,11 +202,11 @@ export class AlbumService {
 | 
				
			|||||||
  async createAlbumSharedLink(authUser: AuthUserDto, dto: CreateAlbumShareLinkDto): Promise<SharedLinkResponseDto> {
 | 
					  async createAlbumSharedLink(authUser: AuthUserDto, dto: CreateAlbumShareLinkDto): Promise<SharedLinkResponseDto> {
 | 
				
			||||||
    const album = await this._getAlbum({ authUser, albumId: dto.albumId });
 | 
					    const album = await this._getAlbum({ authUser, albumId: dto.albumId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sharedLink = await this.shareCore.createSharedLink(authUser.id, {
 | 
					    const sharedLink = await this.shareCore.create(authUser.id, {
 | 
				
			||||||
      sharedType: SharedLinkType.ALBUM,
 | 
					      type: SharedLinkType.ALBUM,
 | 
				
			||||||
      expiredAt: dto.expiredAt,
 | 
					      expiresAt: dto.expiresAt,
 | 
				
			||||||
      allowUpload: dto.allowUpload,
 | 
					      allowUpload: dto.allowUpload,
 | 
				
			||||||
      album: album,
 | 
					      album,
 | 
				
			||||||
      assets: [],
 | 
					      assets: [],
 | 
				
			||||||
      description: dto.description,
 | 
					      description: dto.description,
 | 
				
			||||||
      allowDownload: dto.allowDownload,
 | 
					      allowDownload: dto.allowDownload,
 | 
				
			||||||
 | 
				
			|||||||
@ -7,7 +7,7 @@ export class CreateAlbumShareLinkDto {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  expiredAt?: string;
 | 
					  expiresAt?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsBoolean()
 | 
					  @IsBoolean()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,5 @@
 | 
				
			|||||||
import { ApiProperty } from '@nestjs/swagger';
 | 
					import { ApiProperty } from '@nestjs/swagger';
 | 
				
			||||||
import { AlbumResponseDto } from './album-response.dto';
 | 
					import { AlbumResponseDto } from '@app/domain';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AddAssetsResponseDto {
 | 
					export class AddAssetsResponseDto {
 | 
				
			||||||
  @ApiProperty({ type: 'integer' })
 | 
					  @ApiProperty({ type: 'integer' })
 | 
				
			||||||
 | 
				
			|||||||
@ -30,7 +30,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 './response-dto/asset-response.dto';
 | 
					import { AssetResponseDto } 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 } from './dto/create-asset.dto';
 | 
					import { CreateAssetDto } from './dto/create-asset.dto';
 | 
				
			||||||
@ -52,7 +52,7 @@ import {
 | 
				
			|||||||
} from '../../constants/download.constant';
 | 
					} from '../../constants/download.constant';
 | 
				
			||||||
import { DownloadFilesDto } from './dto/download-files.dto';
 | 
					import { DownloadFilesDto } from './dto/download-files.dto';
 | 
				
			||||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
 | 
					import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
 | 
				
			||||||
import { SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
 | 
					import { SharedLinkResponseDto } from '@app/domain';
 | 
				
			||||||
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
 | 
					import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
 | 
				
			||||||
import { AssetSearchDto } from './dto/asset-search.dto';
 | 
					import { AssetSearchDto } from './dto/asset-search.dto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -11,7 +11,6 @@ import { DownloadModule } from '../../modules/download/download.module';
 | 
				
			|||||||
import { TagModule } from '../tag/tag.module';
 | 
					import { TagModule } from '../tag/tag.module';
 | 
				
			||||||
import { AlbumModule } from '../album/album.module';
 | 
					import { AlbumModule } from '../album/album.module';
 | 
				
			||||||
import { StorageModule } from '@app/storage';
 | 
					import { StorageModule } from '@app/storage';
 | 
				
			||||||
import { ShareModule } from '../share/share.module';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const ASSET_REPOSITORY_PROVIDER = {
 | 
					const ASSET_REPOSITORY_PROVIDER = {
 | 
				
			||||||
  provide: IAssetRepository,
 | 
					  provide: IAssetRepository,
 | 
				
			||||||
@ -27,7 +26,6 @@ const ASSET_REPOSITORY_PROVIDER = {
 | 
				
			|||||||
    TagModule,
 | 
					    TagModule,
 | 
				
			||||||
    StorageModule,
 | 
					    StorageModule,
 | 
				
			||||||
    forwardRef(() => AlbumModule),
 | 
					    forwardRef(() => AlbumModule),
 | 
				
			||||||
    ShareModule,
 | 
					 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  controllers: [AssetController],
 | 
					  controllers: [AssetController],
 | 
				
			||||||
  providers: [AssetService, BackgroundTaskService, ASSET_REPOSITORY_PROVIDER],
 | 
					  providers: [AssetService, BackgroundTaskService, ASSET_REPOSITORY_PROVIDER],
 | 
				
			||||||
 | 
				
			|||||||
@ -9,11 +9,19 @@ import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto';
 | 
				
			|||||||
import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
 | 
					import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto';
 | 
				
			||||||
import { DownloadService } from '../../modules/download/download.service';
 | 
					import { DownloadService } from '../../modules/download/download.service';
 | 
				
			||||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
 | 
					import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
 | 
				
			||||||
import { IAlbumRepository } from '../album/album-repository';
 | 
					import { AlbumRepository, IAlbumRepository } from '../album/album-repository';
 | 
				
			||||||
import { StorageService } from '@app/storage';
 | 
					import { StorageService } from '@app/storage';
 | 
				
			||||||
import { ISharedLinkRepository } from '../share/shared-link.repository';
 | 
					import { ICryptoRepository, IJobRepository, ISharedLinkRepository } from '@app/domain';
 | 
				
			||||||
import { IJobRepository } from '@app/domain';
 | 
					import {
 | 
				
			||||||
import { newJobRepositoryMock } from '@app/domain/../test';
 | 
					  authStub,
 | 
				
			||||||
 | 
					  newCryptoRepositoryMock,
 | 
				
			||||||
 | 
					  newJobRepositoryMock,
 | 
				
			||||||
 | 
					  newSharedLinkRepositoryMock,
 | 
				
			||||||
 | 
					  sharedLinkResponseStub,
 | 
				
			||||||
 | 
					  sharedLinkStub,
 | 
				
			||||||
 | 
					} from '@app/domain/../test';
 | 
				
			||||||
 | 
					import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
 | 
				
			||||||
 | 
					import { BadRequestException, ForbiddenException } from '@nestjs/common';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe('AssetService', () => {
 | 
					describe('AssetService', () => {
 | 
				
			||||||
  let sui: AssetService;
 | 
					  let sui: AssetService;
 | 
				
			||||||
@ -24,6 +32,7 @@ describe('AssetService', () => {
 | 
				
			|||||||
  let backgroundTaskServiceMock: jest.Mocked<BackgroundTaskService>;
 | 
					  let backgroundTaskServiceMock: jest.Mocked<BackgroundTaskService>;
 | 
				
			||||||
  let storageSeriveMock: jest.Mocked<StorageService>;
 | 
					  let storageSeriveMock: jest.Mocked<StorageService>;
 | 
				
			||||||
  let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
 | 
					  let sharedLinkRepositoryMock: jest.Mocked<ISharedLinkRepository>;
 | 
				
			||||||
 | 
					  let cryptoMock: jest.Mocked<ICryptoRepository>;
 | 
				
			||||||
  let jobMock: jest.Mocked<IJobRepository>;
 | 
					  let jobMock: jest.Mocked<IJobRepository>;
 | 
				
			||||||
  const authUser: AuthUserDto = Object.freeze({
 | 
					  const authUser: AuthUserDto = Object.freeze({
 | 
				
			||||||
    id: 'user_id_1',
 | 
					    id: 'user_id_1',
 | 
				
			||||||
@ -132,22 +141,18 @@ describe('AssetService', () => {
 | 
				
			|||||||
      countByIdAndUser: jest.fn(),
 | 
					      countByIdAndUser: jest.fn(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    albumRepositoryMock = {
 | 
				
			||||||
 | 
					      getSharedWithUserAlbumCount: jest.fn(),
 | 
				
			||||||
 | 
					    } as unknown as jest.Mocked<AlbumRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    downloadServiceMock = {
 | 
					    downloadServiceMock = {
 | 
				
			||||||
      downloadArchive: jest.fn(),
 | 
					      downloadArchive: jest.fn(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sharedLinkRepositoryMock = {
 | 
					    sharedLinkRepositoryMock = newSharedLinkRepositoryMock();
 | 
				
			||||||
      create: jest.fn(),
 | 
					 | 
				
			||||||
      get: jest.fn(),
 | 
					 | 
				
			||||||
      getById: jest.fn(),
 | 
					 | 
				
			||||||
      getByKey: jest.fn(),
 | 
					 | 
				
			||||||
      remove: jest.fn(),
 | 
					 | 
				
			||||||
      save: jest.fn(),
 | 
					 | 
				
			||||||
      hasAssetAccess: jest.fn(),
 | 
					 | 
				
			||||||
      getByIdAndUserId: jest.fn(),
 | 
					 | 
				
			||||||
    };
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    jobMock = newJobRepositoryMock();
 | 
					    jobMock = newJobRepositoryMock();
 | 
				
			||||||
 | 
					    cryptoMock = newCryptoRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    sui = new AssetService(
 | 
					    sui = new AssetService(
 | 
				
			||||||
      assetRepositoryMock,
 | 
					      assetRepositoryMock,
 | 
				
			||||||
@ -158,9 +163,64 @@ describe('AssetService', () => {
 | 
				
			|||||||
      storageSeriveMock,
 | 
					      storageSeriveMock,
 | 
				
			||||||
      sharedLinkRepositoryMock,
 | 
					      sharedLinkRepositoryMock,
 | 
				
			||||||
      jobMock,
 | 
					      jobMock,
 | 
				
			||||||
 | 
					      cryptoMock,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('createAssetsSharedLink', () => {
 | 
				
			||||||
 | 
					    it('should create an individual share link', async () => {
 | 
				
			||||||
 | 
					      const asset1 = _getAsset_1();
 | 
				
			||||||
 | 
					      const dto: CreateAssetsShareLinkDto = { assetIds: [asset1.id] };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      assetRepositoryMock.getById.mockResolvedValue(asset1);
 | 
				
			||||||
 | 
					      assetRepositoryMock.countByIdAndUser.mockResolvedValue(1);
 | 
				
			||||||
 | 
					      sharedLinkRepositoryMock.create.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await expect(sui.createAssetsSharedLink(authStub.user1, dto)).resolves.toEqual(sharedLinkResponseStub.valid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id);
 | 
				
			||||||
 | 
					      expect(assetRepositoryMock.countByIdAndUser).toHaveBeenCalledWith(asset1.id, authStub.user1.id);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('updateAssetsInSharedLink', () => {
 | 
				
			||||||
 | 
					    it('should require a valid shared link', async () => {
 | 
				
			||||||
 | 
					      const asset1 = _getAsset_1();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const authDto = authStub.adminSharedLink;
 | 
				
			||||||
 | 
					      const dto = { assetIds: [asset1.id] };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      assetRepositoryMock.getById.mockResolvedValue(asset1);
 | 
				
			||||||
 | 
					      sharedLinkRepositoryMock.get.mockResolvedValue(null);
 | 
				
			||||||
 | 
					      sharedLinkRepositoryMock.hasAssetAccess.mockResolvedValue(true);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await expect(sui.updateAssetsInSharedLink(authDto, dto)).rejects.toBeInstanceOf(BadRequestException);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id);
 | 
				
			||||||
 | 
					      expect(sharedLinkRepositoryMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
 | 
				
			||||||
 | 
					      expect(sharedLinkRepositoryMock.hasAssetAccess).toHaveBeenCalledWith(authDto.sharedLinkId, asset1.id);
 | 
				
			||||||
 | 
					      expect(sharedLinkRepositoryMock.save).not.toHaveBeenCalled();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should remove assets from a shared link', async () => {
 | 
				
			||||||
 | 
					      const asset1 = _getAsset_1();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const authDto = authStub.adminSharedLink;
 | 
				
			||||||
 | 
					      const dto = { assetIds: [asset1.id] };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      assetRepositoryMock.getById.mockResolvedValue(asset1);
 | 
				
			||||||
 | 
					      sharedLinkRepositoryMock.get.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					      sharedLinkRepositoryMock.hasAssetAccess.mockResolvedValue(true);
 | 
				
			||||||
 | 
					      sharedLinkRepositoryMock.save.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      await expect(sui.updateAssetsInSharedLink(authDto, dto)).resolves.toEqual(sharedLinkResponseStub.valid);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(assetRepositoryMock.getById).toHaveBeenCalledWith(asset1.id);
 | 
				
			||||||
 | 
					      expect(sharedLinkRepositoryMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
 | 
				
			||||||
 | 
					      expect(sharedLinkRepositoryMock.hasAssetAccess).toHaveBeenCalledWith(authDto.sharedLinkId, asset1.id);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  // Currently failing due to calculate checksum from a file
 | 
					  // Currently failing due to calculate checksum from a file
 | 
				
			||||||
  it('create an asset', async () => {
 | 
					  it('create an asset', async () => {
 | 
				
			||||||
    const assetEntity = _getAsset_1();
 | 
					    const assetEntity = _getAsset_1();
 | 
				
			||||||
@ -224,4 +284,14 @@ describe('AssetService', () => {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    expect(result).toEqual(assetCount);
 | 
					    expect(result).toEqual(assetCount);
 | 
				
			||||||
  });
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('checkDownloadAccess', () => {
 | 
				
			||||||
 | 
					    it('should validate download access', async () => {
 | 
				
			||||||
 | 
					      await sui.checkDownloadAccess(authStub.adminSharedLink);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should not allow when user is not allowed to download', async () => {
 | 
				
			||||||
 | 
					      expect(() => sui.checkDownloadAccess(authStub.readonlySharedLink)).toThrow(ForbiddenException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ 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, mapAsset, mapAssetWithoutExif } from './response-dto/asset-response.dto';
 | 
					import { AssetResponseDto, mapAsset, mapAssetWithoutExif } from '@app/domain';
 | 
				
			||||||
import { CreateAssetDto } from './dto/create-asset.dto';
 | 
					import { CreateAssetDto } 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';
 | 
				
			||||||
@ -43,16 +43,16 @@ import { CheckExistingAssetsResponseDto } from './response-dto/check-existing-as
 | 
				
			|||||||
import { UpdateAssetDto } from './dto/update-asset.dto';
 | 
					import { UpdateAssetDto } from './dto/update-asset.dto';
 | 
				
			||||||
import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
 | 
					import { AssetFileUploadResponseDto } from './response-dto/asset-file-upload-response.dto';
 | 
				
			||||||
import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
 | 
					import { BackgroundTaskService } from '../../modules/background-task/background-task.service';
 | 
				
			||||||
import { IJobRepository, JobName } from '@app/domain';
 | 
					import { ICryptoRepository, IJobRepository, JobName } from '@app/domain';
 | 
				
			||||||
import { DownloadService } from '../../modules/download/download.service';
 | 
					import { DownloadService } from '../../modules/download/download.service';
 | 
				
			||||||
import { DownloadDto } from './dto/download-library.dto';
 | 
					import { DownloadDto } from './dto/download-library.dto';
 | 
				
			||||||
import { IAlbumRepository } from '../album/album-repository';
 | 
					import { IAlbumRepository } from '../album/album-repository';
 | 
				
			||||||
import { StorageService } from '@app/storage';
 | 
					import { StorageService } from '@app/storage';
 | 
				
			||||||
import { ShareCore } from '../share/share.core';
 | 
					import { ShareCore } from '@app/domain';
 | 
				
			||||||
import { ISharedLinkRepository } from '../share/shared-link.repository';
 | 
					import { ISharedLinkRepository } from '@app/domain';
 | 
				
			||||||
import { DownloadFilesDto } from './dto/download-files.dto';
 | 
					import { DownloadFilesDto } from './dto/download-files.dto';
 | 
				
			||||||
import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
 | 
					import { CreateAssetsShareLinkDto } from './dto/create-asset-shared-link.dto';
 | 
				
			||||||
import { mapSharedLink, SharedLinkResponseDto } from '../share/response-dto/shared-link-response.dto';
 | 
					import { mapSharedLink, SharedLinkResponseDto } from '@app/domain';
 | 
				
			||||||
import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
 | 
					import { UpdateAssetsToSharedLinkDto } from './dto/add-assets-to-shared-link.dto';
 | 
				
			||||||
import { AssetSearchDto } from './dto/asset-search.dto';
 | 
					import { AssetSearchDto } from './dto/asset-search.dto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -73,8 +73,9 @@ export class AssetService {
 | 
				
			|||||||
    private storageService: StorageService,
 | 
					    private storageService: StorageService,
 | 
				
			||||||
    @Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
 | 
					    @Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
 | 
				
			||||||
    @Inject(IJobRepository) private jobRepository: IJobRepository,
 | 
					    @Inject(IJobRepository) private jobRepository: IJobRepository,
 | 
				
			||||||
 | 
					    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.shareCore = new ShareCore(sharedLinkRepository);
 | 
					    this.shareCore = new ShareCore(sharedLinkRepository, cryptoRepository);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  public async handleUploadedAsset(
 | 
					  public async handleUploadedAsset(
 | 
				
			||||||
@ -669,11 +670,10 @@ export class AssetService {
 | 
				
			|||||||
      // Step 1: Check if asset is part of a public shared
 | 
					      // Step 1: Check if asset is part of a public shared
 | 
				
			||||||
      if (authUser.sharedLinkId) {
 | 
					      if (authUser.sharedLinkId) {
 | 
				
			||||||
        const canAccess = await this.shareCore.hasAssetAccess(authUser.sharedLinkId, assetId);
 | 
					        const canAccess = await this.shareCore.hasAssetAccess(authUser.sharedLinkId, assetId);
 | 
				
			||||||
        if (!canAccess) {
 | 
					        if (canAccess) {
 | 
				
			||||||
          throw new ForbiddenException();
 | 
					          continue;
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      } else {
 | 
				
			||||||
 | 
					 | 
				
			||||||
        // Step 2: Check if user owns asset
 | 
					        // Step 2: Check if user owns asset
 | 
				
			||||||
        if ((await this._assetRepository.countByIdAndUser(assetId, authUser.id)) == 1) {
 | 
					        if ((await this._assetRepository.countByIdAndUser(assetId, authUser.id)) == 1) {
 | 
				
			||||||
          continue;
 | 
					          continue;
 | 
				
			||||||
@ -686,6 +686,8 @@ export class AssetService {
 | 
				
			|||||||
            continue;
 | 
					            continue;
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      throw new ForbiddenException();
 | 
					      throw new ForbiddenException();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@ -703,11 +705,11 @@ export class AssetService {
 | 
				
			|||||||
      assets.push(asset);
 | 
					      assets.push(asset);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sharedLink = await this.shareCore.createSharedLink(authUser.id, {
 | 
					    const sharedLink = await this.shareCore.create(authUser.id, {
 | 
				
			||||||
      sharedType: SharedLinkType.INDIVIDUAL,
 | 
					      type: SharedLinkType.INDIVIDUAL,
 | 
				
			||||||
      expiredAt: dto.expiredAt,
 | 
					      expiresAt: dto.expiresAt,
 | 
				
			||||||
      allowUpload: dto.allowUpload,
 | 
					      allowUpload: dto.allowUpload,
 | 
				
			||||||
      assets: assets,
 | 
					      assets,
 | 
				
			||||||
      description: dto.description,
 | 
					      description: dto.description,
 | 
				
			||||||
      allowDownload: dto.allowDownload,
 | 
					      allowDownload: dto.allowDownload,
 | 
				
			||||||
      showExif: dto.showExif,
 | 
					      showExif: dto.showExif,
 | 
				
			||||||
@ -720,15 +722,19 @@ export class AssetService {
 | 
				
			|||||||
    authUser: AuthUserDto,
 | 
					    authUser: AuthUserDto,
 | 
				
			||||||
    dto: UpdateAssetsToSharedLinkDto,
 | 
					    dto: UpdateAssetsToSharedLinkDto,
 | 
				
			||||||
  ): Promise<SharedLinkResponseDto> {
 | 
					  ): Promise<SharedLinkResponseDto> {
 | 
				
			||||||
    if (!authUser.sharedLinkId) throw new ForbiddenException();
 | 
					    if (!authUser.sharedLinkId) {
 | 
				
			||||||
 | 
					      throw new ForbiddenException();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const assets = [];
 | 
					    const assets = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    await this.checkAssetsAccess(authUser, dto.assetIds);
 | 
				
			||||||
    for (const assetId of dto.assetIds) {
 | 
					    for (const assetId of dto.assetIds) {
 | 
				
			||||||
      const asset = await this._assetRepository.getById(assetId);
 | 
					      const asset = await this._assetRepository.getById(assetId);
 | 
				
			||||||
      assets.push(asset);
 | 
					      assets.push(asset);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const updatedLink = await this.shareCore.updateAssetsInSharedLink(authUser.sharedLinkId, assets);
 | 
					    const updatedLink = await this.shareCore.updateAssets(authUser.id, authUser.sharedLinkId, assets);
 | 
				
			||||||
    return mapSharedLink(updatedLink);
 | 
					    return mapSharedLink(updatedLink);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -19,7 +19,7 @@ export class CreateAssetsShareLinkDto {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @IsString()
 | 
					  @IsString()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  expiredAt?: string;
 | 
					  expiresAt?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsBoolean()
 | 
					  @IsBoolean()
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
 | 
				
			|||||||
@ -1,101 +0,0 @@
 | 
				
			|||||||
import { SharedLinkEntity } from '@app/infra';
 | 
					 | 
				
			||||||
import { CreateSharedLinkDto } from './dto/create-shared-link.dto';
 | 
					 | 
				
			||||||
import { ISharedLinkRepository } from './shared-link.repository';
 | 
					 | 
				
			||||||
import crypto from 'node:crypto';
 | 
					 | 
				
			||||||
import { BadRequestException, ForbiddenException, InternalServerErrorException, Logger } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { AssetEntity } from '@app/infra';
 | 
					 | 
				
			||||||
import { EditSharedLinkDto } from './dto/edit-shared-link.dto';
 | 
					 | 
				
			||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class ShareCore {
 | 
					 | 
				
			||||||
  readonly logger = new Logger(ShareCore.name);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  constructor(private sharedLinkRepository: ISharedLinkRepository) {}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async createSharedLink(userId: string, dto: CreateSharedLinkDto): Promise<SharedLinkEntity> {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const sharedLink = new SharedLinkEntity();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      sharedLink.key = Buffer.from(crypto.randomBytes(50));
 | 
					 | 
				
			||||||
      sharedLink.description = dto.description;
 | 
					 | 
				
			||||||
      sharedLink.userId = userId;
 | 
					 | 
				
			||||||
      sharedLink.createdAt = new Date().toISOString();
 | 
					 | 
				
			||||||
      sharedLink.expiresAt = dto.expiredAt ?? null;
 | 
					 | 
				
			||||||
      sharedLink.type = dto.sharedType;
 | 
					 | 
				
			||||||
      sharedLink.assets = dto.assets;
 | 
					 | 
				
			||||||
      sharedLink.album = dto.album;
 | 
					 | 
				
			||||||
      sharedLink.allowUpload = dto.allowUpload ?? false;
 | 
					 | 
				
			||||||
      sharedLink.allowDownload = dto.allowDownload ?? true;
 | 
					 | 
				
			||||||
      sharedLink.showExif = dto.showExif ?? true;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return this.sharedLinkRepository.create(sharedLink);
 | 
					 | 
				
			||||||
    } catch (error: any) {
 | 
					 | 
				
			||||||
      this.logger.error(error, error.stack);
 | 
					 | 
				
			||||||
      throw new InternalServerErrorException('failed to create shared link');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getSharedLinks(userId: string): Promise<SharedLinkEntity[]> {
 | 
					 | 
				
			||||||
    return this.sharedLinkRepository.get(userId);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async removeSharedLink(id: string, userId: string): Promise<SharedLinkEntity> {
 | 
					 | 
				
			||||||
    const link = await this.sharedLinkRepository.getByIdAndUserId(id, userId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!link) {
 | 
					 | 
				
			||||||
      throw new BadRequestException('Shared link not found');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.remove(link);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getSharedLinkById(id: string): Promise<SharedLinkEntity | null> {
 | 
					 | 
				
			||||||
    return this.sharedLinkRepository.getById(id);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  getSharedLinkByKey(key: string): Promise<SharedLinkEntity | null> {
 | 
					 | 
				
			||||||
    return this.sharedLinkRepository.getByKey(key);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async updateAssetsInSharedLink(sharedLinkId: string, assets: AssetEntity[]) {
 | 
					 | 
				
			||||||
    const link = await this.getSharedLinkById(sharedLinkId);
 | 
					 | 
				
			||||||
    if (!link) {
 | 
					 | 
				
			||||||
      throw new BadRequestException('Shared link not found');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    link.assets = assets;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.save(link);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async updateSharedLink(id: string, userId: string, dto: EditSharedLinkDto): Promise<SharedLinkEntity> {
 | 
					 | 
				
			||||||
    const link = await this.sharedLinkRepository.getByIdAndUserId(id, userId);
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (!link) {
 | 
					 | 
				
			||||||
      throw new BadRequestException('Shared link not found');
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    link.description = dto.description ?? link.description;
 | 
					 | 
				
			||||||
    link.allowUpload = dto.allowUpload ?? link.allowUpload;
 | 
					 | 
				
			||||||
    link.allowDownload = dto.allowDownload ?? link.allowDownload;
 | 
					 | 
				
			||||||
    link.showExif = dto.showExif ?? link.showExif;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (dto.isEditExpireTime && dto.expiredAt) {
 | 
					 | 
				
			||||||
      link.expiresAt = dto.expiredAt;
 | 
					 | 
				
			||||||
    } else if (dto.isEditExpireTime && !dto.expiredAt) {
 | 
					 | 
				
			||||||
      link.expiresAt = null;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.save(link);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async hasAssetAccess(id: string, assetId: string): Promise<boolean> {
 | 
					 | 
				
			||||||
    return this.sharedLinkRepository.hasAssetAccess(id, assetId);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  checkDownloadAccess(user: AuthUserDto) {
 | 
					 | 
				
			||||||
    if (user.isPublicUser && !user.isAllowDownload) {
 | 
					 | 
				
			||||||
      throw new ForbiddenException();
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -1,19 +0,0 @@
 | 
				
			|||||||
import { Module } from '@nestjs/common';
 | 
					 | 
				
			||||||
import { ShareService } from './share.service';
 | 
					 | 
				
			||||||
import { ShareController } from './share.controller';
 | 
					 | 
				
			||||||
import { SharedLinkEntity } from '@app/infra';
 | 
					 | 
				
			||||||
import { TypeOrmModule } from '@nestjs/typeorm';
 | 
					 | 
				
			||||||
import { SharedLinkRepository, ISharedLinkRepository } from './shared-link.repository';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const SHARED_LINK_REPOSITORY_PROVIDER = {
 | 
					 | 
				
			||||||
  provide: ISharedLinkRepository,
 | 
					 | 
				
			||||||
  useClass: SharedLinkRepository,
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
@Module({
 | 
					 | 
				
			||||||
  imports: [TypeOrmModule.forFeature([SharedLinkEntity])],
 | 
					 | 
				
			||||||
  controllers: [ShareController],
 | 
					 | 
				
			||||||
  providers: [ShareService, SHARED_LINK_REPOSITORY_PROVIDER],
 | 
					 | 
				
			||||||
  exports: [SHARED_LINK_REPOSITORY_PROVIDER, ShareService],
 | 
					 | 
				
			||||||
})
 | 
					 | 
				
			||||||
export class ShareModule {}
 | 
					 | 
				
			||||||
@ -1,137 +0,0 @@
 | 
				
			|||||||
import { SharedLinkEntity } from '@app/infra';
 | 
					 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					 | 
				
			||||||
import { Repository } from 'typeorm';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
import { Logger } from '@nestjs/common';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export interface ISharedLinkRepository {
 | 
					 | 
				
			||||||
  get(userId: string): Promise<SharedLinkEntity[]>;
 | 
					 | 
				
			||||||
  getById(id: string): Promise<SharedLinkEntity | null>;
 | 
					 | 
				
			||||||
  getByIdAndUserId(id: string, userId: string): Promise<SharedLinkEntity | null>;
 | 
					 | 
				
			||||||
  getByKey(key: string): Promise<SharedLinkEntity | null>;
 | 
					 | 
				
			||||||
  create(payload: SharedLinkEntity): Promise<SharedLinkEntity>;
 | 
					 | 
				
			||||||
  remove(entity: SharedLinkEntity): Promise<SharedLinkEntity>;
 | 
					 | 
				
			||||||
  save(entity: SharedLinkEntity): Promise<SharedLinkEntity>;
 | 
					 | 
				
			||||||
  hasAssetAccess(id: string, assetId: string): Promise<boolean>;
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export const ISharedLinkRepository = 'ISharedLinkRepository';
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
export class SharedLinkRepository implements ISharedLinkRepository {
 | 
					 | 
				
			||||||
  readonly logger = new Logger(SharedLinkRepository.name);
 | 
					 | 
				
			||||||
  constructor(
 | 
					 | 
				
			||||||
    @InjectRepository(SharedLinkEntity)
 | 
					 | 
				
			||||||
    private readonly sharedLinkRepository: Repository<SharedLinkEntity>,
 | 
					 | 
				
			||||||
  ) {}
 | 
					 | 
				
			||||||
  async getByIdAndUserId(id: string, userId: string): Promise<SharedLinkEntity | null> {
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.findOne({
 | 
					 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        userId: userId,
 | 
					 | 
				
			||||||
        id: id,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      order: {
 | 
					 | 
				
			||||||
        createdAt: 'DESC',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async get(userId: string): Promise<SharedLinkEntity[]> {
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.find({
 | 
					 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        userId: userId,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      relations: ['assets', 'album'],
 | 
					 | 
				
			||||||
      order: {
 | 
					 | 
				
			||||||
        createdAt: 'DESC',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async create(payload: SharedLinkEntity): Promise<SharedLinkEntity> {
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.save(payload);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async getById(id: string): Promise<SharedLinkEntity | null> {
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.findOne({
 | 
					 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        id: id,
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      relations: {
 | 
					 | 
				
			||||||
        assets: {
 | 
					 | 
				
			||||||
          exifInfo: true,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        album: {
 | 
					 | 
				
			||||||
          assets: {
 | 
					 | 
				
			||||||
            assetInfo: {
 | 
					 | 
				
			||||||
              exifInfo: true,
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      order: {
 | 
					 | 
				
			||||||
        createdAt: 'DESC',
 | 
					 | 
				
			||||||
        assets: {
 | 
					 | 
				
			||||||
          createdAt: 'ASC',
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
        album: {
 | 
					 | 
				
			||||||
          assets: {
 | 
					 | 
				
			||||||
            assetInfo: {
 | 
					 | 
				
			||||||
              createdAt: 'ASC',
 | 
					 | 
				
			||||||
            },
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async getByKey(key: string): Promise<SharedLinkEntity | null> {
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.findOne({
 | 
					 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        key: Buffer.from(key, 'hex'),
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      relations: {
 | 
					 | 
				
			||||||
        assets: true,
 | 
					 | 
				
			||||||
        album: {
 | 
					 | 
				
			||||||
          assets: {
 | 
					 | 
				
			||||||
            assetInfo: true,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
      order: {
 | 
					 | 
				
			||||||
        createdAt: 'DESC',
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async remove(entity: SharedLinkEntity): Promise<SharedLinkEntity> {
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.remove(entity);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async save(entity: SharedLinkEntity): Promise<SharedLinkEntity> {
 | 
					 | 
				
			||||||
    return await this.sharedLinkRepository.save(entity);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async hasAssetAccess(id: string, assetId: string): Promise<boolean> {
 | 
					 | 
				
			||||||
    const count1 = await this.sharedLinkRepository.count({
 | 
					 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        id,
 | 
					 | 
				
			||||||
        assets: {
 | 
					 | 
				
			||||||
          id: assetId,
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    const count2 = await this.sharedLinkRepository.count({
 | 
					 | 
				
			||||||
      where: {
 | 
					 | 
				
			||||||
        id,
 | 
					 | 
				
			||||||
        album: {
 | 
					 | 
				
			||||||
          assets: {
 | 
					 | 
				
			||||||
            assetId,
 | 
					 | 
				
			||||||
          },
 | 
					 | 
				
			||||||
        },
 | 
					 | 
				
			||||||
      },
 | 
					 | 
				
			||||||
    });
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    return Boolean(count1 + count2);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
@ -5,7 +5,7 @@ import { UpdateTagDto } from './dto/update-tag.dto';
 | 
				
			|||||||
import { Authenticated } from '../../decorators/authenticated.decorator';
 | 
					import { Authenticated } from '../../decorators/authenticated.decorator';
 | 
				
			||||||
import { ApiTags } from '@nestjs/swagger';
 | 
					import { ApiTags } from '@nestjs/swagger';
 | 
				
			||||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 | 
					import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 | 
				
			||||||
import { mapTag, TagResponseDto } from './response-dto/tag-response.dto';
 | 
					import { mapTag, TagResponseDto } from '@app/domain';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Authenticated()
 | 
					@Authenticated()
 | 
				
			||||||
@ApiTags('Tag')
 | 
					@ApiTags('Tag')
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ import { AuthUserDto } from '../../decorators/auth-user.decorator';
 | 
				
			|||||||
import { CreateTagDto } from './dto/create-tag.dto';
 | 
					import { CreateTagDto } from './dto/create-tag.dto';
 | 
				
			||||||
import { UpdateTagDto } from './dto/update-tag.dto';
 | 
					import { UpdateTagDto } from './dto/update-tag.dto';
 | 
				
			||||||
import { ITagRepository } from './tag.repository';
 | 
					import { ITagRepository } from './tag.repository';
 | 
				
			||||||
import { mapTag, TagResponseDto } from './response-dto/tag-response.dto';
 | 
					import { mapTag, TagResponseDto } from '@app/domain';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Injectable()
 | 
					@Injectable()
 | 
				
			||||||
export class TagService {
 | 
					export class TagService {
 | 
				
			||||||
 | 
				
			|||||||
@ -13,13 +13,13 @@ import { ScheduleModule } from '@nestjs/schedule';
 | 
				
			|||||||
import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module';
 | 
					import { ScheduleTasksModule } from './modules/schedule-tasks/schedule-tasks.module';
 | 
				
			||||||
import { JobModule } from './api-v1/job/job.module';
 | 
					import { JobModule } from './api-v1/job/job.module';
 | 
				
			||||||
import { TagModule } from './api-v1/tag/tag.module';
 | 
					import { TagModule } from './api-v1/tag/tag.module';
 | 
				
			||||||
import { ShareModule } from './api-v1/share/share.module';
 | 
					 | 
				
			||||||
import { DomainModule } from '@app/domain';
 | 
					import { DomainModule } from '@app/domain';
 | 
				
			||||||
import { InfraModule } from '@app/infra';
 | 
					import { InfraModule } from '@app/infra';
 | 
				
			||||||
import {
 | 
					import {
 | 
				
			||||||
  APIKeyController,
 | 
					  APIKeyController,
 | 
				
			||||||
  AuthController,
 | 
					  AuthController,
 | 
				
			||||||
  OAuthController,
 | 
					  OAuthController,
 | 
				
			||||||
 | 
					  ShareController,
 | 
				
			||||||
  SystemConfigController,
 | 
					  SystemConfigController,
 | 
				
			||||||
  UserController,
 | 
					  UserController,
 | 
				
			||||||
} from './controllers';
 | 
					} from './controllers';
 | 
				
			||||||
@ -53,8 +53,6 @@ import {
 | 
				
			|||||||
    JobModule,
 | 
					    JobModule,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    TagModule,
 | 
					    TagModule,
 | 
				
			||||||
 | 
					 | 
				
			||||||
    ShareModule,
 | 
					 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
  controllers: [
 | 
					  controllers: [
 | 
				
			||||||
    //
 | 
					    //
 | 
				
			||||||
@ -62,6 +60,7 @@ import {
 | 
				
			|||||||
    APIKeyController,
 | 
					    APIKeyController,
 | 
				
			||||||
    AuthController,
 | 
					    AuthController,
 | 
				
			||||||
    OAuthController,
 | 
					    OAuthController,
 | 
				
			||||||
 | 
					    ShareController,
 | 
				
			||||||
    SystemConfigController,
 | 
					    SystemConfigController,
 | 
				
			||||||
    UserController,
 | 
					    UserController,
 | 
				
			||||||
  ],
 | 
					  ],
 | 
				
			||||||
 | 
				
			|||||||
@ -23,7 +23,7 @@ export const assetUploadOption: MulterOptions = {
 | 
				
			|||||||
export const multerUtils = { fileFilter, filename, destination };
 | 
					export const multerUtils = { fileFilter, filename, destination };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function fileFilter(req: Request, file: any, cb: any) {
 | 
					function fileFilter(req: Request, file: any, cb: any) {
 | 
				
			||||||
  if (!req.user) {
 | 
					  if (!req.user || (req.user.isPublicUser && !req.user.isAllowUpload)) {
 | 
				
			||||||
    return cb(new UnauthorizedException());
 | 
					    return cb(new UnauthorizedException());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  if (
 | 
					  if (
 | 
				
			||||||
@ -39,16 +39,12 @@ function fileFilter(req: Request, file: any, cb: any) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function destination(req: Request, file: Express.Multer.File, cb: any) {
 | 
					function destination(req: Request, file: Express.Multer.File, cb: any) {
 | 
				
			||||||
  if (!req.user) {
 | 
					  if (!req.user || (req.user.isPublicUser && !req.user.isAllowUpload)) {
 | 
				
			||||||
    return cb(new UnauthorizedException());
 | 
					    return cb(new UnauthorizedException());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const user = req.user as AuthUserDto;
 | 
					  const user = req.user as AuthUserDto;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (user.isPublicUser && !user.isAllowUpload) {
 | 
					 | 
				
			||||||
    return cb(new UnauthorizedException());
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  const basePath = APP_UPLOAD_LOCATION;
 | 
					  const basePath = APP_UPLOAD_LOCATION;
 | 
				
			||||||
  const sanitizedDeviceId = sanitize(String(req.body['deviceId']));
 | 
					  const sanitizedDeviceId = sanitize(String(req.body['deviceId']));
 | 
				
			||||||
  const originalUploadFolder = join(basePath, user.id, 'original', sanitizedDeviceId);
 | 
					  const originalUploadFolder = join(basePath, user.id, 'original', sanitizedDeviceId);
 | 
				
			||||||
@ -62,7 +58,7 @@ function destination(req: Request, file: Express.Multer.File, cb: any) {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function filename(req: Request, file: Express.Multer.File, cb: any) {
 | 
					function filename(req: Request, file: Express.Multer.File, cb: any) {
 | 
				
			||||||
  if (!req.user) {
 | 
					  if (!req.user || (req.user.isPublicUser && !req.user.isAllowUpload)) {
 | 
				
			||||||
    return cb(new UnauthorizedException());
 | 
					    return cb(new UnauthorizedException());
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
export * from './api-key.controller';
 | 
					export * from './api-key.controller';
 | 
				
			||||||
export * from './auth.controller';
 | 
					export * from './auth.controller';
 | 
				
			||||||
export * from './oauth.controller';
 | 
					export * from './oauth.controller';
 | 
				
			||||||
 | 
					export * from './share.controller';
 | 
				
			||||||
export * from './system-config.controller';
 | 
					export * from './system-config.controller';
 | 
				
			||||||
export * from './user.controller';
 | 
					export * from './user.controller';
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,8 @@
 | 
				
			|||||||
import { Body, Controller, Delete, Get, Param, Patch, ValidationPipe } from '@nestjs/common';
 | 
					import { Body, Controller, Delete, Get, Param, Patch, ValidationPipe } from '@nestjs/common';
 | 
				
			||||||
import { ApiTags } from '@nestjs/swagger';
 | 
					import { ApiTags } from '@nestjs/swagger';
 | 
				
			||||||
import { AuthUserDto, GetAuthUser } from '../../decorators/auth-user.decorator';
 | 
					import { GetAuthUser } from '../decorators/auth-user.decorator';
 | 
				
			||||||
import { Authenticated } from '../../decorators/authenticated.decorator';
 | 
					import { Authenticated } from '../decorators/authenticated.decorator';
 | 
				
			||||||
import { EditSharedLinkDto } from './dto/edit-shared-link.dto';
 | 
					import { AuthUserDto, EditSharedLinkDto, SharedLinkResponseDto, ShareService } from '@app/domain';
 | 
				
			||||||
import { SharedLinkResponseDto } from './response-dto/shared-link-response.dto';
 | 
					 | 
				
			||||||
import { ShareService } from './share.service';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ApiTags('share')
 | 
					@ApiTags('share')
 | 
				
			||||||
@Controller('share')
 | 
					@Controller('share')
 | 
				
			||||||
@ -24,23 +22,23 @@ export class ShareController {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @Authenticated()
 | 
					  @Authenticated()
 | 
				
			||||||
  @Get(':id')
 | 
					  @Get(':id')
 | 
				
			||||||
  getSharedLinkById(@Param('id') id: string): Promise<SharedLinkResponseDto> {
 | 
					  getSharedLinkById(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<SharedLinkResponseDto> {
 | 
				
			||||||
    return this.shareService.getById(id, true);
 | 
					    return this.shareService.getById(authUser, id, true);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Authenticated()
 | 
					  @Authenticated()
 | 
				
			||||||
  @Delete(':id')
 | 
					  @Delete(':id')
 | 
				
			||||||
  removeSharedLink(@Param('id') id: string, @GetAuthUser() authUser: AuthUserDto): Promise<string> {
 | 
					  removeSharedLink(@GetAuthUser() authUser: AuthUserDto, @Param('id') id: string): Promise<void> {
 | 
				
			||||||
    return this.shareService.remove(id, authUser.id);
 | 
					    return this.shareService.remove(authUser, id);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Authenticated()
 | 
					  @Authenticated()
 | 
				
			||||||
  @Patch(':id')
 | 
					  @Patch(':id')
 | 
				
			||||||
  editSharedLink(
 | 
					  editSharedLink(
 | 
				
			||||||
    @Param('id') id: string,
 | 
					 | 
				
			||||||
    @GetAuthUser() authUser: AuthUserDto,
 | 
					    @GetAuthUser() authUser: AuthUserDto,
 | 
				
			||||||
 | 
					    @Param('id') id: string,
 | 
				
			||||||
    @Body(new ValidationPipe()) dto: EditSharedLinkDto,
 | 
					    @Body(new ValidationPipe()) dto: EditSharedLinkDto,
 | 
				
			||||||
  ): Promise<SharedLinkResponseDto> {
 | 
					  ): Promise<SharedLinkResponseDto> {
 | 
				
			||||||
    return this.shareService.edit(id, authUser, dto);
 | 
					    return this.shareService.edit(authUser, id, dto);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@ -1,11 +1,9 @@
 | 
				
			|||||||
import { Module } from '@nestjs/common';
 | 
					import { Module } from '@nestjs/common';
 | 
				
			||||||
import { ShareModule } from '../../api-v1/share/share.module';
 | 
					 | 
				
			||||||
import { APIKeyStrategy } from './strategies/api-key.strategy';
 | 
					import { APIKeyStrategy } from './strategies/api-key.strategy';
 | 
				
			||||||
import { JwtStrategy } from './strategies/jwt.strategy';
 | 
					import { JwtStrategy } from './strategies/jwt.strategy';
 | 
				
			||||||
import { PublicShareStrategy } from './strategies/public-share.strategy';
 | 
					import { PublicShareStrategy } from './strategies/public-share.strategy';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@Module({
 | 
					@Module({
 | 
				
			||||||
  imports: [ShareModule],
 | 
					 | 
				
			||||||
  providers: [JwtStrategy, APIKeyStrategy, PublicShareStrategy],
 | 
					  providers: [JwtStrategy, APIKeyStrategy, PublicShareStrategy],
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
export class ImmichJwtModule {}
 | 
					export class ImmichJwtModule {}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,8 +1,7 @@
 | 
				
			|||||||
import { Injectable } from '@nestjs/common';
 | 
					import { Injectable } from '@nestjs/common';
 | 
				
			||||||
import { PassportStrategy } from '@nestjs/passport';
 | 
					import { PassportStrategy } from '@nestjs/passport';
 | 
				
			||||||
import { IStrategyOptions, Strategy } from 'passport-http-header-strategy';
 | 
					import { IStrategyOptions, Strategy } from 'passport-http-header-strategy';
 | 
				
			||||||
import { ShareService } from '../../../api-v1/share/share.service';
 | 
					import { AuthUserDto, ShareService } from '@app/domain';
 | 
				
			||||||
import { AuthUserDto } from '../../../decorators/auth-user.decorator';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const PUBLIC_SHARE_STRATEGY = 'public-share';
 | 
					export const PUBLIC_SHARE_STRATEGY = 'public-share';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
@ -4,7 +4,7 @@ import { WebpGeneratorProcessor, JpegGeneratorProcessor, QueueName, JobName } fr
 | 
				
			|||||||
import { InjectQueue, Process, Processor } from '@nestjs/bull';
 | 
					import { InjectQueue, Process, Processor } from '@nestjs/bull';
 | 
				
			||||||
import { Logger } from '@nestjs/common';
 | 
					import { Logger } from '@nestjs/common';
 | 
				
			||||||
import { InjectRepository } from '@nestjs/typeorm';
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
import { mapAsset } from 'apps/immich/src/api-v1/asset/response-dto/asset-response.dto';
 | 
					import { mapAsset } from '@app/domain';
 | 
				
			||||||
import { Job, Queue } from 'bull';
 | 
					import { Job, Queue } from 'bull';
 | 
				
			||||||
import ffmpeg from 'fluent-ffmpeg';
 | 
					import ffmpeg from 'fluent-ffmpeg';
 | 
				
			||||||
import { existsSync, mkdirSync } from 'node:fs';
 | 
					import { existsSync, mkdirSync } from 'node:fs';
 | 
				
			||||||
 | 
				
			|||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@ -1,5 +1,5 @@
 | 
				
			|||||||
import { AssetEntity } from '@app/infra';
 | 
					import { AssetEntity } from '@app/infra';
 | 
				
			||||||
import { AssetResponseDto } from 'apps/immich/src/api-v1/asset/response-dto/asset-response.dto';
 | 
					import { AssetResponseDto } from '@app/domain';
 | 
				
			||||||
import fs from 'fs';
 | 
					import fs from 'fs';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const deleteFiles = (asset: AssetEntity | AssetResponseDto) => {
 | 
					const deleteFiles = (asset: AssetEntity | AssetResponseDto) => {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										1
									
								
								server/libs/domain/src/album/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/libs/domain/src/album/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './response-dto';
 | 
				
			||||||
@ -1,7 +1,7 @@
 | 
				
			|||||||
import { AlbumEntity } from '@app/infra';
 | 
					import { AlbumEntity } from '@app/infra/db/entities';
 | 
				
			||||||
import { UserResponseDto, mapUser } from '@app/domain';
 | 
					 | 
				
			||||||
import { AssetResponseDto, mapAsset } from '../../asset/response-dto/asset-response.dto';
 | 
					 | 
				
			||||||
import { ApiProperty } from '@nestjs/swagger';
 | 
					import { ApiProperty } from '@nestjs/swagger';
 | 
				
			||||||
 | 
					import { AssetResponseDto, mapAsset } from '../../asset';
 | 
				
			||||||
 | 
					import { mapUser, UserResponseDto } from '../../user';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class AlbumResponseDto {
 | 
					export class AlbumResponseDto {
 | 
				
			||||||
  id!: string;
 | 
					  id!: string;
 | 
				
			||||||
							
								
								
									
										1
									
								
								server/libs/domain/src/album/response-dto/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/libs/domain/src/album/response-dto/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './album-response.dto';
 | 
				
			||||||
							
								
								
									
										1
									
								
								server/libs/domain/src/asset/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/libs/domain/src/asset/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './response-dto';
 | 
				
			||||||
@ -1,6 +1,6 @@
 | 
				
			|||||||
import { AssetEntity, AssetType } from '@app/infra';
 | 
					import { AssetEntity, AssetType } from '@app/infra/db/entities';
 | 
				
			||||||
import { ApiProperty } from '@nestjs/swagger';
 | 
					import { ApiProperty } from '@nestjs/swagger';
 | 
				
			||||||
import { mapTag, TagResponseDto } from '../../tag/response-dto/tag-response.dto';
 | 
					import { mapTag, TagResponseDto } from '../../tag';
 | 
				
			||||||
import { ExifResponseDto, mapExif } from './exif-response.dto';
 | 
					import { ExifResponseDto, mapExif } from './exif-response.dto';
 | 
				
			||||||
import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto';
 | 
					import { SmartInfoResponseDto, mapSmartInfo } from './smart-info-response.dto';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { ExifEntity } from '@app/infra';
 | 
					import { ExifEntity } from '@app/infra/db/entities';
 | 
				
			||||||
import { ApiProperty } from '@nestjs/swagger';
 | 
					import { ApiProperty } from '@nestjs/swagger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class ExifResponseDto {
 | 
					export class ExifResponseDto {
 | 
				
			||||||
@ -29,7 +29,7 @@ export class ExifResponseDto {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
export function mapExif(entity: ExifEntity): ExifResponseDto {
 | 
					export function mapExif(entity: ExifEntity): ExifResponseDto {
 | 
				
			||||||
  return {
 | 
					  return {
 | 
				
			||||||
    id: parseInt(entity.id),
 | 
					    id: entity.id,
 | 
				
			||||||
    make: entity.make,
 | 
					    make: entity.make,
 | 
				
			||||||
    model: entity.model,
 | 
					    model: entity.model,
 | 
				
			||||||
    imageName: entity.imageName,
 | 
					    imageName: entity.imageName,
 | 
				
			||||||
							
								
								
									
										3
									
								
								server/libs/domain/src/asset/response-dto/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								server/libs/domain/src/asset/response-dto/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,3 @@
 | 
				
			|||||||
 | 
					export * from './asset-response.dto';
 | 
				
			||||||
 | 
					export * from './exif-response.dto';
 | 
				
			||||||
 | 
					export * from './smart-info-response.dto';
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { SmartInfoEntity } from '@app/infra';
 | 
					import { SmartInfoEntity } from '@app/infra/db/entities';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SmartInfoResponseDto {
 | 
					export class SmartInfoResponseDto {
 | 
				
			||||||
  id?: string;
 | 
					  id?: string;
 | 
				
			||||||
@ -1,5 +1,6 @@
 | 
				
			|||||||
import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
 | 
					import { DynamicModule, Global, Module, ModuleMetadata, Provider } from '@nestjs/common';
 | 
				
			||||||
import { APIKeyService } from './api-key';
 | 
					import { APIKeyService } from './api-key';
 | 
				
			||||||
 | 
					import { ShareService } from './share';
 | 
				
			||||||
import { AuthService } from './auth';
 | 
					import { AuthService } from './auth';
 | 
				
			||||||
import { OAuthService } from './oauth';
 | 
					import { OAuthService } from './oauth';
 | 
				
			||||||
import { INITIAL_SYSTEM_CONFIG, SystemConfigService } from './system-config';
 | 
					import { INITIAL_SYSTEM_CONFIG, SystemConfigService } from './system-config';
 | 
				
			||||||
@ -11,6 +12,7 @@ const providers: Provider[] = [
 | 
				
			|||||||
  OAuthService,
 | 
					  OAuthService,
 | 
				
			||||||
  SystemConfigService,
 | 
					  SystemConfigService,
 | 
				
			||||||
  UserService,
 | 
					  UserService,
 | 
				
			||||||
 | 
					  ShareService,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  {
 | 
					  {
 | 
				
			||||||
    provide: INITIAL_SYSTEM_CONFIG,
 | 
					    provide: INITIAL_SYSTEM_CONFIG,
 | 
				
			||||||
 | 
				
			|||||||
@ -1,7 +1,11 @@
 | 
				
			|||||||
 | 
					export * from './album';
 | 
				
			||||||
export * from './api-key';
 | 
					export * from './api-key';
 | 
				
			||||||
 | 
					export * from './asset';
 | 
				
			||||||
export * from './auth';
 | 
					export * from './auth';
 | 
				
			||||||
export * from './domain.module';
 | 
					export * from './domain.module';
 | 
				
			||||||
export * from './job';
 | 
					export * from './job';
 | 
				
			||||||
export * from './oauth';
 | 
					export * from './oauth';
 | 
				
			||||||
 | 
					export * from './share';
 | 
				
			||||||
export * from './system-config';
 | 
					export * from './system-config';
 | 
				
			||||||
 | 
					export * from './tag';
 | 
				
			||||||
export * from './user';
 | 
					export * from './user';
 | 
				
			||||||
 | 
				
			|||||||
@ -25,7 +25,7 @@ export interface IVideoLengthExtractionProcessor {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IReverseGeocodingProcessor {
 | 
					export interface IReverseGeocodingProcessor {
 | 
				
			||||||
  exifId: string;
 | 
					  exifId: number;
 | 
				
			||||||
  latitude: number;
 | 
					  latitude: number;
 | 
				
			||||||
  longitude: number;
 | 
					  longitude: number;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -1,10 +1,9 @@
 | 
				
			|||||||
import { AlbumEntity, AssetEntity } from '@app/infra';
 | 
					import { AlbumEntity, AssetEntity, SharedLinkType } from '@app/infra/db/entities';
 | 
				
			||||||
import { SharedLinkType } from '@app/infra';
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class CreateSharedLinkDto {
 | 
					export class CreateSharedLinkDto {
 | 
				
			||||||
  description?: string;
 | 
					  description?: string;
 | 
				
			||||||
  expiredAt?: string;
 | 
					  expiresAt?: string;
 | 
				
			||||||
  sharedType!: SharedLinkType;
 | 
					  type!: SharedLinkType;
 | 
				
			||||||
  assets!: AssetEntity[];
 | 
					  assets!: AssetEntity[];
 | 
				
			||||||
  album?: AlbumEntity;
 | 
					  album?: AlbumEntity;
 | 
				
			||||||
  allowUpload?: boolean;
 | 
					  allowUpload?: boolean;
 | 
				
			||||||
@ -1,11 +1,11 @@
 | 
				
			|||||||
import { IsNotEmpty, IsOptional } from 'class-validator';
 | 
					import { IsOptional } from 'class-validator';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class EditSharedLinkDto {
 | 
					export class EditSharedLinkDto {
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  description?: string;
 | 
					  description?: string;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  expiredAt?: string;
 | 
					  expiresAt?: string | null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  allowUpload?: boolean;
 | 
					  allowUpload?: boolean;
 | 
				
			||||||
@ -15,7 +15,4 @@ export class EditSharedLinkDto {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  @IsOptional()
 | 
					  @IsOptional()
 | 
				
			||||||
  showExif?: boolean;
 | 
					  showExif?: boolean;
 | 
				
			||||||
 | 
					 | 
				
			||||||
  @IsNotEmpty()
 | 
					 | 
				
			||||||
  isEditExpireTime?: boolean;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								server/libs/domain/src/share/dto/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								server/libs/domain/src/share/dto/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,2 @@
 | 
				
			|||||||
 | 
					export * from './create-shared-link.dto';
 | 
				
			||||||
 | 
					export * from './edit-shared-link.dto';
 | 
				
			||||||
							
								
								
									
										5
									
								
								server/libs/domain/src/share/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								server/libs/domain/src/share/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
				
			|||||||
 | 
					export * from './dto';
 | 
				
			||||||
 | 
					export * from './response-dto';
 | 
				
			||||||
 | 
					export * from './share.core';
 | 
				
			||||||
 | 
					export * from './share.service';
 | 
				
			||||||
 | 
					export * from './shared-link.repository';
 | 
				
			||||||
							
								
								
									
										1
									
								
								server/libs/domain/src/share/response-dto/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/libs/domain/src/share/response-dto/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './shared-link-response.dto';
 | 
				
			||||||
@ -1,8 +1,8 @@
 | 
				
			|||||||
import { SharedLinkEntity, SharedLinkType } from '@app/infra';
 | 
					import { SharedLinkEntity, SharedLinkType } from '@app/infra/db/entities';
 | 
				
			||||||
import { ApiProperty } from '@nestjs/swagger';
 | 
					import { ApiProperty } from '@nestjs/swagger';
 | 
				
			||||||
import _ from 'lodash';
 | 
					import _ from 'lodash';
 | 
				
			||||||
import { AlbumResponseDto, mapAlbumExcludeAssetInfo } from '../../album/response-dto/album-response.dto';
 | 
					import { AlbumResponseDto, mapAlbumExcludeAssetInfo } from '../../album';
 | 
				
			||||||
import { AssetResponseDto, mapAsset, mapAssetWithoutExif } from '../../asset/response-dto/asset-response.dto';
 | 
					import { AssetResponseDto, mapAsset, mapAssetWithoutExif } from '../../asset';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class SharedLinkResponseDto {
 | 
					export class SharedLinkResponseDto {
 | 
				
			||||||
  id!: string;
 | 
					  id!: string;
 | 
				
			||||||
							
								
								
									
										81
									
								
								server/libs/domain/src/share/share.core.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								server/libs/domain/src/share/share.core.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					import { AssetEntity, SharedLinkEntity } from '@app/infra/db/entities';
 | 
				
			||||||
 | 
					import { BadRequestException, ForbiddenException, InternalServerErrorException, Logger } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { AuthUserDto, ICryptoRepository } from '../auth';
 | 
				
			||||||
 | 
					import { CreateSharedLinkDto } from './dto';
 | 
				
			||||||
 | 
					import { ISharedLinkRepository } from './shared-link.repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export class ShareCore {
 | 
				
			||||||
 | 
					  readonly logger = new Logger(ShareCore.name);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  constructor(private repository: ISharedLinkRepository, private cryptoRepository: ICryptoRepository) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getAll(userId: string): Promise<SharedLinkEntity[]> {
 | 
				
			||||||
 | 
					    return this.repository.getAll(userId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get(userId: string, id: string): Promise<SharedLinkEntity | null> {
 | 
				
			||||||
 | 
					    return this.repository.get(userId, id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getByKey(key: string): Promise<SharedLinkEntity | null> {
 | 
				
			||||||
 | 
					    return this.repository.getByKey(key);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  create(userId: string, dto: CreateSharedLinkDto): Promise<SharedLinkEntity> {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      return this.repository.create({
 | 
				
			||||||
 | 
					        key: Buffer.from(this.cryptoRepository.randomBytes(50)),
 | 
				
			||||||
 | 
					        description: dto.description,
 | 
				
			||||||
 | 
					        userId,
 | 
				
			||||||
 | 
					        createdAt: new Date().toISOString(),
 | 
				
			||||||
 | 
					        expiresAt: dto.expiresAt ?? null,
 | 
				
			||||||
 | 
					        type: dto.type,
 | 
				
			||||||
 | 
					        assets: dto.assets,
 | 
				
			||||||
 | 
					        album: dto.album,
 | 
				
			||||||
 | 
					        allowUpload: dto.allowUpload ?? false,
 | 
				
			||||||
 | 
					        allowDownload: dto.allowDownload ?? true,
 | 
				
			||||||
 | 
					        showExif: dto.showExif ?? true,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } catch (error: any) {
 | 
				
			||||||
 | 
					      this.logger.error(error, error.stack);
 | 
				
			||||||
 | 
					      throw new InternalServerErrorException('failed to create shared link');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async save(userId: string, id: string, entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity> {
 | 
				
			||||||
 | 
					    const link = await this.get(userId, id);
 | 
				
			||||||
 | 
					    if (!link) {
 | 
				
			||||||
 | 
					      throw new BadRequestException('Shared link not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.repository.save({ ...entity, userId, id });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async remove(userId: string, id: string): Promise<SharedLinkEntity> {
 | 
				
			||||||
 | 
					    const link = await this.get(userId, id);
 | 
				
			||||||
 | 
					    if (!link) {
 | 
				
			||||||
 | 
					      throw new BadRequestException('Shared link not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.repository.remove(link);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async updateAssets(userId: string, id: string, assets: AssetEntity[]) {
 | 
				
			||||||
 | 
					    const link = await this.get(userId, id);
 | 
				
			||||||
 | 
					    if (!link) {
 | 
				
			||||||
 | 
					      throw new BadRequestException('Shared link not found');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.repository.save({ ...link, assets });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async hasAssetAccess(id: string, assetId: string): Promise<boolean> {
 | 
				
			||||||
 | 
					    return this.repository.hasAssetAccess(id, assetId);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  checkDownloadAccess(user: AuthUserDto) {
 | 
				
			||||||
 | 
					    if (user.isPublicUser && !user.isAllowDownload) {
 | 
				
			||||||
 | 
					      throw new ForbiddenException();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										170
									
								
								server/libs/domain/src/share/share.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										170
									
								
								server/libs/domain/src/share/share.service.spec.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,170 @@
 | 
				
			|||||||
 | 
					import { BadRequestException, ForbiddenException, UnauthorizedException } from '@nestjs/common';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					  authStub,
 | 
				
			||||||
 | 
					  entityStub,
 | 
				
			||||||
 | 
					  newCryptoRepositoryMock,
 | 
				
			||||||
 | 
					  newSharedLinkRepositoryMock,
 | 
				
			||||||
 | 
					  newUserRepositoryMock,
 | 
				
			||||||
 | 
					  sharedLinkResponseStub,
 | 
				
			||||||
 | 
					  sharedLinkStub,
 | 
				
			||||||
 | 
					} from '../../test';
 | 
				
			||||||
 | 
					import { ICryptoRepository } from '../auth';
 | 
				
			||||||
 | 
					import { IUserRepository } from '../user';
 | 
				
			||||||
 | 
					import { ShareService } from './share.service';
 | 
				
			||||||
 | 
					import { ISharedLinkRepository } from './shared-link.repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe(ShareService.name, () => {
 | 
				
			||||||
 | 
					  let sut: ShareService;
 | 
				
			||||||
 | 
					  let cryptoMock: jest.Mocked<ICryptoRepository>;
 | 
				
			||||||
 | 
					  let shareMock: jest.Mocked<ISharedLinkRepository>;
 | 
				
			||||||
 | 
					  let userMock: jest.Mocked<IUserRepository>;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  beforeEach(async () => {
 | 
				
			||||||
 | 
					    cryptoMock = newCryptoRepositoryMock();
 | 
				
			||||||
 | 
					    shareMock = newSharedLinkRepositoryMock();
 | 
				
			||||||
 | 
					    userMock = newUserRepositoryMock();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sut = new ShareService(cryptoMock, shareMock, userMock);
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  it('should work', () => {
 | 
				
			||||||
 | 
					    expect(sut).toBeDefined();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('validate', () => {
 | 
				
			||||||
 | 
					    it('should not accept a non-existant key', async () => {
 | 
				
			||||||
 | 
					      shareMock.getByKey.mockResolvedValue(null);
 | 
				
			||||||
 | 
					      await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should not accept an expired key', async () => {
 | 
				
			||||||
 | 
					      shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
 | 
				
			||||||
 | 
					      await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should not accept a key without a user', async () => {
 | 
				
			||||||
 | 
					      shareMock.getByKey.mockResolvedValue(sharedLinkStub.expired);
 | 
				
			||||||
 | 
					      userMock.get.mockResolvedValue(null);
 | 
				
			||||||
 | 
					      await expect(sut.validate('key')).rejects.toBeInstanceOf(UnauthorizedException);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should accept a valid key', async () => {
 | 
				
			||||||
 | 
					      shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					      userMock.get.mockResolvedValue(entityStub.admin);
 | 
				
			||||||
 | 
					      await expect(sut.validate('key')).resolves.toEqual(authStub.adminSharedLink);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('getAll', () => {
 | 
				
			||||||
 | 
					    it('should return all keys for a user', async () => {
 | 
				
			||||||
 | 
					      shareMock.getAll.mockResolvedValue([sharedLinkStub.expired, sharedLinkStub.valid]);
 | 
				
			||||||
 | 
					      await expect(sut.getAll(authStub.user1)).resolves.toEqual([
 | 
				
			||||||
 | 
					        sharedLinkResponseStub.expired,
 | 
				
			||||||
 | 
					        sharedLinkResponseStub.valid,
 | 
				
			||||||
 | 
					      ]);
 | 
				
			||||||
 | 
					      expect(shareMock.getAll).toHaveBeenCalledWith(authStub.user1.id);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('getMine', () => {
 | 
				
			||||||
 | 
					    it('should only work for a public user', async () => {
 | 
				
			||||||
 | 
					      await expect(sut.getMine(authStub.admin)).rejects.toBeInstanceOf(ForbiddenException);
 | 
				
			||||||
 | 
					      expect(shareMock.get).not.toHaveBeenCalled();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should return the key for the public user (auth dto)', async () => {
 | 
				
			||||||
 | 
					      const authDto = authStub.adminSharedLink;
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					      await expect(sut.getMine(authDto)).resolves.toEqual(sharedLinkResponseStub.valid);
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authDto.id, authDto.sharedLinkId);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('get', () => {
 | 
				
			||||||
 | 
					    it('should not work on a missing key', async () => {
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(null);
 | 
				
			||||||
 | 
					      await expect(sut.getById(authStub.user1, sharedLinkStub.valid.id, true)).rejects.toBeInstanceOf(
 | 
				
			||||||
 | 
					        BadRequestException,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
 | 
				
			||||||
 | 
					      expect(shareMock.remove).not.toHaveBeenCalled();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should get a key by id', async () => {
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					      await expect(sut.getById(authStub.user1, sharedLinkStub.valid.id, false)).resolves.toEqual(
 | 
				
			||||||
 | 
					        sharedLinkResponseStub.valid,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should include exif', async () => {
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(sharedLinkStub.readonly);
 | 
				
			||||||
 | 
					      await expect(sut.getById(authStub.user1, sharedLinkStub.readonly.id, true)).resolves.toEqual(
 | 
				
			||||||
 | 
					        sharedLinkResponseStub.readonly,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.readonly.id);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should exclude exif', async () => {
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(sharedLinkStub.readonly);
 | 
				
			||||||
 | 
					      await expect(sut.getById(authStub.user1, sharedLinkStub.readonly.id, false)).resolves.toEqual(
 | 
				
			||||||
 | 
					        sharedLinkResponseStub.readonlyNoExif,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.readonly.id);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('remove', () => {
 | 
				
			||||||
 | 
					    it('should not work on a missing key', async () => {
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(null);
 | 
				
			||||||
 | 
					      await expect(sut.remove(authStub.user1, sharedLinkStub.valid.id)).rejects.toBeInstanceOf(BadRequestException);
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
 | 
				
			||||||
 | 
					      expect(shareMock.remove).not.toHaveBeenCalled();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should remove a key', async () => {
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					      await sut.remove(authStub.user1, sharedLinkStub.valid.id);
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
 | 
				
			||||||
 | 
					      expect(shareMock.remove).toHaveBeenCalledWith(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('getByKey', () => {
 | 
				
			||||||
 | 
					    it('should not work on a missing key', async () => {
 | 
				
			||||||
 | 
					      shareMock.getByKey.mockResolvedValue(null);
 | 
				
			||||||
 | 
					      await expect(sut.getByKey('secret-key')).rejects.toBeInstanceOf(BadRequestException);
 | 
				
			||||||
 | 
					      expect(shareMock.getByKey).toHaveBeenCalledWith('secret-key');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should find a key', async () => {
 | 
				
			||||||
 | 
					      shareMock.getByKey.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					      await expect(sut.getByKey('secret-key')).resolves.toEqual(sharedLinkResponseStub.valid);
 | 
				
			||||||
 | 
					      expect(shareMock.getByKey).toHaveBeenCalledWith('secret-key');
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe('edit', () => {
 | 
				
			||||||
 | 
					    it('should not work on a missing key', async () => {
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(null);
 | 
				
			||||||
 | 
					      await expect(sut.edit(authStub.user1, sharedLinkStub.valid.id, {})).rejects.toBeInstanceOf(BadRequestException);
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
 | 
				
			||||||
 | 
					      expect(shareMock.save).not.toHaveBeenCalled();
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it('should edit a key', async () => {
 | 
				
			||||||
 | 
					      shareMock.get.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					      shareMock.save.mockResolvedValue(sharedLinkStub.valid);
 | 
				
			||||||
 | 
					      const dto = { allowDownload: false };
 | 
				
			||||||
 | 
					      await sut.edit(authStub.user1, sharedLinkStub.valid.id, dto);
 | 
				
			||||||
 | 
					      // await expect(sut.edit(authStub.user1, sharedLinkStub.valid.id, dto)).rejects.toBeInstanceOf(BadRequestException);
 | 
				
			||||||
 | 
					      expect(shareMock.get).toHaveBeenCalledWith(authStub.user1.id, sharedLinkStub.valid.id);
 | 
				
			||||||
 | 
					      expect(shareMock.save).toHaveBeenCalledWith({
 | 
				
			||||||
 | 
					        id: sharedLinkStub.valid.id,
 | 
				
			||||||
 | 
					        userId: authStub.user1.id,
 | 
				
			||||||
 | 
					        allowDownload: false,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
@ -6,10 +6,10 @@ import {
 | 
				
			|||||||
  Logger,
 | 
					  Logger,
 | 
				
			||||||
  UnauthorizedException,
 | 
					  UnauthorizedException,
 | 
				
			||||||
} from '@nestjs/common';
 | 
					} from '@nestjs/common';
 | 
				
			||||||
import { UserService } from '@app/domain';
 | 
					import { AuthUserDto, ICryptoRepository } from '../auth';
 | 
				
			||||||
import { AuthUserDto } from '../../decorators/auth-user.decorator';
 | 
					import { IUserRepository, UserCore } from '../user';
 | 
				
			||||||
import { EditSharedLinkDto } from './dto/edit-shared-link.dto';
 | 
					import { EditSharedLinkDto } from './dto';
 | 
				
			||||||
import { mapSharedLink, mapSharedLinkWithNoExif, SharedLinkResponseDto } from './response-dto/shared-link-response.dto';
 | 
					import { mapSharedLink, mapSharedLinkWithNoExif, SharedLinkResponseDto } from './response-dto';
 | 
				
			||||||
import { ShareCore } from './share.core';
 | 
					import { ShareCore } from './share.core';
 | 
				
			||||||
import { ISharedLinkRepository } from './shared-link.repository';
 | 
					import { ISharedLinkRepository } from './shared-link.repository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -17,20 +17,22 @@ import { ISharedLinkRepository } from './shared-link.repository';
 | 
				
			|||||||
export class ShareService {
 | 
					export class ShareService {
 | 
				
			||||||
  readonly logger = new Logger(ShareService.name);
 | 
					  readonly logger = new Logger(ShareService.name);
 | 
				
			||||||
  private shareCore: ShareCore;
 | 
					  private shareCore: ShareCore;
 | 
				
			||||||
 | 
					  private userCore: UserCore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  constructor(
 | 
					  constructor(
 | 
				
			||||||
    @Inject(ISharedLinkRepository)
 | 
					    @Inject(ICryptoRepository) cryptoRepository: ICryptoRepository,
 | 
				
			||||||
    sharedLinkRepository: ISharedLinkRepository,
 | 
					    @Inject(ISharedLinkRepository) sharedLinkRepository: ISharedLinkRepository,
 | 
				
			||||||
    private userService: UserService,
 | 
					    @Inject(IUserRepository) userRepository: IUserRepository,
 | 
				
			||||||
  ) {
 | 
					  ) {
 | 
				
			||||||
    this.shareCore = new ShareCore(sharedLinkRepository);
 | 
					    this.shareCore = new ShareCore(sharedLinkRepository, cryptoRepository);
 | 
				
			||||||
 | 
					    this.userCore = new UserCore(userRepository);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async validate(key: string): Promise<AuthUserDto> {
 | 
					  async validate(key: string): Promise<AuthUserDto> {
 | 
				
			||||||
    const link = await this.shareCore.getSharedLinkByKey(key);
 | 
					    const link = await this.shareCore.getByKey(key);
 | 
				
			||||||
    if (link) {
 | 
					    if (link) {
 | 
				
			||||||
      if (!link.expiresAt || new Date(link.expiresAt) > new Date()) {
 | 
					      if (!link.expiresAt || new Date(link.expiresAt) > new Date()) {
 | 
				
			||||||
        const user = await this.userService.getUserById(link.userId).catch(() => null);
 | 
					        const user = await this.userCore.get(link.userId);
 | 
				
			||||||
        if (user) {
 | 
					        if (user) {
 | 
				
			||||||
          return {
 | 
					          return {
 | 
				
			||||||
            id: user.id,
 | 
					            id: user.id,
 | 
				
			||||||
@ -49,7 +51,7 @@ export class ShareService {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
 | 
					  async getAll(authUser: AuthUserDto): Promise<SharedLinkResponseDto[]> {
 | 
				
			||||||
    const links = await this.shareCore.getSharedLinks(authUser.id);
 | 
					    const links = await this.shareCore.getAll(authUser.id);
 | 
				
			||||||
    return links.map(mapSharedLink);
 | 
					    return links.map(mapSharedLink);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -63,11 +65,11 @@ export class ShareService {
 | 
				
			|||||||
      allowExif = authUser.isShowExif;
 | 
					      allowExif = authUser.isShowExif;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.getById(authUser.sharedLinkId, allowExif);
 | 
					    return this.getById(authUser, authUser.sharedLinkId, allowExif);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async getById(id: string, allowExif: boolean): Promise<SharedLinkResponseDto> {
 | 
					  async getById(authUser: AuthUserDto, id: string, allowExif: boolean): Promise<SharedLinkResponseDto> {
 | 
				
			||||||
    const link = await this.shareCore.getSharedLinkById(id);
 | 
					    const link = await this.shareCore.get(authUser.id, id);
 | 
				
			||||||
    if (!link) {
 | 
					    if (!link) {
 | 
				
			||||||
      throw new BadRequestException('Shared link not found');
 | 
					      throw new BadRequestException('Shared link not found');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@ -79,21 +81,20 @@ export class ShareService {
 | 
				
			|||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async remove(id: string, userId: string): Promise<string> {
 | 
					 | 
				
			||||||
    await this.shareCore.removeSharedLink(id, userId);
 | 
					 | 
				
			||||||
    return id;
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  async getByKey(key: string): Promise<SharedLinkResponseDto> {
 | 
					  async getByKey(key: string): Promise<SharedLinkResponseDto> {
 | 
				
			||||||
    const link = await this.shareCore.getSharedLinkByKey(key);
 | 
					    const link = await this.shareCore.getByKey(key);
 | 
				
			||||||
    if (!link) {
 | 
					    if (!link) {
 | 
				
			||||||
      throw new BadRequestException('Shared link not found');
 | 
					      throw new BadRequestException('Shared link not found');
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
    return mapSharedLink(link);
 | 
					    return mapSharedLink(link);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  async edit(id: string, authUser: AuthUserDto, dto: EditSharedLinkDto) {
 | 
					  async remove(authUser: AuthUserDto, id: string): Promise<void> {
 | 
				
			||||||
    const link = await this.shareCore.updateSharedLink(id, authUser.id, dto);
 | 
					    await this.shareCore.remove(authUser.id, id);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async edit(authUser: AuthUserDto, id: string, dto: EditSharedLinkDto) {
 | 
				
			||||||
 | 
					    const link = await this.shareCore.save(authUser.id, id, dto);
 | 
				
			||||||
    return mapSharedLink(link);
 | 
					    return mapSharedLink(link);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
							
								
								
									
										13
									
								
								server/libs/domain/src/share/shared-link.repository.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								server/libs/domain/src/share/shared-link.repository.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import { SharedLinkEntity } from '@app/infra/db/entities';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const ISharedLinkRepository = 'ISharedLinkRepository';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface ISharedLinkRepository {
 | 
				
			||||||
 | 
					  getAll(userId: string): Promise<SharedLinkEntity[]>;
 | 
				
			||||||
 | 
					  get(userId: string, id: string): Promise<SharedLinkEntity | null>;
 | 
				
			||||||
 | 
					  getByKey(key: string): Promise<SharedLinkEntity | null>;
 | 
				
			||||||
 | 
					  create(entity: Omit<SharedLinkEntity, 'id'>): Promise<SharedLinkEntity>;
 | 
				
			||||||
 | 
					  remove(entity: SharedLinkEntity): Promise<SharedLinkEntity>;
 | 
				
			||||||
 | 
					  save(entity: Partial<SharedLinkEntity>): Promise<SharedLinkEntity>;
 | 
				
			||||||
 | 
					  hasAssetAccess(id: string, assetId: string): Promise<boolean>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1
									
								
								server/libs/domain/src/tag/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/libs/domain/src/tag/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './response-dto';
 | 
				
			||||||
							
								
								
									
										1
									
								
								server/libs/domain/src/tag/response-dto/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								server/libs/domain/src/tag/response-dto/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1 @@
 | 
				
			|||||||
 | 
					export * from './tag-response.dto';
 | 
				
			||||||
@ -1,4 +1,4 @@
 | 
				
			|||||||
import { TagEntity, TagType } from '@app/infra';
 | 
					import { TagEntity, TagType } from '@app/infra/db/entities';
 | 
				
			||||||
import { ApiProperty } from '@nestjs/swagger';
 | 
					import { ApiProperty } from '@nestjs/swagger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export class TagResponseDto {
 | 
					export class TagResponseDto {
 | 
				
			||||||
@ -1,5 +1,71 @@
 | 
				
			|||||||
import { SystemConfig, UserEntity } from '@app/infra/db/entities';
 | 
					import { AssetType, SharedLinkEntity, SharedLinkType, SystemConfig, UserEntity } from '@app/infra/db/entities';
 | 
				
			||||||
import { AuthUserDto } from '../src';
 | 
					import { AlbumResponseDto, AssetResponseDto, AuthUserDto, ExifResponseDto, SharedLinkResponseDto } from '../src';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const today = new Date();
 | 
				
			||||||
 | 
					const tomorrow = new Date();
 | 
				
			||||||
 | 
					const yesterday = new Date();
 | 
				
			||||||
 | 
					tomorrow.setDate(today.getDate() + 1);
 | 
				
			||||||
 | 
					yesterday.setDate(yesterday.getDate() - 1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const assetInfo: ExifResponseDto = {
 | 
				
			||||||
 | 
					  id: 1,
 | 
				
			||||||
 | 
					  make: 'camera-make',
 | 
				
			||||||
 | 
					  model: 'camera-model',
 | 
				
			||||||
 | 
					  imageName: 'fancy-image',
 | 
				
			||||||
 | 
					  exifImageWidth: 500,
 | 
				
			||||||
 | 
					  exifImageHeight: 500,
 | 
				
			||||||
 | 
					  fileSizeInByte: 100,
 | 
				
			||||||
 | 
					  orientation: 'orientation',
 | 
				
			||||||
 | 
					  dateTimeOriginal: today,
 | 
				
			||||||
 | 
					  modifyDate: today,
 | 
				
			||||||
 | 
					  lensModel: 'fancy',
 | 
				
			||||||
 | 
					  fNumber: 100,
 | 
				
			||||||
 | 
					  focalLength: 100,
 | 
				
			||||||
 | 
					  iso: 100,
 | 
				
			||||||
 | 
					  exposureTime: 100,
 | 
				
			||||||
 | 
					  latitude: 100,
 | 
				
			||||||
 | 
					  longitude: 100,
 | 
				
			||||||
 | 
					  city: 'city',
 | 
				
			||||||
 | 
					  state: 'state',
 | 
				
			||||||
 | 
					  country: 'country',
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const assetResponse: AssetResponseDto = {
 | 
				
			||||||
 | 
					  id: 'id_1',
 | 
				
			||||||
 | 
					  deviceAssetId: 'device_asset_id_1',
 | 
				
			||||||
 | 
					  ownerId: 'user_id_1',
 | 
				
			||||||
 | 
					  deviceId: 'device_id_1',
 | 
				
			||||||
 | 
					  type: AssetType.VIDEO,
 | 
				
			||||||
 | 
					  originalPath: 'fake_path/jpeg',
 | 
				
			||||||
 | 
					  resizePath: '',
 | 
				
			||||||
 | 
					  createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					  modifiedAt: today.toISOString(),
 | 
				
			||||||
 | 
					  isFavorite: false,
 | 
				
			||||||
 | 
					  mimeType: 'image/jpeg',
 | 
				
			||||||
 | 
					  smartInfo: {
 | 
				
			||||||
 | 
					    id: 'should-be-a-number',
 | 
				
			||||||
 | 
					    tags: [],
 | 
				
			||||||
 | 
					    objects: ['a', 'b', 'c'],
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  webpPath: '',
 | 
				
			||||||
 | 
					  encodedVideoPath: '',
 | 
				
			||||||
 | 
					  duration: '0:00:00.00000',
 | 
				
			||||||
 | 
					  exifInfo: assetInfo,
 | 
				
			||||||
 | 
					  livePhotoVideoId: null,
 | 
				
			||||||
 | 
					  tags: [],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const albumResponse: AlbumResponseDto = {
 | 
				
			||||||
 | 
					  albumName: 'Test Album',
 | 
				
			||||||
 | 
					  albumThumbnailAssetId: null,
 | 
				
			||||||
 | 
					  createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					  id: 'album-123',
 | 
				
			||||||
 | 
					  ownerId: 'admin_id',
 | 
				
			||||||
 | 
					  sharedUsers: [],
 | 
				
			||||||
 | 
					  shared: false,
 | 
				
			||||||
 | 
					  assets: [],
 | 
				
			||||||
 | 
					  assetCount: 1,
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const authStub = {
 | 
					export const authStub = {
 | 
				
			||||||
  admin: Object.freeze<AuthUserDto>({
 | 
					  admin: Object.freeze<AuthUserDto>({
 | 
				
			||||||
@ -16,6 +82,26 @@ export const authStub = {
 | 
				
			|||||||
    isPublicUser: false,
 | 
					    isPublicUser: false,
 | 
				
			||||||
    isAllowUpload: true,
 | 
					    isAllowUpload: true,
 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
 | 
					  adminSharedLink: Object.freeze<AuthUserDto>({
 | 
				
			||||||
 | 
					    id: 'admin_id',
 | 
				
			||||||
 | 
					    email: 'admin@test.com',
 | 
				
			||||||
 | 
					    isAdmin: true,
 | 
				
			||||||
 | 
					    isAllowUpload: true,
 | 
				
			||||||
 | 
					    isAllowDownload: true,
 | 
				
			||||||
 | 
					    isPublicUser: true,
 | 
				
			||||||
 | 
					    isShowExif: true,
 | 
				
			||||||
 | 
					    sharedLinkId: '123',
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  readonlySharedLink: Object.freeze<AuthUserDto>({
 | 
				
			||||||
 | 
					    id: 'admin_id',
 | 
				
			||||||
 | 
					    email: 'admin@test.com',
 | 
				
			||||||
 | 
					    isAdmin: true,
 | 
				
			||||||
 | 
					    isAllowUpload: false,
 | 
				
			||||||
 | 
					    isAllowDownload: false,
 | 
				
			||||||
 | 
					    isPublicUser: true,
 | 
				
			||||||
 | 
					    isShowExif: true,
 | 
				
			||||||
 | 
					    sharedLinkId: '123',
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const entityStub = {
 | 
					export const entityStub = {
 | 
				
			||||||
@ -165,3 +251,175 @@ export const loginResponseStub = {
 | 
				
			|||||||
    ],
 | 
					    ],
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sharedLinkStub = {
 | 
				
			||||||
 | 
					  valid: Object.freeze({
 | 
				
			||||||
 | 
					    id: '123',
 | 
				
			||||||
 | 
					    userId: authStub.admin.id,
 | 
				
			||||||
 | 
					    key: Buffer.from('secret-key', 'utf8'),
 | 
				
			||||||
 | 
					    type: SharedLinkType.ALBUM,
 | 
				
			||||||
 | 
					    createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					    expiresAt: tomorrow.toISOString(),
 | 
				
			||||||
 | 
					    allowUpload: true,
 | 
				
			||||||
 | 
					    allowDownload: true,
 | 
				
			||||||
 | 
					    showExif: true,
 | 
				
			||||||
 | 
					    album: undefined,
 | 
				
			||||||
 | 
					    assets: [],
 | 
				
			||||||
 | 
					  } as SharedLinkEntity),
 | 
				
			||||||
 | 
					  expired: Object.freeze({
 | 
				
			||||||
 | 
					    id: '123',
 | 
				
			||||||
 | 
					    userId: authStub.admin.id,
 | 
				
			||||||
 | 
					    key: Buffer.from('secret-key', 'utf8'),
 | 
				
			||||||
 | 
					    type: SharedLinkType.ALBUM,
 | 
				
			||||||
 | 
					    createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					    expiresAt: yesterday.toISOString(),
 | 
				
			||||||
 | 
					    allowUpload: true,
 | 
				
			||||||
 | 
					    allowDownload: true,
 | 
				
			||||||
 | 
					    showExif: true,
 | 
				
			||||||
 | 
					    assets: [],
 | 
				
			||||||
 | 
					  } as SharedLinkEntity),
 | 
				
			||||||
 | 
					  readonly: Object.freeze<SharedLinkEntity>({
 | 
				
			||||||
 | 
					    id: '123',
 | 
				
			||||||
 | 
					    userId: authStub.admin.id,
 | 
				
			||||||
 | 
					    key: Buffer.from('secret-key', 'utf8'),
 | 
				
			||||||
 | 
					    type: SharedLinkType.ALBUM,
 | 
				
			||||||
 | 
					    createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					    expiresAt: tomorrow.toISOString(),
 | 
				
			||||||
 | 
					    allowUpload: false,
 | 
				
			||||||
 | 
					    allowDownload: false,
 | 
				
			||||||
 | 
					    showExif: true,
 | 
				
			||||||
 | 
					    assets: [],
 | 
				
			||||||
 | 
					    album: {
 | 
				
			||||||
 | 
					      id: 'album-123',
 | 
				
			||||||
 | 
					      ownerId: authStub.admin.id,
 | 
				
			||||||
 | 
					      albumName: 'Test Album',
 | 
				
			||||||
 | 
					      createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					      albumThumbnailAssetId: null,
 | 
				
			||||||
 | 
					      sharedUsers: [],
 | 
				
			||||||
 | 
					      sharedLinks: [],
 | 
				
			||||||
 | 
					      assets: [
 | 
				
			||||||
 | 
					        {
 | 
				
			||||||
 | 
					          id: 'album-asset-123',
 | 
				
			||||||
 | 
					          albumId: 'album-123',
 | 
				
			||||||
 | 
					          assetId: 'asset-123',
 | 
				
			||||||
 | 
					          albumInfo: {} as any,
 | 
				
			||||||
 | 
					          assetInfo: {
 | 
				
			||||||
 | 
					            id: 'id_1',
 | 
				
			||||||
 | 
					            userId: 'user_id_1',
 | 
				
			||||||
 | 
					            deviceAssetId: 'device_asset_id_1',
 | 
				
			||||||
 | 
					            deviceId: 'device_id_1',
 | 
				
			||||||
 | 
					            type: AssetType.VIDEO,
 | 
				
			||||||
 | 
					            originalPath: 'fake_path/jpeg',
 | 
				
			||||||
 | 
					            resizePath: '',
 | 
				
			||||||
 | 
					            createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					            modifiedAt: today.toISOString(),
 | 
				
			||||||
 | 
					            isFavorite: false,
 | 
				
			||||||
 | 
					            mimeType: 'image/jpeg',
 | 
				
			||||||
 | 
					            smartInfo: {
 | 
				
			||||||
 | 
					              id: 'should-be-a-number',
 | 
				
			||||||
 | 
					              assetId: 'id_1',
 | 
				
			||||||
 | 
					              tags: [],
 | 
				
			||||||
 | 
					              objects: ['a', 'b', 'c'],
 | 
				
			||||||
 | 
					              asset: null as any,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            webpPath: '',
 | 
				
			||||||
 | 
					            encodedVideoPath: '',
 | 
				
			||||||
 | 
					            duration: null,
 | 
				
			||||||
 | 
					            isVisible: true,
 | 
				
			||||||
 | 
					            livePhotoVideoId: null,
 | 
				
			||||||
 | 
					            exifInfo: {
 | 
				
			||||||
 | 
					              id: 1,
 | 
				
			||||||
 | 
					              assetId: 'id_1',
 | 
				
			||||||
 | 
					              description: 'description',
 | 
				
			||||||
 | 
					              exifImageWidth: 500,
 | 
				
			||||||
 | 
					              exifImageHeight: 500,
 | 
				
			||||||
 | 
					              fileSizeInByte: 100,
 | 
				
			||||||
 | 
					              orientation: 'orientation',
 | 
				
			||||||
 | 
					              dateTimeOriginal: today,
 | 
				
			||||||
 | 
					              modifyDate: today,
 | 
				
			||||||
 | 
					              latitude: 100,
 | 
				
			||||||
 | 
					              longitude: 100,
 | 
				
			||||||
 | 
					              city: 'city',
 | 
				
			||||||
 | 
					              state: 'state',
 | 
				
			||||||
 | 
					              country: 'country',
 | 
				
			||||||
 | 
					              make: 'camera-make',
 | 
				
			||||||
 | 
					              model: 'camera-model',
 | 
				
			||||||
 | 
					              imageName: 'fancy-image',
 | 
				
			||||||
 | 
					              lensModel: 'fancy',
 | 
				
			||||||
 | 
					              fNumber: 100,
 | 
				
			||||||
 | 
					              focalLength: 100,
 | 
				
			||||||
 | 
					              iso: 100,
 | 
				
			||||||
 | 
					              exposureTime: 100,
 | 
				
			||||||
 | 
					              fps: 100,
 | 
				
			||||||
 | 
					              asset: null as any,
 | 
				
			||||||
 | 
					              exifTextSearchableColumn: '',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            tags: [],
 | 
				
			||||||
 | 
					            sharedLinks: [],
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const sharedLinkResponseStub = {
 | 
				
			||||||
 | 
					  valid: Object.freeze<SharedLinkResponseDto>({
 | 
				
			||||||
 | 
					    allowDownload: true,
 | 
				
			||||||
 | 
					    allowUpload: true,
 | 
				
			||||||
 | 
					    assets: [],
 | 
				
			||||||
 | 
					    createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					    description: undefined,
 | 
				
			||||||
 | 
					    expiresAt: tomorrow.toISOString(),
 | 
				
			||||||
 | 
					    id: '123',
 | 
				
			||||||
 | 
					    key: '7365637265742d6b6579',
 | 
				
			||||||
 | 
					    showExif: true,
 | 
				
			||||||
 | 
					    type: SharedLinkType.ALBUM,
 | 
				
			||||||
 | 
					    userId: 'admin_id',
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  expired: Object.freeze<SharedLinkResponseDto>({
 | 
				
			||||||
 | 
					    album: undefined,
 | 
				
			||||||
 | 
					    allowDownload: true,
 | 
				
			||||||
 | 
					    allowUpload: true,
 | 
				
			||||||
 | 
					    assets: [],
 | 
				
			||||||
 | 
					    createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					    description: undefined,
 | 
				
			||||||
 | 
					    expiresAt: yesterday.toISOString(),
 | 
				
			||||||
 | 
					    id: '123',
 | 
				
			||||||
 | 
					    key: '7365637265742d6b6579',
 | 
				
			||||||
 | 
					    showExif: true,
 | 
				
			||||||
 | 
					    type: SharedLinkType.ALBUM,
 | 
				
			||||||
 | 
					    userId: 'admin_id',
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  readonly: Object.freeze<SharedLinkResponseDto>({
 | 
				
			||||||
 | 
					    id: '123',
 | 
				
			||||||
 | 
					    userId: 'admin_id',
 | 
				
			||||||
 | 
					    key: '7365637265742d6b6579',
 | 
				
			||||||
 | 
					    type: SharedLinkType.ALBUM,
 | 
				
			||||||
 | 
					    createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					    expiresAt: tomorrow.toISOString(),
 | 
				
			||||||
 | 
					    description: undefined,
 | 
				
			||||||
 | 
					    allowUpload: false,
 | 
				
			||||||
 | 
					    allowDownload: false,
 | 
				
			||||||
 | 
					    showExif: true,
 | 
				
			||||||
 | 
					    album: albumResponse,
 | 
				
			||||||
 | 
					    assets: [assetResponse],
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					  readonlyNoExif: Object.freeze<SharedLinkResponseDto>({
 | 
				
			||||||
 | 
					    id: '123',
 | 
				
			||||||
 | 
					    userId: 'admin_id',
 | 
				
			||||||
 | 
					    key: '7365637265742d6b6579',
 | 
				
			||||||
 | 
					    type: SharedLinkType.ALBUM,
 | 
				
			||||||
 | 
					    createdAt: today.toISOString(),
 | 
				
			||||||
 | 
					    expiresAt: tomorrow.toISOString(),
 | 
				
			||||||
 | 
					    description: undefined,
 | 
				
			||||||
 | 
					    allowUpload: false,
 | 
				
			||||||
 | 
					    allowDownload: false,
 | 
				
			||||||
 | 
					    showExif: true,
 | 
				
			||||||
 | 
					    album: albumResponse,
 | 
				
			||||||
 | 
					    assets: [{ ...assetResponse, exifInfo: undefined }],
 | 
				
			||||||
 | 
					  }),
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// TODO - the constructor isn't used anywhere, so not test coverage
 | 
				
			||||||
 | 
					new ExifResponseDto();
 | 
				
			||||||
 | 
				
			|||||||
@ -2,5 +2,6 @@ export * from './api-key.repository.mock';
 | 
				
			|||||||
export * from './crypto.repository.mock';
 | 
					export * from './crypto.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 './system-config.repository.mock';
 | 
					export * from './system-config.repository.mock';
 | 
				
			||||||
export * from './user.repository.mock';
 | 
					export * from './user.repository.mock';
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										13
									
								
								server/libs/domain/test/shared-link.repository.mock.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										13
									
								
								server/libs/domain/test/shared-link.repository.mock.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,13 @@
 | 
				
			|||||||
 | 
					import { ISharedLinkRepository } from '../src';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const newSharedLinkRepositoryMock = (): jest.Mocked<ISharedLinkRepository> => {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    getAll: jest.fn(),
 | 
				
			||||||
 | 
					    get: jest.fn(),
 | 
				
			||||||
 | 
					    getByKey: jest.fn(),
 | 
				
			||||||
 | 
					    create: jest.fn(),
 | 
				
			||||||
 | 
					    remove: jest.fn(),
 | 
				
			||||||
 | 
					    save: jest.fn(),
 | 
				
			||||||
 | 
					    hasAssetAccess: jest.fn(),
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
@ -7,7 +7,7 @@ import { AssetEntity } from './asset.entity';
 | 
				
			|||||||
@Entity('exif')
 | 
					@Entity('exif')
 | 
				
			||||||
export class ExifEntity {
 | 
					export class ExifEntity {
 | 
				
			||||||
  @PrimaryGeneratedColumn()
 | 
					  @PrimaryGeneratedColumn()
 | 
				
			||||||
  id!: string;
 | 
					  id!: number;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  @Index({ unique: true })
 | 
					  @Index({ unique: true })
 | 
				
			||||||
  @Column({ type: 'uuid' })
 | 
					  @Column({ type: 'uuid' })
 | 
				
			||||||
 | 
				
			|||||||
@ -1,2 +1,3 @@
 | 
				
			|||||||
export * from './api-key.repository';
 | 
					export * from './api-key.repository';
 | 
				
			||||||
 | 
					export * from './shared-link.repository';
 | 
				
			||||||
export * from './user.repository';
 | 
					export * from './user.repository';
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										119
									
								
								server/libs/infra/src/db/repository/shared-link.repository.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										119
									
								
								server/libs/infra/src/db/repository/shared-link.repository.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,119 @@
 | 
				
			|||||||
 | 
					import { ISharedLinkRepository } from '@app/domain';
 | 
				
			||||||
 | 
					import { Injectable, Logger } from '@nestjs/common';
 | 
				
			||||||
 | 
					import { InjectRepository } from '@nestjs/typeorm';
 | 
				
			||||||
 | 
					import { Repository } from 'typeorm';
 | 
				
			||||||
 | 
					import { SharedLinkEntity } from '../entities';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					@Injectable()
 | 
				
			||||||
 | 
					export class SharedLinkRepository implements ISharedLinkRepository {
 | 
				
			||||||
 | 
					  readonly logger = new Logger(SharedLinkRepository.name);
 | 
				
			||||||
 | 
					  constructor(
 | 
				
			||||||
 | 
					    @InjectRepository(SharedLinkEntity)
 | 
				
			||||||
 | 
					    private readonly repository: Repository<SharedLinkEntity>,
 | 
				
			||||||
 | 
					  ) {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  get(userId: string, id: string): Promise<SharedLinkEntity | null> {
 | 
				
			||||||
 | 
					    return this.repository.findOne({
 | 
				
			||||||
 | 
					      where: {
 | 
				
			||||||
 | 
					        id,
 | 
				
			||||||
 | 
					        userId,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      relations: {
 | 
				
			||||||
 | 
					        assets: {
 | 
				
			||||||
 | 
					          exifInfo: true,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        album: {
 | 
				
			||||||
 | 
					          assets: {
 | 
				
			||||||
 | 
					            assetInfo: {
 | 
				
			||||||
 | 
					              exifInfo: true,
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      order: {
 | 
				
			||||||
 | 
					        createdAt: 'DESC',
 | 
				
			||||||
 | 
					        assets: {
 | 
				
			||||||
 | 
					          createdAt: 'ASC',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        album: {
 | 
				
			||||||
 | 
					          assets: {
 | 
				
			||||||
 | 
					            assetInfo: {
 | 
				
			||||||
 | 
					              createdAt: 'ASC',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getAll(userId: string): Promise<SharedLinkEntity[]> {
 | 
				
			||||||
 | 
					    return this.repository.find({
 | 
				
			||||||
 | 
					      where: {
 | 
				
			||||||
 | 
					        userId,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      relations: {
 | 
				
			||||||
 | 
					        assets: true,
 | 
				
			||||||
 | 
					        album: true,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      order: {
 | 
				
			||||||
 | 
					        createdAt: 'DESC',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async getByKey(key: string): Promise<SharedLinkEntity | null> {
 | 
				
			||||||
 | 
					    return await this.repository.findOne({
 | 
				
			||||||
 | 
					      where: {
 | 
				
			||||||
 | 
					        key: Buffer.from(key, 'hex'),
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      relations: {
 | 
				
			||||||
 | 
					        assets: true,
 | 
				
			||||||
 | 
					        album: {
 | 
				
			||||||
 | 
					          assets: {
 | 
				
			||||||
 | 
					            assetInfo: true,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      order: {
 | 
				
			||||||
 | 
					        createdAt: 'DESC',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  create(entity: Omit<SharedLinkEntity, 'id'>): Promise<SharedLinkEntity> {
 | 
				
			||||||
 | 
					    return this.repository.save(entity);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  remove(entity: SharedLinkEntity): Promise<SharedLinkEntity> {
 | 
				
			||||||
 | 
					    return this.repository.remove(entity);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async save(entity: SharedLinkEntity): Promise<SharedLinkEntity> {
 | 
				
			||||||
 | 
					    await this.repository.save(entity);
 | 
				
			||||||
 | 
					    return this.repository.findOneOrFail({ where: { id: entity.id } });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async hasAssetAccess(id: string, assetId: string): Promise<boolean> {
 | 
				
			||||||
 | 
					    const count1 = await this.repository.count({
 | 
				
			||||||
 | 
					      where: {
 | 
				
			||||||
 | 
					        id,
 | 
				
			||||||
 | 
					        assets: {
 | 
				
			||||||
 | 
					          id: assetId,
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const count2 = await this.repository.count({
 | 
				
			||||||
 | 
					      where: {
 | 
				
			||||||
 | 
					        id,
 | 
				
			||||||
 | 
					        album: {
 | 
				
			||||||
 | 
					          assets: {
 | 
				
			||||||
 | 
					            assetId,
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Boolean(count1 + count2);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@ -2,6 +2,7 @@ import {
 | 
				
			|||||||
  ICryptoRepository,
 | 
					  ICryptoRepository,
 | 
				
			||||||
  IJobRepository,
 | 
					  IJobRepository,
 | 
				
			||||||
  IKeyRepository,
 | 
					  IKeyRepository,
 | 
				
			||||||
 | 
					  ISharedLinkRepository,
 | 
				
			||||||
  ISystemConfigRepository,
 | 
					  ISystemConfigRepository,
 | 
				
			||||||
  IUserRepository,
 | 
					  IUserRepository,
 | 
				
			||||||
  QueueName,
 | 
					  QueueName,
 | 
				
			||||||
@ -11,10 +12,10 @@ import { BullModule } from '@nestjs/bull';
 | 
				
			|||||||
import { Global, Module, Provider } from '@nestjs/common';
 | 
					import { Global, Module, Provider } from '@nestjs/common';
 | 
				
			||||||
import { JwtModule } from '@nestjs/jwt';
 | 
					import { JwtModule } from '@nestjs/jwt';
 | 
				
			||||||
import { TypeOrmModule } from '@nestjs/typeorm';
 | 
					import { TypeOrmModule } from '@nestjs/typeorm';
 | 
				
			||||||
 | 
					import { APIKeyEntity, SharedLinkEntity, SystemConfigEntity, UserRepository } from './db';
 | 
				
			||||||
 | 
					import { APIKeyRepository, SharedLinkRepository } from './db/repository';
 | 
				
			||||||
import { jwtConfig } from '@app/domain';
 | 
					import { jwtConfig } from '@app/domain';
 | 
				
			||||||
import { CryptoRepository } from './auth/crypto.repository';
 | 
					import { CryptoRepository } from './auth/crypto.repository';
 | 
				
			||||||
import { APIKeyEntity, SystemConfigEntity, UserRepository } from './db';
 | 
					 | 
				
			||||||
import { APIKeyRepository } from './db/repository';
 | 
					 | 
				
			||||||
import { SystemConfigRepository } from './db/repository/system-config.repository';
 | 
					import { SystemConfigRepository } from './db/repository/system-config.repository';
 | 
				
			||||||
import { JobRepository } from './job';
 | 
					import { JobRepository } from './job';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -22,6 +23,7 @@ const providers: Provider[] = [
 | 
				
			|||||||
  { provide: ICryptoRepository, useClass: CryptoRepository },
 | 
					  { provide: ICryptoRepository, useClass: CryptoRepository },
 | 
				
			||||||
  { provide: IKeyRepository, useClass: APIKeyRepository },
 | 
					  { provide: IKeyRepository, useClass: APIKeyRepository },
 | 
				
			||||||
  { provide: IJobRepository, useClass: JobRepository },
 | 
					  { provide: IJobRepository, useClass: JobRepository },
 | 
				
			||||||
 | 
					  { provide: ISharedLinkRepository, useClass: SharedLinkRepository },
 | 
				
			||||||
  { provide: ISystemConfigRepository, useClass: SystemConfigRepository },
 | 
					  { provide: ISystemConfigRepository, useClass: SystemConfigRepository },
 | 
				
			||||||
  { provide: IUserRepository, useClass: UserRepository },
 | 
					  { provide: IUserRepository, useClass: UserRepository },
 | 
				
			||||||
];
 | 
					];
 | 
				
			||||||
@ -31,7 +33,7 @@ const providers: Provider[] = [
 | 
				
			|||||||
  imports: [
 | 
					  imports: [
 | 
				
			||||||
    JwtModule.register(jwtConfig),
 | 
					    JwtModule.register(jwtConfig),
 | 
				
			||||||
    TypeOrmModule.forRoot(databaseConfig),
 | 
					    TypeOrmModule.forRoot(databaseConfig),
 | 
				
			||||||
    TypeOrmModule.forFeature([APIKeyEntity, UserEntity, SystemConfigEntity]),
 | 
					    TypeOrmModule.forFeature([APIKeyEntity, UserEntity, SharedLinkEntity, SystemConfigEntity]),
 | 
				
			||||||
    BullModule.forRootAsync({
 | 
					    BullModule.forRootAsync({
 | 
				
			||||||
      useFactory: async () => ({
 | 
					      useFactory: async () => ({
 | 
				
			||||||
        prefix: 'immich_bull',
 | 
					        prefix: 'immich_bull',
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										16
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										16
									
								
								web/src/api/open-api/api.ts
									
									
									
										generated
									
									
									
								
							@ -658,7 +658,7 @@ export interface CreateAlbumShareLinkDto {
 | 
				
			|||||||
     * @type {string}
 | 
					     * @type {string}
 | 
				
			||||||
     * @memberof CreateAlbumShareLinkDto
 | 
					     * @memberof CreateAlbumShareLinkDto
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    'expiredAt'?: string;
 | 
					    'expiresAt'?: string;
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 
 | 
					     * 
 | 
				
			||||||
     * @type {boolean}
 | 
					     * @type {boolean}
 | 
				
			||||||
@ -701,7 +701,7 @@ export interface CreateAssetsShareLinkDto {
 | 
				
			|||||||
     * @type {string}
 | 
					     * @type {string}
 | 
				
			||||||
     * @memberof CreateAssetsShareLinkDto
 | 
					     * @memberof CreateAssetsShareLinkDto
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    'expiredAt'?: string;
 | 
					    'expiresAt'?: string;
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 
 | 
					     * 
 | 
				
			||||||
     * @type {boolean}
 | 
					     * @type {boolean}
 | 
				
			||||||
@ -1004,7 +1004,7 @@ export interface EditSharedLinkDto {
 | 
				
			|||||||
     * @type {string}
 | 
					     * @type {string}
 | 
				
			||||||
     * @memberof EditSharedLinkDto
 | 
					     * @memberof EditSharedLinkDto
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    'expiredAt'?: string;
 | 
					    'expiresAt'?: string | null;
 | 
				
			||||||
    /**
 | 
					    /**
 | 
				
			||||||
     * 
 | 
					     * 
 | 
				
			||||||
     * @type {boolean}
 | 
					     * @type {boolean}
 | 
				
			||||||
@ -1023,12 +1023,6 @@ export interface EditSharedLinkDto {
 | 
				
			|||||||
     * @memberof EditSharedLinkDto
 | 
					     * @memberof EditSharedLinkDto
 | 
				
			||||||
     */
 | 
					     */
 | 
				
			||||||
    'showExif'?: boolean;
 | 
					    'showExif'?: boolean;
 | 
				
			||||||
    /**
 | 
					 | 
				
			||||||
     * 
 | 
					 | 
				
			||||||
     * @type {boolean}
 | 
					 | 
				
			||||||
     * @memberof EditSharedLinkDto
 | 
					 | 
				
			||||||
     */
 | 
					 | 
				
			||||||
    'isEditExpireTime'?: boolean;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * 
 | 
					 * 
 | 
				
			||||||
@ -6745,7 +6739,7 @@ export const ShareApiFp = function(configuration?: Configuration) {
 | 
				
			|||||||
         * @param {*} [options] Override http request option.
 | 
					         * @param {*} [options] Override http request option.
 | 
				
			||||||
         * @throws {RequiredError}
 | 
					         * @throws {RequiredError}
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        async removeSharedLink(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<string>> {
 | 
					        async removeSharedLink(id: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<void>> {
 | 
				
			||||||
            const localVarAxiosArgs = await localVarAxiosParamCreator.removeSharedLink(id, options);
 | 
					            const localVarAxiosArgs = await localVarAxiosParamCreator.removeSharedLink(id, options);
 | 
				
			||||||
            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
 | 
					            return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration);
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
@ -6800,7 +6794,7 @@ export const ShareApiFactory = function (configuration?: Configuration, basePath
 | 
				
			|||||||
         * @param {*} [options] Override http request option.
 | 
					         * @param {*} [options] Override http request option.
 | 
				
			||||||
         * @throws {RequiredError}
 | 
					         * @throws {RequiredError}
 | 
				
			||||||
         */
 | 
					         */
 | 
				
			||||||
        removeSharedLink(id: string, options?: any): AxiosPromise<string> {
 | 
					        removeSharedLink(id: string, options?: any): AxiosPromise<void> {
 | 
				
			||||||
            return localVarFp.removeSharedLink(id, options).then((request) => request(axios, basePath));
 | 
					            return localVarFp.removeSharedLink(id, options).then((request) => request(axios, basePath));
 | 
				
			||||||
        },
 | 
					        },
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
				
			|||||||
@ -60,7 +60,7 @@
 | 
				
			|||||||
			if (shareType === SharedLinkType.Album && album) {
 | 
								if (shareType === SharedLinkType.Album && album) {
 | 
				
			||||||
				const { data } = await api.albumApi.createAlbumSharedLink({
 | 
									const { data } = await api.albumApi.createAlbumSharedLink({
 | 
				
			||||||
					albumId: album.id,
 | 
										albumId: album.id,
 | 
				
			||||||
					expiredAt: expirationDate,
 | 
										expiresAt: expirationDate,
 | 
				
			||||||
					allowUpload: isAllowUpload,
 | 
										allowUpload: isAllowUpload,
 | 
				
			||||||
					description: description,
 | 
										description: description,
 | 
				
			||||||
					allowDownload: isAllowDownload,
 | 
										allowDownload: isAllowDownload,
 | 
				
			||||||
@ -70,7 +70,7 @@
 | 
				
			|||||||
			} else {
 | 
								} else {
 | 
				
			||||||
				const { data } = await api.assetApi.createAssetsSharedLink({
 | 
									const { data } = await api.assetApi.createAssetsSharedLink({
 | 
				
			||||||
					assetIds: sharedAssets.map((a) => a.id),
 | 
										assetIds: sharedAssets.map((a) => a.id),
 | 
				
			||||||
					expiredAt: expirationDate,
 | 
										expiresAt: expirationDate,
 | 
				
			||||||
					allowUpload: isAllowUpload,
 | 
										allowUpload: isAllowUpload,
 | 
				
			||||||
					description: description,
 | 
										description: description,
 | 
				
			||||||
					allowDownload: isAllowDownload,
 | 
										allowDownload: isAllowDownload,
 | 
				
			||||||
@ -128,19 +128,14 @@
 | 
				
			|||||||
			try {
 | 
								try {
 | 
				
			||||||
				const expirationTime = getExpirationTimeInMillisecond();
 | 
									const expirationTime = getExpirationTimeInMillisecond();
 | 
				
			||||||
				const currentTime = new Date().getTime();
 | 
									const currentTime = new Date().getTime();
 | 
				
			||||||
				let expirationDate = expirationTime
 | 
									const expirationDate: string | null = expirationTime
 | 
				
			||||||
					? new Date(currentTime + expirationTime).toISOString()
 | 
										? new Date(currentTime + expirationTime).toISOString()
 | 
				
			||||||
					: undefined;
 | 
										: null;
 | 
				
			||||||
 | 
					 | 
				
			||||||
				if (expirationTime === 0) {
 | 
					 | 
				
			||||||
					expirationDate = undefined;
 | 
					 | 
				
			||||||
				}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
				await api.shareApi.editSharedLink(editingLink.id, {
 | 
									await api.shareApi.editSharedLink(editingLink.id, {
 | 
				
			||||||
					description: description,
 | 
										description,
 | 
				
			||||||
					expiredAt: expirationDate,
 | 
										expiresAt: shouldChangeExpirationTime ? expirationDate : undefined,
 | 
				
			||||||
					allowUpload: isAllowUpload,
 | 
										allowUpload: isAllowUpload,
 | 
				
			||||||
					isEditExpireTime: shouldChangeExpirationTime,
 | 
					 | 
				
			||||||
					allowDownload: isAllowDownload,
 | 
										allowDownload: isAllowDownload,
 | 
				
			||||||
					showExif: shouldShowExif
 | 
										showExif: shouldShowExif
 | 
				
			||||||
				});
 | 
									});
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user