mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feat(web): add asset and album count info (#623)
* Get asset and album count * Generate APIs * Added asset count for each type * Added api on the web * Added info button for asset and album count to trigger getting info on hover * Remove websocket event from photo page
This commit is contained in:
		| @@ -6,10 +6,12 @@ doc/AddAssetsDto.md | ||||
| doc/AddUsersDto.md | ||||
| doc/AdminSignupResponseDto.md | ||||
| doc/AlbumApi.md | ||||
| doc/AlbumCountResponseDto.md | ||||
| doc/AlbumResponseDto.md | ||||
| doc/AssetApi.md | ||||
| doc/AssetCountByTimeBucket.md | ||||
| doc/AssetCountByTimeBucketResponseDto.md | ||||
| doc/AssetCountByUserIdResponseDto.md | ||||
| doc/AssetFileUploadResponseDto.md | ||||
| doc/AssetResponseDto.md | ||||
| doc/AssetTypeEnum.md | ||||
| @@ -70,9 +72,11 @@ lib/auth/oauth.dart | ||||
| lib/model/add_assets_dto.dart | ||||
| lib/model/add_users_dto.dart | ||||
| lib/model/admin_signup_response_dto.dart | ||||
| lib/model/album_count_response_dto.dart | ||||
| lib/model/album_response_dto.dart | ||||
| lib/model/asset_count_by_time_bucket.dart | ||||
| lib/model/asset_count_by_time_bucket_response_dto.dart | ||||
| lib/model/asset_count_by_user_id_response_dto.dart | ||||
| lib/model/asset_file_upload_response_dto.dart | ||||
| lib/model/asset_response_dto.dart | ||||
| lib/model/asset_type_enum.dart | ||||
| @@ -111,3 +115,4 @@ lib/model/user_count_response_dto.dart | ||||
| lib/model/user_response_dto.dart | ||||
| lib/model/validate_access_token_response_dto.dart | ||||
| pubspec.yaml | ||||
| test/asset_count_by_user_id_response_dto_test.dart | ||||
|   | ||||
| @@ -69,6 +69,7 @@ Class | Method | HTTP request | Description | ||||
| *AlbumApi* | [**addUsersToAlbum**](doc//AlbumApi.md#adduserstoalbum) | **PUT** /album/{albumId}/users |  | ||||
| *AlbumApi* | [**createAlbum**](doc//AlbumApi.md#createalbum) | **POST** /album |  | ||||
| *AlbumApi* | [**deleteAlbum**](doc//AlbumApi.md#deletealbum) | **DELETE** /album/{albumId} |  | ||||
| *AlbumApi* | [**getAlbumCountByUserId**](doc//AlbumApi.md#getalbumcountbyuserid) | **GET** /album/count-by-user-id |  | ||||
| *AlbumApi* | [**getAlbumInfo**](doc//AlbumApi.md#getalbuminfo) | **GET** /album/{albumId} |  | ||||
| *AlbumApi* | [**getAllAlbums**](doc//AlbumApi.md#getallalbums) | **GET** /album |  | ||||
| *AlbumApi* | [**removeAssetFromAlbum**](doc//AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{albumId}/assets |  | ||||
| @@ -81,6 +82,7 @@ Class | Method | HTTP request | Description | ||||
| *AssetApi* | [**getAssetById**](doc//AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |  | ||||
| *AssetApi* | [**getAssetByTimeBucket**](doc//AssetApi.md#getassetbytimebucket) | **POST** /asset/time-bucket |  | ||||
| *AssetApi* | [**getAssetCountByTimeBucket**](doc//AssetApi.md#getassetcountbytimebucket) | **POST** /asset/count-by-time-bucket |  | ||||
| *AssetApi* | [**getAssetCountByUserId**](doc//AssetApi.md#getassetcountbyuserid) | **GET** /asset/count-by-user-id |  | ||||
| *AssetApi* | [**getAssetSearchTerms**](doc//AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |  | ||||
| *AssetApi* | [**getAssetThumbnail**](doc//AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |  | ||||
| *AssetApi* | [**getCuratedLocations**](doc//AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |  | ||||
| @@ -113,9 +115,11 @@ Class | Method | HTTP request | Description | ||||
|  - [AddAssetsDto](doc//AddAssetsDto.md) | ||||
|  - [AddUsersDto](doc//AddUsersDto.md) | ||||
|  - [AdminSignupResponseDto](doc//AdminSignupResponseDto.md) | ||||
|  - [AlbumCountResponseDto](doc//AlbumCountResponseDto.md) | ||||
|  - [AlbumResponseDto](doc//AlbumResponseDto.md) | ||||
|  - [AssetCountByTimeBucket](doc//AssetCountByTimeBucket.md) | ||||
|  - [AssetCountByTimeBucketResponseDto](doc//AssetCountByTimeBucketResponseDto.md) | ||||
|  - [AssetCountByUserIdResponseDto](doc//AssetCountByUserIdResponseDto.md) | ||||
|  - [AssetFileUploadResponseDto](doc//AssetFileUploadResponseDto.md) | ||||
|  - [AssetResponseDto](doc//AssetResponseDto.md) | ||||
|  - [AssetTypeEnum](doc//AssetTypeEnum.md) | ||||
|   | ||||
| @@ -13,6 +13,7 @@ Method | HTTP request | Description | ||||
| [**addUsersToAlbum**](AlbumApi.md#adduserstoalbum) | **PUT** /album/{albumId}/users |  | ||||
| [**createAlbum**](AlbumApi.md#createalbum) | **POST** /album |  | ||||
| [**deleteAlbum**](AlbumApi.md#deletealbum) | **DELETE** /album/{albumId} |  | ||||
| [**getAlbumCountByUserId**](AlbumApi.md#getalbumcountbyuserid) | **GET** /album/count-by-user-id |  | ||||
| [**getAlbumInfo**](AlbumApi.md#getalbuminfo) | **GET** /album/{albumId} |  | ||||
| [**getAllAlbums**](AlbumApi.md#getallalbums) | **GET** /album |  | ||||
| [**removeAssetFromAlbum**](AlbumApi.md#removeassetfromalbum) | **DELETE** /album/{albumId}/assets |  | ||||
| @@ -211,6 +212,49 @@ void (empty response body) | ||||
|  | ||||
| [[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) | ||||
|  | ||||
| # **getAlbumCountByUserId** | ||||
| > AlbumCountResponseDto getAlbumCountByUserId() | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Example | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| // TODO Configure HTTP Bearer authorization: bearer | ||||
| // Case 1. Use String Token | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); | ||||
| // Case 2. Use Function which generate token. | ||||
| // String yourTokenGeneratorFunction() { ... } | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction); | ||||
|  | ||||
| final api_instance = AlbumApi(); | ||||
|  | ||||
| try { | ||||
|     final result = api_instance.getAlbumCountByUserId(); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling AlbumApi->getAlbumCountByUserId: $e\n'); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Parameters | ||||
| This endpoint does not need any parameter. | ||||
|  | ||||
| ### Return type | ||||
|  | ||||
| [**AlbumCountResponseDto**](AlbumCountResponseDto.md) | ||||
|  | ||||
| ### Authorization | ||||
|  | ||||
| [bearer](../README.md#bearer) | ||||
|  | ||||
| ### HTTP request headers | ||||
|  | ||||
|  - **Content-Type**: Not defined | ||||
|  - **Accept**: application/json | ||||
|  | ||||
| [[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) | ||||
|  | ||||
| # **getAlbumInfo** | ||||
| > AlbumResponseDto getAlbumInfo(albumId) | ||||
|  | ||||
|   | ||||
							
								
								
									
										17
									
								
								mobile/openapi/doc/AlbumCountResponseDto.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								mobile/openapi/doc/AlbumCountResponseDto.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,17 @@ | ||||
| # openapi.model.AlbumCountResponseDto | ||||
|  | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
|  | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **owned** | **int** |  |  | ||||
| **shared** | **int** |  |  | ||||
| **sharing** | **int** |  |  | ||||
|  | ||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||
|  | ||||
|  | ||||
| @@ -16,6 +16,7 @@ Method | HTTP request | Description | ||||
| [**getAssetById**](AssetApi.md#getassetbyid) | **GET** /asset/assetById/{assetId} |  | ||||
| [**getAssetByTimeBucket**](AssetApi.md#getassetbytimebucket) | **POST** /asset/time-bucket |  | ||||
| [**getAssetCountByTimeBucket**](AssetApi.md#getassetcountbytimebucket) | **POST** /asset/count-by-time-bucket |  | ||||
| [**getAssetCountByUserId**](AssetApi.md#getassetcountbyuserid) | **GET** /asset/count-by-user-id |  | ||||
| [**getAssetSearchTerms**](AssetApi.md#getassetsearchterms) | **GET** /asset/search-terms |  | ||||
| [**getAssetThumbnail**](AssetApi.md#getassetthumbnail) | **GET** /asset/thumbnail/{assetId} |  | ||||
| [**getCuratedLocations**](AssetApi.md#getcuratedlocations) | **GET** /asset/curated-locations |  | ||||
| @@ -363,6 +364,49 @@ Name | Type | Description  | Notes | ||||
|  | ||||
| [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) | ||||
|  | ||||
| # **getAssetCountByUserId** | ||||
| > AssetCountByUserIdResponseDto getAssetCountByUserId() | ||||
|  | ||||
|  | ||||
|  | ||||
| ### Example | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| // TODO Configure HTTP Bearer authorization: bearer | ||||
| // Case 1. Use String Token | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); | ||||
| // Case 2. Use Function which generate token. | ||||
| // String yourTokenGeneratorFunction() { ... } | ||||
| //defaultApiClient.getAuthentication<HttpBearerAuth>('bearer').setAccessToken(yourTokenGeneratorFunction); | ||||
|  | ||||
| final api_instance = AssetApi(); | ||||
|  | ||||
| try { | ||||
|     final result = api_instance.getAssetCountByUserId(); | ||||
|     print(result); | ||||
| } catch (e) { | ||||
|     print('Exception when calling AssetApi->getAssetCountByUserId: $e\n'); | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Parameters | ||||
| This endpoint does not need any parameter. | ||||
|  | ||||
| ### Return type | ||||
|  | ||||
| [**AssetCountByUserIdResponseDto**](AssetCountByUserIdResponseDto.md) | ||||
|  | ||||
| ### Authorization | ||||
|  | ||||
| [bearer](../README.md#bearer) | ||||
|  | ||||
| ### HTTP request headers | ||||
|  | ||||
|  - **Content-Type**: Not defined | ||||
|  - **Accept**: application/json | ||||
|  | ||||
| [[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) | ||||
|  | ||||
| # **getAssetSearchTerms** | ||||
| > List<String> getAssetSearchTerms() | ||||
|  | ||||
|   | ||||
							
								
								
									
										16
									
								
								mobile/openapi/doc/AssetCountByUserIdResponseDto.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								mobile/openapi/doc/AssetCountByUserIdResponseDto.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,16 @@ | ||||
| # openapi.model.AssetCountByUserIdResponseDto | ||||
|  | ||||
| ## Load the model package | ||||
| ```dart | ||||
| import 'package:openapi/api.dart'; | ||||
| ``` | ||||
|  | ||||
| ## Properties | ||||
| Name | Type | Description | Notes | ||||
| ------------ | ------------- | ------------- | ------------- | ||||
| **photos** | **int** |  |  | ||||
| **videos** | **int** |  |  | ||||
|  | ||||
| [[Back to Model list]](../README.md#documentation-for-models) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to README]](../README.md) | ||||
|  | ||||
|  | ||||
| @@ -37,9 +37,11 @@ part 'api/user_api.dart'; | ||||
| part 'model/add_assets_dto.dart'; | ||||
| part 'model/add_users_dto.dart'; | ||||
| part 'model/admin_signup_response_dto.dart'; | ||||
| part 'model/album_count_response_dto.dart'; | ||||
| part 'model/album_response_dto.dart'; | ||||
| part 'model/asset_count_by_time_bucket.dart'; | ||||
| part 'model/asset_count_by_time_bucket_response_dto.dart'; | ||||
| part 'model/asset_count_by_user_id_response_dto.dart'; | ||||
| part 'model/asset_file_upload_response_dto.dart'; | ||||
| part 'model/asset_response_dto.dart'; | ||||
| part 'model/asset_type_enum.dart'; | ||||
|   | ||||
| @@ -207,6 +207,47 @@ class AlbumApi { | ||||
|     } | ||||
|   } | ||||
|  | ||||
|   /// Performs an HTTP 'GET /album/count-by-user-id' operation and returns the [Response]. | ||||
|   Future<Response> getAlbumCountByUserIdWithHttpInfo() async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/album/count-by-user-id'; | ||||
|  | ||||
|     // ignore: prefer_final_locals | ||||
|     Object? postBody; | ||||
|  | ||||
|     final queryParams = <QueryParam>[]; | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
|  | ||||
|     const contentTypes = <String>[]; | ||||
|  | ||||
|  | ||||
|     return apiClient.invokeAPI( | ||||
|       path, | ||||
|       'GET', | ||||
|       queryParams, | ||||
|       postBody, | ||||
|       headerParams, | ||||
|       formParams, | ||||
|       contentTypes.isEmpty ? null : contentTypes.first, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<AlbumCountResponseDto?> getAlbumCountByUserId() async { | ||||
|     final response = await getAlbumCountByUserIdWithHttpInfo(); | ||||
|     if (response.statusCode >= HttpStatus.badRequest) { | ||||
|       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), 'AlbumCountResponseDto',) as AlbumCountResponseDto; | ||||
|      | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   /// Performs an HTTP 'GET /album/{albumId}' operation and returns the [Response]. | ||||
|   /// Parameters: | ||||
|   /// | ||||
|   | ||||
| @@ -395,6 +395,47 @@ class AssetApi { | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   /// Performs an HTTP 'GET /asset/count-by-user-id' operation and returns the [Response]. | ||||
|   Future<Response> getAssetCountByUserIdWithHttpInfo() async { | ||||
|     // ignore: prefer_const_declarations | ||||
|     final path = r'/asset/count-by-user-id'; | ||||
|  | ||||
|     // ignore: prefer_final_locals | ||||
|     Object? postBody; | ||||
|  | ||||
|     final queryParams = <QueryParam>[]; | ||||
|     final headerParams = <String, String>{}; | ||||
|     final formParams = <String, String>{}; | ||||
|  | ||||
|     const contentTypes = <String>[]; | ||||
|  | ||||
|  | ||||
|     return apiClient.invokeAPI( | ||||
|       path, | ||||
|       'GET', | ||||
|       queryParams, | ||||
|       postBody, | ||||
|       headerParams, | ||||
|       formParams, | ||||
|       contentTypes.isEmpty ? null : contentTypes.first, | ||||
|     ); | ||||
|   } | ||||
|  | ||||
|   Future<AssetCountByUserIdResponseDto?> getAssetCountByUserId() async { | ||||
|     final response = await getAssetCountByUserIdWithHttpInfo(); | ||||
|     if (response.statusCode >= HttpStatus.badRequest) { | ||||
|       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), 'AssetCountByUserIdResponseDto',) as AssetCountByUserIdResponseDto; | ||||
|      | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   /// Performs an HTTP 'GET /asset/search-terms' operation and returns the [Response]. | ||||
|   Future<Response> getAssetSearchTermsWithHttpInfo() async { | ||||
|     // ignore: prefer_const_declarations | ||||
|   | ||||
| @@ -198,12 +198,16 @@ class ApiClient { | ||||
|           return AddUsersDto.fromJson(value); | ||||
|         case 'AdminSignupResponseDto': | ||||
|           return AdminSignupResponseDto.fromJson(value); | ||||
|         case 'AlbumCountResponseDto': | ||||
|           return AlbumCountResponseDto.fromJson(value); | ||||
|         case 'AlbumResponseDto': | ||||
|           return AlbumResponseDto.fromJson(value); | ||||
|         case 'AssetCountByTimeBucket': | ||||
|           return AssetCountByTimeBucket.fromJson(value); | ||||
|         case 'AssetCountByTimeBucketResponseDto': | ||||
|           return AssetCountByTimeBucketResponseDto.fromJson(value); | ||||
|         case 'AssetCountByUserIdResponseDto': | ||||
|           return AssetCountByUserIdResponseDto.fromJson(value); | ||||
|         case 'AssetFileUploadResponseDto': | ||||
|           return AssetFileUploadResponseDto.fromJson(value); | ||||
|         case 'AssetResponseDto': | ||||
|   | ||||
							
								
								
									
										127
									
								
								mobile/openapi/lib/model/album_count_response_dto.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								mobile/openapi/lib/model/album_count_response_dto.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
|  | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
|  | ||||
| part of openapi.api; | ||||
|  | ||||
| class AlbumCountResponseDto { | ||||
|   /// Returns a new [AlbumCountResponseDto] instance. | ||||
|   AlbumCountResponseDto({ | ||||
|     required this.owned, | ||||
|     required this.shared, | ||||
|     required this.sharing, | ||||
|   }); | ||||
|  | ||||
|   int owned; | ||||
|  | ||||
|   int shared; | ||||
|  | ||||
|   int sharing; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is AlbumCountResponseDto && | ||||
|      other.owned == owned && | ||||
|      other.shared == shared && | ||||
|      other.sharing == sharing; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (owned.hashCode) + | ||||
|     (shared.hashCode) + | ||||
|     (sharing.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'AlbumCountResponseDto[owned=$owned, shared=$shared, sharing=$sharing]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|       _json[r'owned'] = owned; | ||||
|       _json[r'shared'] = shared; | ||||
|       _json[r'sharing'] = sharing; | ||||
|     return _json; | ||||
|   } | ||||
|  | ||||
|   /// Returns a new [AlbumCountResponseDto] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static AlbumCountResponseDto? fromJson(dynamic value) { | ||||
|     if (value is Map) { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
|  | ||||
|       // Ensure that the map contains the required keys. | ||||
|       // Note 1: the values aren't checked for validity beyond being non-null. | ||||
|       // Note 2: this code is stripped in release mode! | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           assert(json.containsKey(key), 'Required key "AlbumCountResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "AlbumCountResponseDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return AlbumCountResponseDto( | ||||
|         owned: mapValueOfType<int>(json, r'owned')!, | ||||
|         shared: mapValueOfType<int>(json, r'shared')!, | ||||
|         sharing: mapValueOfType<int>(json, r'sharing')!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<AlbumCountResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <AlbumCountResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = AlbumCountResponseDto.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
|  | ||||
|   static Map<String, AlbumCountResponseDto> mapFromJson(dynamic json) { | ||||
|     final map = <String, AlbumCountResponseDto>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AlbumCountResponseDto.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of AlbumCountResponseDto-objects as value to a dart map | ||||
|   static Map<String, List<AlbumCountResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<AlbumCountResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AlbumCountResponseDto.listFromJson(entry.value, growable: growable,); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'owned', | ||||
|     'shared', | ||||
|     'sharing', | ||||
|   }; | ||||
| } | ||||
|  | ||||
| @@ -0,0 +1,119 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
|  | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
|  | ||||
| part of openapi.api; | ||||
|  | ||||
| class AssetCountByUserIdResponseDto { | ||||
|   /// Returns a new [AssetCountByUserIdResponseDto] instance. | ||||
|   AssetCountByUserIdResponseDto({ | ||||
|     required this.photos, | ||||
|     required this.videos, | ||||
|   }); | ||||
|  | ||||
|   int photos; | ||||
|  | ||||
|   int videos; | ||||
|  | ||||
|   @override | ||||
|   bool operator ==(Object other) => identical(this, other) || other is AssetCountByUserIdResponseDto && | ||||
|      other.photos == photos && | ||||
|      other.videos == videos; | ||||
|  | ||||
|   @override | ||||
|   int get hashCode => | ||||
|     // ignore: unnecessary_parenthesis | ||||
|     (photos.hashCode) + | ||||
|     (videos.hashCode); | ||||
|  | ||||
|   @override | ||||
|   String toString() => 'AssetCountByUserIdResponseDto[photos=$photos, videos=$videos]'; | ||||
|  | ||||
|   Map<String, dynamic> toJson() { | ||||
|     final _json = <String, dynamic>{}; | ||||
|       _json[r'photos'] = photos; | ||||
|       _json[r'videos'] = videos; | ||||
|     return _json; | ||||
|   } | ||||
|  | ||||
|   /// Returns a new [AssetCountByUserIdResponseDto] instance and imports its values from | ||||
|   /// [value] if it's a [Map], null otherwise. | ||||
|   // ignore: prefer_constructors_over_static_methods | ||||
|   static AssetCountByUserIdResponseDto? fromJson(dynamic value) { | ||||
|     if (value is Map) { | ||||
|       final json = value.cast<String, dynamic>(); | ||||
|  | ||||
|       // Ensure that the map contains the required keys. | ||||
|       // Note 1: the values aren't checked for validity beyond being non-null. | ||||
|       // Note 2: this code is stripped in release mode! | ||||
|       assert(() { | ||||
|         requiredKeys.forEach((key) { | ||||
|           assert(json.containsKey(key), 'Required key "AssetCountByUserIdResponseDto[$key]" is missing from JSON.'); | ||||
|           assert(json[key] != null, 'Required key "AssetCountByUserIdResponseDto[$key]" has a null value in JSON.'); | ||||
|         }); | ||||
|         return true; | ||||
|       }()); | ||||
|  | ||||
|       return AssetCountByUserIdResponseDto( | ||||
|         photos: mapValueOfType<int>(json, r'photos')!, | ||||
|         videos: mapValueOfType<int>(json, r'videos')!, | ||||
|       ); | ||||
|     } | ||||
|     return null; | ||||
|   } | ||||
|  | ||||
|   static List<AssetCountByUserIdResponseDto>? listFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final result = <AssetCountByUserIdResponseDto>[]; | ||||
|     if (json is List && json.isNotEmpty) { | ||||
|       for (final row in json) { | ||||
|         final value = AssetCountByUserIdResponseDto.fromJson(row); | ||||
|         if (value != null) { | ||||
|           result.add(value); | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return result.toList(growable: growable); | ||||
|   } | ||||
|  | ||||
|   static Map<String, AssetCountByUserIdResponseDto> mapFromJson(dynamic json) { | ||||
|     final map = <String, AssetCountByUserIdResponseDto>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AssetCountByUserIdResponseDto.fromJson(entry.value); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   // maps a json object with a list of AssetCountByUserIdResponseDto-objects as value to a dart map | ||||
|   static Map<String, List<AssetCountByUserIdResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) { | ||||
|     final map = <String, List<AssetCountByUserIdResponseDto>>{}; | ||||
|     if (json is Map && json.isNotEmpty) { | ||||
|       json = json.cast<String, dynamic>(); // ignore: parameter_assignments | ||||
|       for (final entry in json.entries) { | ||||
|         final value = AssetCountByUserIdResponseDto.listFromJson(entry.value, growable: growable,); | ||||
|         if (value != null) { | ||||
|           map[entry.key] = value; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
|     return map; | ||||
|   } | ||||
|  | ||||
|   /// The list of required keys that must be present in a JSON. | ||||
|   static const requiredKeys = <String>{ | ||||
|     'photos', | ||||
|     'videos', | ||||
|   }; | ||||
| } | ||||
|  | ||||
							
								
								
									
										37
									
								
								mobile/openapi/test/album_count_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								mobile/openapi/test/album_count_response_dto_test.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,37 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
|  | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
|  | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:test/test.dart'; | ||||
|  | ||||
| // tests for AlbumCountResponseDto | ||||
| void main() { | ||||
|   // final instance = AlbumCountResponseDto(); | ||||
|  | ||||
|   group('test AlbumCountResponseDto', () { | ||||
|     // int owned | ||||
|     test('to test the property `owned`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|     // int shared | ||||
|     test('to test the property `shared`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|     // int sharing | ||||
|     test('to test the property `sharing`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|  | ||||
|   }); | ||||
|  | ||||
| } | ||||
| @@ -0,0 +1,32 @@ | ||||
| // | ||||
| // AUTO-GENERATED FILE, DO NOT MODIFY! | ||||
| // | ||||
| // @dart=2.12 | ||||
|  | ||||
| // ignore_for_file: unused_element, unused_import | ||||
| // ignore_for_file: always_put_required_named_parameters_first | ||||
| // ignore_for_file: constant_identifier_names | ||||
| // ignore_for_file: lines_longer_than_80_chars | ||||
|  | ||||
| import 'package:openapi/api.dart'; | ||||
| import 'package:test/test.dart'; | ||||
|  | ||||
| // tests for AssetCountByUserIdResponseDto | ||||
| void main() { | ||||
|   // final instance = AssetCountByUserIdResponseDto(); | ||||
|  | ||||
|   group('test AssetCountByUserIdResponseDto', () { | ||||
|     // int photos | ||||
|     test('to test the property `photos`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|     // int videos | ||||
|     test('to test the property `videos`', () async { | ||||
|       // TODO | ||||
|     }); | ||||
|  | ||||
|  | ||||
|   }); | ||||
|  | ||||
| } | ||||
| @@ -10,6 +10,7 @@ import { CreateAlbumDto } from './dto/create-album.dto'; | ||||
| import { GetAlbumsDto } from './dto/get-albums.dto'; | ||||
| import { RemoveAssetsDto } from './dto/remove-assets.dto'; | ||||
| import { UpdateAlbumDto } from './dto/update-album.dto'; | ||||
| import { AlbumCountResponseDto } from './response-dto/album-count-response.dto'; | ||||
| import { AlbumResponseDto } from './response-dto/album-response.dto'; | ||||
|  | ||||
| export interface IAlbumRepository { | ||||
| @@ -23,6 +24,7 @@ export interface IAlbumRepository { | ||||
|   addAssets(album: AlbumEntity, addAssetsDto: AddAssetsDto): Promise<AlbumEntity>; | ||||
|   updateAlbum(album: AlbumEntity, updateAlbumDto: UpdateAlbumDto): Promise<AlbumEntity>; | ||||
|   getListByAssetId(userId: string, assetId: string): Promise<AlbumEntity[]>; | ||||
|   getCountByUserId(userId: string): Promise<AlbumCountResponseDto>; | ||||
| } | ||||
|  | ||||
| export const ALBUM_REPOSITORY = 'ALBUM_REPOSITORY'; | ||||
| @@ -42,6 +44,18 @@ export class AlbumRepository implements IAlbumRepository { | ||||
|     private dataSource: DataSource, | ||||
|   ) {} | ||||
|  | ||||
|   async getCountByUserId(userId: string): Promise<AlbumCountResponseDto> { | ||||
|     const ownedAlbums = await this.albumRepository.find({ where: { ownerId: userId }, relations: ['sharedUsers'] }); | ||||
|  | ||||
|     const sharedAlbums = await this.userAlbumRepository.count({ | ||||
|       where: { sharedUserId: userId }, | ||||
|     }); | ||||
|  | ||||
|     const sharingAlbums = ownedAlbums.map((album) => album.sharedUsers?.length || 0).reduce((a, b) => a + b, 0); | ||||
|  | ||||
|     return new AlbumCountResponseDto(ownedAlbums.length, sharedAlbums, sharingAlbums); | ||||
|   } | ||||
|  | ||||
|   async create(ownerId: string, createAlbumDto: CreateAlbumDto): Promise<AlbumEntity> { | ||||
|     return this.dataSource.transaction(async (transactionalEntityManager) => { | ||||
|       // Create album entity | ||||
| @@ -228,9 +242,10 @@ export class AlbumRepository implements IAlbumRepository { | ||||
|  | ||||
|     // TODO: No need to return boolean if using a singe delete query | ||||
|     if (deleteAssetCount == removeAssetsDto.assetIds.length) { | ||||
|       const retAlbum = await this.get(album.id) as AlbumEntity; | ||||
|       const retAlbum = (await this.get(album.id)) as AlbumEntity; | ||||
|  | ||||
|       if (retAlbum?.assets?.length === 0) { // is empty album | ||||
|       if (retAlbum?.assets?.length === 0) { | ||||
|         // is empty album | ||||
|         await this.albumRepository.update(album.id, { albumThumbnailAssetId: null }); | ||||
|         retAlbum.albumThumbnailAssetId = null; | ||||
|       } | ||||
|   | ||||
| @@ -11,6 +11,7 @@ import { | ||||
|   ParseUUIDPipe, | ||||
|   Put, | ||||
|   Query, | ||||
|   Header, | ||||
| } from '@nestjs/common'; | ||||
| import { ParseMeUUIDPipe } from '../validation/parse-me-uuid-pipe'; | ||||
| import { AlbumService } from './album.service'; | ||||
| @@ -24,6 +25,7 @@ import { UpdateAlbumDto } from './dto/update-album.dto'; | ||||
| import { GetAlbumsDto } from './dto/get-albums.dto'; | ||||
| import { ApiBearerAuth, ApiTags } from '@nestjs/swagger'; | ||||
| import { AlbumResponseDto } from './response-dto/album-response.dto'; | ||||
| import { AlbumCountResponseDto } from './response-dto/album-count-response.dto'; | ||||
|  | ||||
| // TODO might be worth creating a AlbumParamsDto that validates `albumId` instead of using the pipe. | ||||
| @UseGuards(JwtAuthGuard) | ||||
| @@ -33,6 +35,11 @@ import { AlbumResponseDto } from './response-dto/album-response.dto'; | ||||
| export class AlbumController { | ||||
|   constructor(private readonly albumService: AlbumService) {} | ||||
|  | ||||
|   @Get('count-by-user-id') | ||||
|   async getAlbumCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AlbumCountResponseDto> { | ||||
|     return this.albumService.getAlbumCountByUserId(authUser); | ||||
|   } | ||||
|  | ||||
|   @Post() | ||||
|   async createAlbum(@GetAuthUser() authUser: AuthUserDto, @Body(ValidationPipe) createAlbumDto: CreateAlbumDto) { | ||||
|     return this.albumService.create(authUser, createAlbumDto); | ||||
|   | ||||
| @@ -116,7 +116,8 @@ describe('Album service', () => { | ||||
|       removeAssets: jest.fn(), | ||||
|       removeUser: jest.fn(), | ||||
|       updateAlbum: jest.fn(), | ||||
|       getListByAssetId: jest.fn() | ||||
|       getListByAssetId: jest.fn(), | ||||
|       getCountByUserId: jest.fn(), | ||||
|     }; | ||||
|     sut = new AlbumService(albumRepositoryMock); | ||||
|   }); | ||||
|   | ||||
| @@ -9,6 +9,7 @@ import { UpdateAlbumDto } from './dto/update-album.dto'; | ||||
| import { GetAlbumsDto } from './dto/get-albums.dto'; | ||||
| import { AlbumResponseDto, mapAlbum, mapAlbumExcludeAssetInfo } from './response-dto/album-response.dto'; | ||||
| import { ALBUM_REPOSITORY, IAlbumRepository } from './album-repository'; | ||||
| import { AlbumCountResponseDto } from './response-dto/album-count-response.dto'; | ||||
|  | ||||
| @Injectable() | ||||
| export class AlbumService { | ||||
| @@ -118,4 +119,8 @@ export class AlbumService { | ||||
|     const updatedAlbum = await this._albumRepository.updateAlbum(album, updateAlbumDto); | ||||
|     return mapAlbum(updatedAlbum); | ||||
|   } | ||||
|  | ||||
|   async getAlbumCountByUserId(authUser: AuthUserDto): Promise<AlbumCountResponseDto> { | ||||
|     return this._albumRepository.getCountByUserId(authUser.id); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,18 @@ | ||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||
|  | ||||
| export class AlbumCountResponseDto { | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   owned!: number; | ||||
|  | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   shared!: number; | ||||
|  | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   sharing!: number; | ||||
|  | ||||
|   constructor(owned: number, shared: number, sharing: number) { | ||||
|     this.owned = owned; | ||||
|     this.shared = shared; | ||||
|     this.sharing = sharing; | ||||
|   } | ||||
| } | ||||
| @@ -9,6 +9,7 @@ import { CuratedObjectsResponseDto } from './response-dto/curated-objects-respon | ||||
| import { AssetCountByTimeBucket } from './response-dto/asset-count-by-time-group-response.dto'; | ||||
| import { TimeGroupEnum } from './dto/get-asset-count-by-time-bucket.dto'; | ||||
| import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; | ||||
| import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; | ||||
|  | ||||
| export interface IAssetRepository { | ||||
|   create( | ||||
| @@ -25,6 +26,7 @@ export interface IAssetRepository { | ||||
|   getDetectedObjectsByUserId(userId: string): Promise<CuratedObjectsResponseDto[]>; | ||||
|   getSearchPropertiesByUserId(userId: string): Promise<SearchPropertiesDto[]>; | ||||
|   getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum): Promise<AssetCountByTimeBucket[]>; | ||||
|   getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto>; | ||||
|   getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]>; | ||||
|   getAssetByChecksum(userId: string, checksum: Buffer): Promise<AssetEntity>; | ||||
| } | ||||
| @@ -38,6 +40,28 @@ export class AssetRepository implements IAssetRepository { | ||||
|     private assetRepository: Repository<AssetEntity>, | ||||
|   ) {} | ||||
|  | ||||
|   async getAssetCountByUserId(userId: string): Promise<AssetCountByUserIdResponseDto> { | ||||
|     // Get asset count by AssetType | ||||
|     const res = await this.assetRepository | ||||
|       .createQueryBuilder('asset') | ||||
|       .select(`COUNT(asset.id)`, 'count') | ||||
|       .addSelect(`asset.type`, 'type') | ||||
|       .where('"userId" = :userId', { userId: userId }) | ||||
|       .groupBy('asset.type') | ||||
|       .getRawMany(); | ||||
|  | ||||
|     const assetCountByUserId = new AssetCountByUserIdResponseDto(0, 0); | ||||
|     res.map((item) => { | ||||
|       if (item.type === 'IMAGE') { | ||||
|         assetCountByUserId.photos = item.count; | ||||
|       } else if (item.type === 'VIDEO') { | ||||
|         assetCountByUserId.videos = item.count; | ||||
|       } | ||||
|     }); | ||||
|  | ||||
|     return assetCountByUserId; | ||||
|   } | ||||
|  | ||||
|   async getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise<AssetEntity[]> { | ||||
|     // Get asset entity from a list of time buckets | ||||
|     return await this.assetRepository | ||||
|   | ||||
| @@ -48,6 +48,7 @@ import { AssetCountByTimeBucketResponseDto } from './response-dto/asset-count-by | ||||
| import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto'; | ||||
| import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; | ||||
| import { QueryFailedError } from 'typeorm'; | ||||
| import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; | ||||
|  | ||||
| @UseGuards(JwtAuthGuard) | ||||
| @ApiBearerAuth() | ||||
| @@ -78,7 +79,13 @@ export class AssetController { | ||||
|     const checksum = await this.assetService.calculateChecksum(file.path); | ||||
|  | ||||
|     try { | ||||
|       const savedAsset = await this.assetService.createUserAsset(authUser, assetInfo, file.path, file.mimetype, checksum); | ||||
|       const savedAsset = await this.assetService.createUserAsset( | ||||
|         authUser, | ||||
|         assetInfo, | ||||
|         file.path, | ||||
|         file.mimetype, | ||||
|         checksum, | ||||
|       ); | ||||
|  | ||||
|       if (!savedAsset) { | ||||
|         await this.backgroundTaskService.deleteFileOnDisk([ | ||||
| @@ -104,7 +111,7 @@ export class AssetController { | ||||
|       ]); // simulate asset to make use of delete queue (or use fs.unlink instead) | ||||
|  | ||||
|       if (err instanceof QueryFailedError && (err as any).constraint === 'UQ_userid_checksum') { | ||||
|         const existedAsset = await this.assetService.getAssetByChecksum(authUser.id, checksum) | ||||
|         const existedAsset = await this.assetService.getAssetByChecksum(authUser.id, checksum); | ||||
|         return new AssetFileUploadResponseDto(existedAsset.id); | ||||
|       } | ||||
|  | ||||
| @@ -172,6 +179,10 @@ export class AssetController { | ||||
|     return this.assetService.getAssetCountByTimeBucket(authUser, getAssetCountByTimeGroupDto); | ||||
|   } | ||||
|  | ||||
|   @Get('/count-by-user-id') | ||||
|   async getAssetCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> { | ||||
|     return this.assetService.getAssetCountByUserId(authUser); | ||||
|   } | ||||
|   /** | ||||
|    * Get all AssetEntity belong to the user | ||||
|    */ | ||||
|   | ||||
| @@ -61,6 +61,7 @@ describe('AssetService', () => { | ||||
|       getSearchPropertiesByUserId: jest.fn(), | ||||
|       getAssetByTimeBucket: jest.fn(), | ||||
|       getAssetByChecksum: jest.fn(), | ||||
|       getAssetCountByUserId: jest.fn(), | ||||
|     }; | ||||
|  | ||||
|     sui = new AssetService(assetRepositoryMock, a); | ||||
|   | ||||
| @@ -35,6 +35,7 @@ import { | ||||
| } from './response-dto/asset-count-by-time-group-response.dto'; | ||||
| import { GetAssetCountByTimeBucketDto } from './dto/get-asset-count-by-time-bucket.dto'; | ||||
| import { GetAssetByTimeBucketDto } from './dto/get-asset-by-time-bucket.dto'; | ||||
| import { AssetCountByUserIdResponseDto } from './response-dto/asset-count-by-user-id-response.dto'; | ||||
|  | ||||
| const fileInfo = promisify(stat); | ||||
|  | ||||
| @@ -479,4 +480,8 @@ export class AssetService { | ||||
|     fileReadStream.pipe(sha1Hash); | ||||
|     return deferred; | ||||
|   } | ||||
|  | ||||
|   getAssetCountByUserId(authUser: AuthUserDto): Promise<AssetCountByUserIdResponseDto> { | ||||
|     return this._assetRepository.getAssetCountByUserId(authUser.id); | ||||
|   } | ||||
| } | ||||
|   | ||||
| @@ -0,0 +1,14 @@ | ||||
| import { ApiProperty } from '@nestjs/swagger'; | ||||
|  | ||||
| export class AssetCountByUserIdResponseDto { | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   photos!: number; | ||||
|  | ||||
|   @ApiProperty({ type: 'integer' }) | ||||
|   videos!: number; | ||||
|  | ||||
|   constructor(photos: number, videos: number) { | ||||
|     this.photos = photos; | ||||
|     this.videos = videos; | ||||
|   } | ||||
| } | ||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -84,6 +84,31 @@ export interface AdminSignupResponseDto { | ||||
|      */ | ||||
|     'createdAt': string; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface AlbumCountResponseDto | ||||
|  */ | ||||
| export interface AlbumCountResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof AlbumCountResponseDto | ||||
|      */ | ||||
|     'owned': number; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof AlbumCountResponseDto | ||||
|      */ | ||||
|     'shared': number; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof AlbumCountResponseDto | ||||
|      */ | ||||
|     'sharing': number; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -183,6 +208,25 @@ export interface AssetCountByTimeBucketResponseDto { | ||||
|      */ | ||||
|     'buckets': Array<AssetCountByTimeBucket>; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
|  * @interface AssetCountByUserIdResponseDto | ||||
|  */ | ||||
| export interface AssetCountByUserIdResponseDto { | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof AssetCountByUserIdResponseDto | ||||
|      */ | ||||
|     'photos': number; | ||||
|     /** | ||||
|      *  | ||||
|      * @type {number} | ||||
|      * @memberof AssetCountByUserIdResponseDto | ||||
|      */ | ||||
|     'videos': number; | ||||
| } | ||||
| /** | ||||
|  *  | ||||
|  * @export | ||||
| @@ -1408,6 +1452,39 @@ export const AlbumApiAxiosParamCreator = function (configuration?: Configuration | ||||
|  | ||||
|  | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
|  | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getAlbumCountByUserId: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             const localVarPath = `/album/count-by-user-id`; | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs. | ||||
|             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||
|             let baseOptions; | ||||
|             if (configuration) { | ||||
|                 baseOptions = configuration.baseOptions; | ||||
|             } | ||||
|  | ||||
|             const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; | ||||
|             const localVarHeaderParameter = {} as any; | ||||
|             const localVarQueryParameter = {} as any; | ||||
|  | ||||
|             // authentication bearer required | ||||
|             // http bearer authentication required | ||||
|             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||
|  | ||||
|  | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
| @@ -1676,6 +1753,15 @@ export const AlbumApiFp = function(configuration?: Configuration) { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.deleteAlbum(albumId, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async getAlbumCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AlbumCountResponseDto>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getAlbumCountByUserId(options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} albumId  | ||||
| @@ -1778,6 +1864,14 @@ export const AlbumApiFactory = function (configuration?: Configuration, basePath | ||||
|         deleteAlbum(albumId: string, options?: any): AxiosPromise<void> { | ||||
|             return localVarFp.deleteAlbum(albumId, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getAlbumCountByUserId(options?: any): AxiosPromise<AlbumCountResponseDto> { | ||||
|             return localVarFp.getAlbumCountByUserId(options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {string} albumId  | ||||
| @@ -1883,6 +1977,16 @@ export class AlbumApi extends BaseAPI { | ||||
|         return AlbumApiFp(this.configuration).deleteAlbum(albumId, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  | ||||
|      * @param {*} [options] Override http request option. | ||||
|      * @throws {RequiredError} | ||||
|      * @memberof AlbumApi | ||||
|      */ | ||||
|     public getAlbumCountByUserId(options?: AxiosRequestConfig) { | ||||
|         return AlbumApiFp(this.configuration).getAlbumCountByUserId(options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  | ||||
|      * @param {string} albumId  | ||||
| @@ -2236,6 +2340,39 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getAssetCountByUserId: async (options: AxiosRequestConfig = {}): Promise<RequestArgs> => { | ||||
|             const localVarPath = `/asset/count-by-user-id`; | ||||
|             // use dummy base URL string because the URL constructor only accepts absolute URLs. | ||||
|             const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); | ||||
|             let baseOptions; | ||||
|             if (configuration) { | ||||
|                 baseOptions = configuration.baseOptions; | ||||
|             } | ||||
|  | ||||
|             const localVarRequestOptions = { method: 'GET', ...baseOptions, ...options}; | ||||
|             const localVarHeaderParameter = {} as any; | ||||
|             const localVarQueryParameter = {} as any; | ||||
|  | ||||
|             // authentication bearer required | ||||
|             // http bearer authentication required | ||||
|             await setBearerAuthToObject(localVarHeaderParameter, configuration) | ||||
|  | ||||
|  | ||||
|      | ||||
|             setSearchParams(localVarUrlObj, localVarQueryParameter); | ||||
|             let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; | ||||
|             localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; | ||||
|  | ||||
|             return { | ||||
|                 url: toPathString(localVarUrlObj), | ||||
|                 options: localVarRequestOptions, | ||||
|             }; | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
| @@ -2640,6 +2777,15 @@ export const AssetApiFp = function(configuration?: Configuration) { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         async getAssetCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise<AssetCountByUserIdResponseDto>> { | ||||
|             const localVarAxiosArgs = await localVarAxiosParamCreator.getAssetCountByUserId(options); | ||||
|             return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
| @@ -2800,6 +2946,14 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath | ||||
|         getAssetCountByTimeBucket(getAssetCountByTimeBucketDto: GetAssetCountByTimeBucketDto, options?: any): AxiosPromise<AssetCountByTimeBucketResponseDto> { | ||||
|             return localVarFp.getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
|          * @throws {RequiredError} | ||||
|          */ | ||||
|         getAssetCountByUserId(options?: any): AxiosPromise<AssetCountByUserIdResponseDto> { | ||||
|             return localVarFp.getAssetCountByUserId(options).then((request) => request(axios, basePath)); | ||||
|         }, | ||||
|         /** | ||||
|          *  | ||||
|          * @param {*} [options] Override http request option. | ||||
| @@ -2966,6 +3120,16 @@ export class AssetApi extends BaseAPI { | ||||
|         return AssetApiFp(this.configuration).getAssetCountByTimeBucket(getAssetCountByTimeBucketDto, options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  | ||||
|      * @param {*} [options] Override http request option. | ||||
|      * @throws {RequiredError} | ||||
|      * @memberof AssetApi | ||||
|      */ | ||||
|     public getAssetCountByUserId(options?: AxiosRequestConfig) { | ||||
|         return AssetApiFp(this.configuration).getAssetCountByUserId(options).then((request) => request(this.axios, this.basePath)); | ||||
|     } | ||||
|  | ||||
|     /** | ||||
|      *  | ||||
|      * @param {*} [options] Override http request option. | ||||
|   | ||||
| @@ -5,11 +5,19 @@ | ||||
| 	import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte'; | ||||
| 	import ImageOutline from 'svelte-material-icons/ImageOutline.svelte'; | ||||
| 	import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte'; | ||||
| 	import InformationOutline from 'svelte-material-icons/InformationOutline.svelte'; | ||||
| 	import SideBarButton from './side-bar-button.svelte'; | ||||
| 	import StatusBox from '../status-box.svelte'; | ||||
| 	import { AlbumCountResponseDto, api, AssetCountByUserIdResponseDto } from '@api'; | ||||
| 	import { fade } from 'svelte/transition'; | ||||
| 	import LoadingSpinner from '../loading-spinner.svelte'; | ||||
|  | ||||
| 	let selectedAction: AppSideBarSelection; | ||||
|  | ||||
| 	let showAssetCount: boolean = false; | ||||
| 	let showSharingCount = false; | ||||
| 	let showAlbumsCount = false; | ||||
|  | ||||
| 	onMount(async () => { | ||||
| 		if ($page.routeId == 'albums') { | ||||
| 			selectedAction = AppSideBarSelection.ALBUMS; | ||||
| @@ -19,35 +27,130 @@ | ||||
| 			selectedAction = AppSideBarSelection.SHARING; | ||||
| 		} | ||||
| 	}); | ||||
|  | ||||
| 	const getAssetCount = async () => { | ||||
| 		const { data: assetCount } = await api.assetApi.getAssetCountByUserId(); | ||||
|  | ||||
| 		return { | ||||
| 			videos: assetCount.videos, | ||||
| 			photos: assetCount.photos | ||||
| 		}; | ||||
| 	}; | ||||
|  | ||||
| 	const getAlbumCount = async () => { | ||||
| 		const { data: albumCount } = await api.albumApi.getAlbumCountByUserId(); | ||||
| 		return { | ||||
| 			shared: albumCount.shared, | ||||
| 			sharing: albumCount.sharing, | ||||
| 			owned: albumCount.owned | ||||
| 		}; | ||||
| 	}; | ||||
| </script> | ||||
|  | ||||
| <section id="sidebar" class="flex flex-col gap-1 pt-8 pr-6"> | ||||
| 	<a sveltekit:prefetch sveltekit:noscroll href={$page.routeId !== 'photos' ? `/photos` : null}> | ||||
| 	<a | ||||
| 		sveltekit:prefetch | ||||
| 		sveltekit:noscroll | ||||
| 		href={$page.routeId !== 'photos' ? `/photos` : null} | ||||
| 		class="relative" | ||||
| 	> | ||||
| 		<SideBarButton | ||||
| 			title="Photos" | ||||
| 			title={`Photos`} | ||||
| 			logo={ImageOutline} | ||||
| 			actionType={AppSideBarSelection.PHOTOS} | ||||
| 			isSelected={selectedAction === AppSideBarSelection.PHOTOS} | ||||
| 		/></a | ||||
| 		/> | ||||
| 		<div | ||||
| 			id="asset-count-info" | ||||
| 			class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help" | ||||
| 			on:mouseenter={() => (showAssetCount = true)} | ||||
| 			on:mouseleave={() => (showAssetCount = false)} | ||||
| 		> | ||||
| 	<a sveltekit:prefetch href={$page.routeId !== 'sharing' ? `/sharing` : null}> | ||||
| 			<InformationOutline size={18} color="#4250af" /> | ||||
| 			{#if showAssetCount} | ||||
| 				<div | ||||
| 					transition:fade={{ duration: 200 }} | ||||
| 					id="asset-count-info-detail" | ||||
| 					class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center" | ||||
| 				> | ||||
| 					{#await getAssetCount()} | ||||
| 						<LoadingSpinner /> | ||||
| 					{:then data} | ||||
| 						<div> | ||||
| 							<p>{data.videos} Videos</p> | ||||
| 							<p>{data.photos} Photos</p> | ||||
| 						</div> | ||||
| 					{/await} | ||||
| 				</div> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	</a> | ||||
|  | ||||
| 	<a sveltekit:prefetch href={$page.routeId !== 'sharing' ? `/sharing` : null} class="relative"> | ||||
| 		<SideBarButton | ||||
| 			title="Sharing" | ||||
| 			logo={AccountMultipleOutline} | ||||
| 			actionType={AppSideBarSelection.SHARING} | ||||
| 			isSelected={selectedAction === AppSideBarSelection.SHARING} | ||||
| 		/></a | ||||
| 		/> | ||||
| 		<div | ||||
| 			id="sharing-count-info" | ||||
| 			class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help" | ||||
| 			on:mouseenter={() => (showSharingCount = true)} | ||||
| 			on:mouseleave={() => (showSharingCount = false)} | ||||
| 		> | ||||
| 			<InformationOutline size={18} color="#4250af" /> | ||||
| 			{#if showSharingCount} | ||||
| 				<div | ||||
| 					transition:fade={{ duration: 200 }} | ||||
| 					id="asset-count-info-detail" | ||||
| 					class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center" | ||||
| 				> | ||||
| 					{#await getAlbumCount()} | ||||
| 						<LoadingSpinner /> | ||||
| 					{:then data} | ||||
| 						<div> | ||||
| 							<p>{data.shared + data.sharing} albums</p> | ||||
| 						</div> | ||||
| 					{/await} | ||||
| 				</div> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	</a> | ||||
| 	<div class="text-xs ml-5 my-4"> | ||||
| 		<p>LIBRARY</p> | ||||
| 	</div> | ||||
| 	<a sveltekit:prefetch href={$page.routeId !== 'albums' ? `/albums` : null}> | ||||
| 	<a sveltekit:prefetch href={$page.routeId !== 'albums' ? `/albums` : null} class="relative"> | ||||
| 		<SideBarButton | ||||
| 			title="Albums" | ||||
| 			logo={ImageAlbum} | ||||
| 			actionType={AppSideBarSelection.ALBUMS} | ||||
| 			isSelected={selectedAction === AppSideBarSelection.ALBUMS} | ||||
| 		/> | ||||
|  | ||||
| 		<div | ||||
| 			id="album-count-info" | ||||
| 			class="absolute right-4 top-[15px] z-40 text-xs hover:cursor-help" | ||||
| 			on:mouseenter={() => (showAlbumsCount = true)} | ||||
| 			on:mouseleave={() => (showAlbumsCount = false)} | ||||
| 		> | ||||
| 			<InformationOutline size={18} color="#4250af" /> | ||||
| 			{#if showAlbumsCount} | ||||
| 				<div | ||||
| 					transition:fade={{ duration: 200 }} | ||||
| 					id="asset-count-info-detail" | ||||
| 					class="w-32 rounded-lg px-4 py-2 shadow-lg bg-white absolute -right-[135px] top-0 z-[9999] flex place-items-center place-content-center" | ||||
| 				> | ||||
| 					{#await getAlbumCount()} | ||||
| 						<LoadingSpinner /> | ||||
| 					{:then data} | ||||
| 						<div> | ||||
| 							<p>{data.owned} albums</p> | ||||
| 						</div> | ||||
| 					{/await} | ||||
| 				</div> | ||||
| 			{/if} | ||||
| 		</div> | ||||
| 	</a> | ||||
|  | ||||
| 	<!-- Status Box --> | ||||
|   | ||||
| @@ -26,14 +26,6 @@ | ||||
|  | ||||
| 	export let data: PageData; | ||||
|  | ||||
| 	onMount(async () => { | ||||
| 		openWebsocketConnection(); | ||||
|  | ||||
| 		return () => { | ||||
| 			closeWebsocketConnection(); | ||||
| 		}; | ||||
| 	}); | ||||
|  | ||||
| 	const deleteSelectedAssetHandler = async () => { | ||||
| 		try { | ||||
| 			if ( | ||||
|   | ||||
		Reference in New Issue
	
	Block a user