diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 1dbcfdfb0a3f1..80610bf6a1757 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -94,6 +94,7 @@ Class | Method | HTTP request | Description *AssetApi* | [**downloadFiles**](doc//AssetApi.md#downloadfiles) | **POST** /asset/download-files | *AssetApi* | [**downloadLibrary**](doc//AssetApi.md#downloadlibrary) | **GET** /asset/download-library | *AssetApi* | [**getAllAssets**](doc//AssetApi.md#getallassets) | **GET** /asset | +*AssetApi* | [**getArchivedAssetCountByUserId**](doc//AssetApi.md#getarchivedassetcountbyuserid) | **GET** /asset/stat/archive | *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 | diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index de0ad6a8b56b8..2012f41c06c2a 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -18,6 +18,7 @@ Method | HTTP request | Description [**downloadFiles**](AssetApi.md#downloadfiles) | **POST** /asset/download-files | [**downloadLibrary**](AssetApi.md#downloadlibrary) | **GET** /asset/download-library | [**getAllAssets**](AssetApi.md#getallassets) | **GET** /asset | +[**getArchivedAssetCountByUserId**](AssetApi.md#getarchivedassetcountbyuserid) | **GET** /asset/stat/archive | [**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 | @@ -471,7 +472,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **getAllAssets** -> List getAllAssets(isFavorite, skip, ifNoneMatch) +> List getAllAssets(isFavorite, isArchived, skip, ifNoneMatch) @@ -493,11 +494,12 @@ import 'package:openapi/api.dart'; final api_instance = AssetApi(); final isFavorite = true; // bool | +final isArchived = true; // bool | final skip = 8.14; // num | final ifNoneMatch = ifNoneMatch_example; // String | ETag of data already cached on the client try { - final result = api_instance.getAllAssets(isFavorite, skip, ifNoneMatch); + final result = api_instance.getAllAssets(isFavorite, isArchived, skip, ifNoneMatch); print(result); } catch (e) { print('Exception when calling AssetApi->getAllAssets: $e\n'); @@ -509,6 +511,7 @@ try { Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **isFavorite** | **bool**| | [optional] + **isArchived** | **bool**| | [optional] **skip** | **num**| | [optional] **ifNoneMatch** | **String**| ETag of data already cached on the client | [optional] @@ -527,6 +530,55 @@ 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) +# **getArchivedAssetCountByUserId** +> AssetCountByUserIdResponseDto getArchivedAssetCountByUserId() + + + + + +### Example +```dart +import 'package:openapi/api.dart'; +// TODO Configure API key authorization: cookie +//defaultApiClient.getAuthentication('cookie').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('cookie').apiKeyPrefix = 'Bearer'; +// TODO Configure HTTP Bearer authorization: bearer +// Case 1. Use String Token +//defaultApiClient.getAuthentication('bearer').setAccessToken('YOUR_ACCESS_TOKEN'); +// Case 2. Use Function which generate token. +// String yourTokenGeneratorFunction() { ... } +//defaultApiClient.getAuthentication('bearer').setAccessToken(yourTokenGeneratorFunction); + +final api_instance = AssetApi(); + +try { + final result = api_instance.getArchivedAssetCountByUserId(); + print(result); +} catch (e) { + print('Exception when calling AssetApi->getArchivedAssetCountByUserId: $e\n'); +} +``` + +### Parameters +This endpoint does not need any parameter. + +### Return type + +[**AssetCountByUserIdResponseDto**](AssetCountByUserIdResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [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) + # **getAssetById** > AssetResponseDto getAssetById(assetId, key) @@ -1217,7 +1269,7 @@ Name | Type | Description | Notes [[Back to top]](#) [[Back to API list]](../README.md#documentation-for-api-endpoints) [[Back to Model list]](../README.md#documentation-for-models) [[Back to README]](../README.md) # **uploadFile** -> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isVisible, duration) +> AssetFileUploadResponseDto uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isArchived, isVisible, duration) @@ -1248,11 +1300,12 @@ final isFavorite = true; // bool | final fileExtension = fileExtension_example; // String | final key = key_example; // String | final livePhotoData = BINARY_DATA_HERE; // MultipartFile | +final isArchived = true; // bool | final isVisible = true; // bool | final duration = duration_example; // String | try { - final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isVisible, duration); + final result = api_instance.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isArchived, isVisible, duration); print(result); } catch (e) { print('Exception when calling AssetApi->uploadFile: $e\n'); @@ -1273,6 +1326,7 @@ Name | Type | Description | Notes **fileExtension** | **String**| | **key** | **String**| | [optional] **livePhotoData** | **MultipartFile**| | [optional] + **isArchived** | **bool**| | [optional] **isVisible** | **bool**| | [optional] **duration** | **String**| | [optional] diff --git a/mobile/openapi/doc/AssetResponseDto.md b/mobile/openapi/doc/AssetResponseDto.md index 0c8bb3363e8a5..ea9c3784351d0 100644 --- a/mobile/openapi/doc/AssetResponseDto.md +++ b/mobile/openapi/doc/AssetResponseDto.md @@ -20,6 +20,7 @@ Name | Type | Description | Notes **fileModifiedAt** | **String** | | **updatedAt** | **String** | | **isFavorite** | **bool** | | +**isArchived** | **bool** | | **mimeType** | **String** | | **duration** | **String** | | **webpPath** | **String** | | diff --git a/mobile/openapi/doc/SearchApi.md b/mobile/openapi/doc/SearchApi.md index 7c351ea3b94bc..3cd5f5e2d6894 100644 --- a/mobile/openapi/doc/SearchApi.md +++ b/mobile/openapi/doc/SearchApi.md @@ -113,7 +113,7 @@ This endpoint does not need any parameter. [[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) # **search** -> SearchResponseDto search(q, query, clip, type, isFavorite, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion) +> SearchResponseDto search(q, query, clip, type, isFavorite, isArchived, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion) @@ -139,6 +139,7 @@ final query = query_example; // String | final clip = true; // bool | final type = type_example; // String | final isFavorite = true; // bool | +final isArchived = true; // bool | final exifInfoPeriodCity = exifInfoPeriodCity_example; // String | final exifInfoPeriodState = exifInfoPeriodState_example; // String | final exifInfoPeriodCountry = exifInfoPeriodCountry_example; // String | @@ -150,7 +151,7 @@ final recent = true; // bool | final motion = true; // bool | try { - final result = api_instance.search(q, query, clip, type, isFavorite, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion); + final result = api_instance.search(q, query, clip, type, isFavorite, isArchived, exifInfoPeriodCity, exifInfoPeriodState, exifInfoPeriodCountry, exifInfoPeriodMake, exifInfoPeriodModel, smartInfoPeriodObjects, smartInfoPeriodTags, recent, motion); print(result); } catch (e) { print('Exception when calling SearchApi->search: $e\n'); @@ -166,6 +167,7 @@ Name | Type | Description | Notes **clip** | **bool**| | [optional] **type** | **String**| | [optional] **isFavorite** | **bool**| | [optional] + **isArchived** | **bool**| | [optional] **exifInfoPeriodCity** | **String**| | [optional] **exifInfoPeriodState** | **String**| | [optional] **exifInfoPeriodCountry** | **String**| | [optional] diff --git a/mobile/openapi/doc/UpdateAssetDto.md b/mobile/openapi/doc/UpdateAssetDto.md index f02b6d5ed4ae1..4b844a54f04fd 100644 --- a/mobile/openapi/doc/UpdateAssetDto.md +++ b/mobile/openapi/doc/UpdateAssetDto.md @@ -10,6 +10,7 @@ Name | Type | Description | Notes ------------ | ------------- | ------------- | ------------- **tagIds** | **List** | | [optional] [default to const []] **isFavorite** | **bool** | | [optional] +**isArchived** | **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) diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index ea05bf8449736..4c9732518c83e 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -494,11 +494,13 @@ class AssetApi { /// /// * [bool] isFavorite: /// + /// * [bool] isArchived: + /// /// * [num] skip: /// /// * [String] ifNoneMatch: /// ETag of data already cached on the client - Future getAllAssetsWithHttpInfo({ bool? isFavorite, num? skip, String? ifNoneMatch, }) async { + Future getAllAssetsWithHttpInfo({ bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async { // ignore: prefer_const_declarations final path = r'/asset'; @@ -512,6 +514,9 @@ class AssetApi { if (isFavorite != null) { queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); } + if (isArchived != null) { + queryParams.addAll(_queryParams('', 'isArchived', isArchived)); + } if (skip != null) { queryParams.addAll(_queryParams('', 'skip', skip)); } @@ -540,12 +545,14 @@ class AssetApi { /// /// * [bool] isFavorite: /// + /// * [bool] isArchived: + /// /// * [num] skip: /// /// * [String] ifNoneMatch: /// ETag of data already cached on the client - Future?> getAllAssets({ bool? isFavorite, num? skip, String? ifNoneMatch, }) async { - final response = await getAllAssetsWithHttpInfo( isFavorite: isFavorite, skip: skip, ifNoneMatch: ifNoneMatch, ); + Future?> getAllAssets({ bool? isFavorite, bool? isArchived, num? skip, String? ifNoneMatch, }) async { + final response = await getAllAssetsWithHttpInfo( isFavorite: isFavorite, isArchived: isArchived, skip: skip, ifNoneMatch: ifNoneMatch, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -562,6 +569,50 @@ class AssetApi { return null; } + /// + /// + /// Note: This method returns the HTTP [Response]. + Future getArchivedAssetCountByUserIdWithHttpInfo() async { + // ignore: prefer_const_declarations + final path = r'/asset/stat/archive'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// + Future getArchivedAssetCountByUserId() async { + final response = await getArchivedAssetCountByUserIdWithHttpInfo(); + 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; + } + /// Get a single asset's information /// /// Note: This method returns the HTTP [Response]. @@ -1312,10 +1363,12 @@ class AssetApi { /// /// * [MultipartFile] livePhotoData: /// + /// * [bool] isArchived: + /// /// * [bool] isVisible: /// /// * [String] duration: - Future uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async { + Future uploadFileWithHttpInfo(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, bool? isArchived, bool? isVisible, String? duration, }) async { // ignore: prefer_const_declarations final path = r'/asset/upload'; @@ -1368,6 +1421,10 @@ class AssetApi { hasFields = true; mp.fields[r'isFavorite'] = parameterToString(isFavorite); } + if (isArchived != null) { + hasFields = true; + mp.fields[r'isArchived'] = parameterToString(isArchived); + } if (isVisible != null) { hasFields = true; mp.fields[r'isVisible'] = parameterToString(isVisible); @@ -1419,11 +1476,13 @@ class AssetApi { /// /// * [MultipartFile] livePhotoData: /// + /// * [bool] isArchived: + /// /// * [bool] isVisible: /// /// * [String] duration: - Future uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, bool? isVisible, String? duration, }) async { - final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key: key, livePhotoData: livePhotoData, isVisible: isVisible, duration: duration, ); + Future uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String? key, MultipartFile? livePhotoData, bool? isArchived, bool? isVisible, String? duration, }) async { + final response = await uploadFileWithHttpInfo(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key: key, livePhotoData: livePhotoData, isArchived: isArchived, isVisible: isVisible, duration: duration, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/api/search_api.dart b/mobile/openapi/lib/api/search_api.dart index 645ccf876b7ea..b4ff53e9ea1c0 100644 --- a/mobile/openapi/lib/api/search_api.dart +++ b/mobile/openapi/lib/api/search_api.dart @@ -123,6 +123,8 @@ class SearchApi { /// /// * [bool] isFavorite: /// + /// * [bool] isArchived: + /// /// * [String] exifInfoPeriodCity: /// /// * [String] exifInfoPeriodState: @@ -140,7 +142,7 @@ class SearchApi { /// * [bool] recent: /// /// * [bool] motion: - Future searchWithHttpInfo({ String? q, String? query, bool? clip, String? type, bool? isFavorite, String? exifInfoPeriodCity, String? exifInfoPeriodState, String? exifInfoPeriodCountry, String? exifInfoPeriodMake, String? exifInfoPeriodModel, List? smartInfoPeriodObjects, List? smartInfoPeriodTags, bool? recent, bool? motion, }) async { + Future searchWithHttpInfo({ String? q, String? query, bool? clip, String? type, bool? isFavorite, bool? isArchived, String? exifInfoPeriodCity, String? exifInfoPeriodState, String? exifInfoPeriodCountry, String? exifInfoPeriodMake, String? exifInfoPeriodModel, List? smartInfoPeriodObjects, List? smartInfoPeriodTags, bool? recent, bool? motion, }) async { // ignore: prefer_const_declarations final path = r'/search'; @@ -166,6 +168,9 @@ class SearchApi { if (isFavorite != null) { queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); } + if (isArchived != null) { + queryParams.addAll(_queryParams('', 'isArchived', isArchived)); + } if (exifInfoPeriodCity != null) { queryParams.addAll(_queryParams('', 'exifInfo.city', exifInfoPeriodCity)); } @@ -222,6 +227,8 @@ class SearchApi { /// /// * [bool] isFavorite: /// + /// * [bool] isArchived: + /// /// * [String] exifInfoPeriodCity: /// /// * [String] exifInfoPeriodState: @@ -239,8 +246,8 @@ class SearchApi { /// * [bool] recent: /// /// * [bool] motion: - Future search({ String? q, String? query, bool? clip, String? type, bool? isFavorite, String? exifInfoPeriodCity, String? exifInfoPeriodState, String? exifInfoPeriodCountry, String? exifInfoPeriodMake, String? exifInfoPeriodModel, List? smartInfoPeriodObjects, List? smartInfoPeriodTags, bool? recent, bool? motion, }) async { - final response = await searchWithHttpInfo( q: q, query: query, clip: clip, type: type, isFavorite: isFavorite, exifInfoPeriodCity: exifInfoPeriodCity, exifInfoPeriodState: exifInfoPeriodState, exifInfoPeriodCountry: exifInfoPeriodCountry, exifInfoPeriodMake: exifInfoPeriodMake, exifInfoPeriodModel: exifInfoPeriodModel, smartInfoPeriodObjects: smartInfoPeriodObjects, smartInfoPeriodTags: smartInfoPeriodTags, recent: recent, motion: motion, ); + Future search({ String? q, String? query, bool? clip, String? type, bool? isFavorite, bool? isArchived, String? exifInfoPeriodCity, String? exifInfoPeriodState, String? exifInfoPeriodCountry, String? exifInfoPeriodMake, String? exifInfoPeriodModel, List? smartInfoPeriodObjects, List? smartInfoPeriodTags, bool? recent, bool? motion, }) async { + final response = await searchWithHttpInfo( q: q, query: query, clip: clip, type: type, isFavorite: isFavorite, isArchived: isArchived, exifInfoPeriodCity: exifInfoPeriodCity, exifInfoPeriodState: exifInfoPeriodState, exifInfoPeriodCountry: exifInfoPeriodCountry, exifInfoPeriodMake: exifInfoPeriodMake, exifInfoPeriodModel: exifInfoPeriodModel, smartInfoPeriodObjects: smartInfoPeriodObjects, smartInfoPeriodTags: smartInfoPeriodTags, recent: recent, motion: motion, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } diff --git a/mobile/openapi/lib/model/asset_response_dto.dart b/mobile/openapi/lib/model/asset_response_dto.dart index fbe790190d2d0..7fdc42559dd1d 100644 --- a/mobile/openapi/lib/model/asset_response_dto.dart +++ b/mobile/openapi/lib/model/asset_response_dto.dart @@ -25,6 +25,7 @@ class AssetResponseDto { required this.fileModifiedAt, required this.updatedAt, required this.isFavorite, + required this.isArchived, required this.mimeType, required this.duration, required this.webpPath, @@ -59,6 +60,8 @@ class AssetResponseDto { bool isFavorite; + bool isArchived; + String? mimeType; String duration; @@ -101,6 +104,7 @@ class AssetResponseDto { other.fileModifiedAt == fileModifiedAt && other.updatedAt == updatedAt && other.isFavorite == isFavorite && + other.isArchived == isArchived && other.mimeType == mimeType && other.duration == duration && other.webpPath == webpPath && @@ -125,6 +129,7 @@ class AssetResponseDto { (fileModifiedAt.hashCode) + (updatedAt.hashCode) + (isFavorite.hashCode) + + (isArchived.hashCode) + (mimeType == null ? 0 : mimeType!.hashCode) + (duration.hashCode) + (webpPath == null ? 0 : webpPath!.hashCode) + @@ -135,7 +140,7 @@ class AssetResponseDto { (tags.hashCode); @override - String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resizePath=$resizePath, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]'; + String toString() => 'AssetResponseDto[type=$type, id=$id, deviceAssetId=$deviceAssetId, ownerId=$ownerId, deviceId=$deviceId, originalPath=$originalPath, originalFileName=$originalFileName, resizePath=$resizePath, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, updatedAt=$updatedAt, isFavorite=$isFavorite, isArchived=$isArchived, mimeType=$mimeType, duration=$duration, webpPath=$webpPath, encodedVideoPath=$encodedVideoPath, exifInfo=$exifInfo, smartInfo=$smartInfo, livePhotoVideoId=$livePhotoVideoId, tags=$tags]'; Map toJson() { final json = {}; @@ -155,6 +160,7 @@ class AssetResponseDto { json[r'fileModifiedAt'] = this.fileModifiedAt; json[r'updatedAt'] = this.updatedAt; json[r'isFavorite'] = this.isFavorite; + json[r'isArchived'] = this.isArchived; if (this.mimeType != null) { json[r'mimeType'] = this.mimeType; } else { @@ -221,6 +227,7 @@ class AssetResponseDto { fileModifiedAt: mapValueOfType(json, r'fileModifiedAt')!, updatedAt: mapValueOfType(json, r'updatedAt')!, isFavorite: mapValueOfType(json, r'isFavorite')!, + isArchived: mapValueOfType(json, r'isArchived')!, mimeType: mapValueOfType(json, r'mimeType'), duration: mapValueOfType(json, r'duration')!, webpPath: mapValueOfType(json, r'webpPath'), @@ -290,6 +297,7 @@ class AssetResponseDto { 'fileModifiedAt', 'updatedAt', 'isFavorite', + 'isArchived', 'mimeType', 'duration', 'webpPath', diff --git a/mobile/openapi/lib/model/update_asset_dto.dart b/mobile/openapi/lib/model/update_asset_dto.dart index dad7b2c3e15ee..2faee2f7c4d46 100644 --- a/mobile/openapi/lib/model/update_asset_dto.dart +++ b/mobile/openapi/lib/model/update_asset_dto.dart @@ -15,6 +15,7 @@ class UpdateAssetDto { UpdateAssetDto({ this.tagIds = const [], this.isFavorite, + this.isArchived, }); List tagIds; @@ -27,19 +28,29 @@ class UpdateAssetDto { /// bool? isFavorite; + /// + /// 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? isArchived; + @override bool operator ==(Object other) => identical(this, other) || other is UpdateAssetDto && other.tagIds == tagIds && - other.isFavorite == isFavorite; + other.isFavorite == isFavorite && + other.isArchived == isArchived; @override int get hashCode => // ignore: unnecessary_parenthesis (tagIds.hashCode) + - (isFavorite == null ? 0 : isFavorite!.hashCode); + (isFavorite == null ? 0 : isFavorite!.hashCode) + + (isArchived == null ? 0 : isArchived!.hashCode); @override - String toString() => 'UpdateAssetDto[tagIds=$tagIds, isFavorite=$isFavorite]'; + String toString() => 'UpdateAssetDto[tagIds=$tagIds, isFavorite=$isFavorite, isArchived=$isArchived]'; Map toJson() { final json = {}; @@ -49,6 +60,11 @@ class UpdateAssetDto { } else { // json[r'isFavorite'] = null; } + if (this.isArchived != null) { + json[r'isArchived'] = this.isArchived; + } else { + // json[r'isArchived'] = null; + } return json; } @@ -75,6 +91,7 @@ class UpdateAssetDto { ? (json[r'tagIds'] as List).cast() : const [], isFavorite: mapValueOfType(json, r'isFavorite'), + isArchived: mapValueOfType(json, r'isArchived'), ); } return null; diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index 3430b2e0399c0..fdd4307958367 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -75,11 +75,18 @@ void main() { // Get all AssetEntity belong to the user // - //Future> getAllAssets({ bool isFavorite, num skip, String ifNoneMatch }) async + //Future> getAllAssets({ bool isFavorite, bool isArchived, num skip, String ifNoneMatch }) async test('test getAllAssets', () async { // TODO }); + // + // + //Future getArchivedAssetCountByUserId() async + test('test getArchivedAssetCountByUserId', () async { + // TODO + }); + // Get a single asset's information // //Future getAssetById(String assetId, { String key }) async @@ -173,7 +180,7 @@ void main() { // // - //Future uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String key, MultipartFile livePhotoData, bool isVisible, String duration }) async + //Future uploadFile(AssetTypeEnum assetType, MultipartFile assetData, String deviceAssetId, String deviceId, String fileCreatedAt, String fileModifiedAt, bool isFavorite, String fileExtension, { String key, MultipartFile livePhotoData, bool isArchived, bool isVisible, String duration }) async test('test uploadFile', () async { // TODO }); diff --git a/mobile/openapi/test/asset_response_dto_test.dart b/mobile/openapi/test/asset_response_dto_test.dart index b5c72242d6e35..4a9be63cb8f88 100644 --- a/mobile/openapi/test/asset_response_dto_test.dart +++ b/mobile/openapi/test/asset_response_dto_test.dart @@ -76,6 +76,11 @@ void main() { // TODO }); + // bool isArchived + test('to test the property `isArchived`', () async { + // TODO + }); + // String mimeType test('to test the property `mimeType`', () async { // TODO diff --git a/mobile/openapi/test/search_api_test.dart b/mobile/openapi/test/search_api_test.dart index 89037f3c49771..1bd93ca6b9892 100644 --- a/mobile/openapi/test/search_api_test.dart +++ b/mobile/openapi/test/search_api_test.dart @@ -33,7 +33,7 @@ void main() { // // - //Future search({ String q, String query, bool clip, String type, bool isFavorite, String exifInfoPeriodCity, String exifInfoPeriodState, String exifInfoPeriodCountry, String exifInfoPeriodMake, String exifInfoPeriodModel, List smartInfoPeriodObjects, List smartInfoPeriodTags, bool recent, bool motion }) async + //Future search({ String q, String query, bool clip, String type, bool isFavorite, bool isArchived, String exifInfoPeriodCity, String exifInfoPeriodState, String exifInfoPeriodCountry, String exifInfoPeriodMake, String exifInfoPeriodModel, List smartInfoPeriodObjects, List smartInfoPeriodTags, bool recent, bool motion }) async test('test search', () async { // TODO }); diff --git a/mobile/openapi/test/update_asset_dto_test.dart b/mobile/openapi/test/update_asset_dto_test.dart index 16f51f44b6a5b..7f9d874afc98e 100644 --- a/mobile/openapi/test/update_asset_dto_test.dart +++ b/mobile/openapi/test/update_asset_dto_test.dart @@ -26,6 +26,11 @@ void main() { // TODO }); + // bool isArchived + test('to test the property `isArchived`', () async { + // TODO + }); + }); diff --git a/server/apps/immich/src/api-v1/asset/asset-repository.ts b/server/apps/immich/src/api-v1/asset/asset-repository.ts index 9d7fa77ad1e63..4bd1743dadb4b 100644 --- a/server/apps/immich/src/api-v1/asset/asset-repository.ts +++ b/server/apps/immich/src/api-v1/asset/asset-repository.ts @@ -36,6 +36,7 @@ export interface IAssetRepository { getSearchPropertiesByUserId(userId: string): Promise; getAssetCountByTimeBucket(userId: string, timeBucket: TimeGroupEnum): Promise; getAssetCountByUserId(userId: string): Promise; + getArchivedAssetCountByUserId(userId: string): Promise; getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise; getAssetByChecksum(userId: string, checksum: Buffer): Promise; getExistingAssets( @@ -83,26 +84,22 @@ export class AssetRepository implements IAssetRepository { .groupBy('asset.type') .getRawMany(); - const assetCountByUserId = new AssetCountByUserIdResponseDto(); + return this.getAssetCount(items); + } - // asset type to dto property mapping - const map: Record = { - [AssetType.AUDIO]: 'audio', - [AssetType.IMAGE]: 'photos', - [AssetType.VIDEO]: 'videos', - [AssetType.OTHER]: 'other', - }; + async getArchivedAssetCountByUserId(ownerId: string): Promise { + // Get archived asset count by AssetType + const items = await this.assetRepository + .createQueryBuilder('asset') + .select(`COUNT(asset.id)`, 'count') + .addSelect(`asset.type`, 'type') + .where('"ownerId" = :ownerId', { ownerId: ownerId }) + .andWhere('asset.isVisible = true') + .andWhere('asset.isArchived = true') + .groupBy('asset.type') + .getRawMany(); - for (const item of items) { - const count = Number(item.count) || 0; - const assetType = item.type as AssetType; - const type = map[assetType]; - - assetCountByUserId[type] = count; - assetCountByUserId.total += count; - } - - return assetCountByUserId; + return this.getAssetCount(items); } async getAssetByTimeBucket(userId: string, getAssetByTimeBucketDto: GetAssetByTimeBucketDto): Promise { @@ -115,6 +112,7 @@ export class AssetRepository implements IAssetRepository { }) .andWhere('asset.resizePath is not NULL') .andWhere('asset.isVisible = true') + .andWhere('asset.isArchived = false') .orderBy('asset.fileCreatedAt', 'DESC') .getMany(); } @@ -130,6 +128,7 @@ export class AssetRepository implements IAssetRepository { .where('"ownerId" = :userId', { userId: userId }) .andWhere('asset.resizePath is not NULL') .andWhere('asset.isVisible = true') + .andWhere('asset.isArchived = false') .groupBy(`date_trunc('month', "fileCreatedAt")`) .orderBy(`date_trunc('month', "fileCreatedAt")`, 'DESC') .getRawMany(); @@ -141,6 +140,7 @@ export class AssetRepository implements IAssetRepository { .where('"ownerId" = :userId', { userId: userId }) .andWhere('asset.resizePath is not NULL') .andWhere('asset.isVisible = true') + .andWhere('asset.isArchived = false') .groupBy(`date_trunc('day', "fileCreatedAt")`) .orderBy(`date_trunc('day', "fileCreatedAt")`, 'DESC') .getRawMany(); @@ -224,6 +224,7 @@ export class AssetRepository implements IAssetRepository { resizePath: Not(IsNull()), isVisible: true, isFavorite: dto.isFavorite, + isArchived: dto.isArchived, }, relations: { exifInfo: true, @@ -260,6 +261,7 @@ export class AssetRepository implements IAssetRepository { */ async update(userId: string, asset: AssetEntity, dto: UpdateAssetDto): Promise { asset.isFavorite = dto.isFavorite ?? asset.isFavorite; + asset.isArchived = dto.isArchived ?? asset.isArchived; if (dto.tagIds) { const tags = await this._tagRepository.getByIds(userId, dto.tagIds); @@ -330,4 +332,27 @@ export class AssetRepository implements IAssetRepository { }, }); } + + private getAssetCount(items: any): AssetCountByUserIdResponseDto { + const assetCountByUserId = new AssetCountByUserIdResponseDto(); + + // asset type to dto property mapping + const map: Record = { + [AssetType.AUDIO]: 'audio', + [AssetType.IMAGE]: 'photos', + [AssetType.VIDEO]: 'videos', + [AssetType.OTHER]: 'other', + }; + + for (const item of items) { + const count = Number(item.count) || 0; + const assetType = item.type as AssetType; + const type = map[assetType]; + + assetCountByUserId[type] = count; + assetCountByUserId.total += count; + } + + return assetCountByUserId; + } } diff --git a/server/apps/immich/src/api-v1/asset/asset.controller.ts b/server/apps/immich/src/api-v1/asset/asset.controller.ts index 0c14386157895..6088b8122217a 100644 --- a/server/apps/immich/src/api-v1/asset/asset.controller.ts +++ b/server/apps/immich/src/api-v1/asset/asset.controller.ts @@ -228,6 +228,11 @@ export class AssetController { return this.assetService.getAssetCountByUserId(authUser); } + @Authenticated() + @Get('/stat/archive') + async getArchivedAssetCountByUserId(@GetAuthUser() authUser: AuthUserDto): Promise { + return this.assetService.getArchivedAssetCountByUserId(authUser); + } /** * Get all AssetEntity belong to the user */ diff --git a/server/apps/immich/src/api-v1/asset/asset.core.ts b/server/apps/immich/src/api-v1/asset/asset.core.ts index 14261b001e922..972fafd44527e 100644 --- a/server/apps/immich/src/api-v1/asset/asset.core.ts +++ b/server/apps/immich/src/api-v1/asset/asset.core.ts @@ -28,6 +28,7 @@ export class AssetCore { type: dto.assetType, isFavorite: dto.isFavorite, + isArchived: dto.isArchived ?? false, duration: dto.duration || null, isVisible: dto.isVisible ?? true, livePhotoVideo: livePhotoAssetId != null ? ({ id: livePhotoAssetId } as AssetEntity) : null, diff --git a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts index e71bda605eb2b..0a5083296152a 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.spec.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.spec.ts @@ -32,6 +32,7 @@ const _getCreateAssetDto = (): CreateAssetDto => { createAssetDto.fileCreatedAt = '2022-06-19T23:41:36.910Z'; createAssetDto.fileModifiedAt = '2022-06-19T23:41:36.910Z'; createAssetDto.isFavorite = false; + createAssetDto.isArchived = false; createAssetDto.duration = '0:00:00.000000'; return createAssetDto; @@ -51,6 +52,7 @@ const _getAsset_1 = () => { asset_1.fileCreatedAt = '2022-06-19T23:41:36.910Z'; asset_1.updatedAt = '2022-06-19T23:41:36.910Z'; asset_1.isFavorite = false; + asset_1.isArchived = false; asset_1.mimeType = 'image/jpeg'; asset_1.webpPath = ''; asset_1.encodedVideoPath = ''; @@ -72,6 +74,7 @@ const _getAsset_2 = () => { asset_2.fileCreatedAt = '2022-06-19T23:41:36.910Z'; asset_2.updatedAt = '2022-06-19T23:41:36.910Z'; asset_2.isFavorite = false; + asset_2.isArchived = false; asset_2.mimeType = 'image/jpeg'; asset_2.webpPath = ''; asset_2.encodedVideoPath = ''; @@ -105,6 +108,15 @@ const _getAssetCountByUserId = (): AssetCountByUserIdResponseDto => { return result; }; +const _getArchivedAssetsCountByUserId = (): AssetCountByUserIdResponseDto => { + const result = new AssetCountByUserIdResponseDto(); + + result.videos = 1; + result.photos = 2; + + return result; +}; + describe('AssetService', () => { let sut: AssetService; let a: Repository; // TO BE DELETED AFTER FINISHED REFACTORING @@ -136,6 +148,7 @@ describe('AssetService', () => { getAssetByTimeBucket: jest.fn(), getAssetByChecksum: jest.fn(), getAssetCountByUserId: jest.fn(), + getArchivedAssetCountByUserId: jest.fn(), getExistingAssets: jest.fn(), countByIdAndUser: jest.fn(), }; @@ -350,14 +363,16 @@ describe('AssetService', () => { it('get asset count by user id', async () => { const assetCount = _getAssetCountByUserId(); + assetRepositoryMock.getAssetCountByUserId.mockResolvedValue(assetCount); - assetRepositoryMock.getAssetCountByUserId.mockImplementation(() => - Promise.resolve(assetCount), - ); + await expect(sut.getAssetCountByUserId(authStub.user1)).resolves.toEqual(assetCount); + }); - const result = await sut.getAssetCountByUserId(authStub.user1); + it('get archived asset count by user id', async () => { + const assetCount = _getArchivedAssetsCountByUserId(); + assetRepositoryMock.getArchivedAssetCountByUserId.mockResolvedValue(assetCount); - expect(result).toEqual(assetCount); + await expect(sut.getArchivedAssetCountByUserId(authStub.user1)).resolves.toEqual(assetCount); }); describe('deleteAll', () => { diff --git a/server/apps/immich/src/api-v1/asset/asset.service.ts b/server/apps/immich/src/api-v1/asset/asset.service.ts index 59d14e3d1cd45..e9b3c7893bd1e 100644 --- a/server/apps/immich/src/api-v1/asset/asset.service.ts +++ b/server/apps/immich/src/api-v1/asset/asset.service.ts @@ -466,6 +466,10 @@ export class AssetService { return this._assetRepository.getAssetCountByUserId(authUser.id); } + getArchivedAssetCountByUserId(authUser: AuthUserDto): Promise { + return this._assetRepository.getArchivedAssetCountByUserId(authUser.id); + } + async checkAssetsAccess(authUser: AuthUserDto, assetIds: string[], mustBeOwner = false) { for (const assetId of assetIds) { // Step 1: Check if asset is part of a public shared diff --git a/server/apps/immich/src/api-v1/asset/dto/asset-search.dto.ts b/server/apps/immich/src/api-v1/asset/dto/asset-search.dto.ts index 0d1ba7ccfc311..ab7812220c759 100644 --- a/server/apps/immich/src/api-v1/asset/dto/asset-search.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/asset-search.dto.ts @@ -9,6 +9,12 @@ export class AssetSearchDto { @Transform(toBoolean) isFavorite?: boolean; + @IsOptional() + @IsNotEmpty() + @IsBoolean() + @Transform(toBoolean) + isArchived?: boolean; + @IsOptional() @IsNumber() skip?: number; diff --git a/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts b/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts index 4f76964e68c32..cf158a7e2321c 100644 --- a/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/create-asset.dto.ts @@ -24,6 +24,10 @@ export class CreateAssetDto { @IsNotEmpty() isFavorite!: boolean; + @IsOptional() + @IsBoolean() + isArchived?: boolean; + @IsOptional() @IsBoolean() isVisible?: boolean; diff --git a/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts b/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts index 9c18c50a76ac4..5b17b043fe616 100644 --- a/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts +++ b/server/apps/immich/src/api-v1/asset/dto/update-asset.dto.ts @@ -6,6 +6,10 @@ export class UpdateAssetDto { @IsBoolean() isFavorite?: boolean; + @IsOptional() + @IsBoolean() + isArchived?: boolean; + @IsOptional() @IsArray() @IsString({ each: true }) diff --git a/server/immich-openapi-specs.json b/server/immich-openapi-specs.json index c699528caad83..fab096fb32046 100644 --- a/server/immich-openapi-specs.json +++ b/server/immich-openapi-specs.json @@ -767,6 +767,14 @@ "type": "boolean" } }, + { + "name": "isArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, { "name": "exifInfo.city", "required": false, @@ -2282,6 +2290,36 @@ ] } }, + "/asset/stat/archive": { + "get": { + "operationId": "getArchivedAssetCountByUserId", + "description": "", + "parameters": [], + "responses": { + "200": { + "description": "", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/AssetCountByUserIdResponseDto" + } + } + } + } + }, + "tags": [ + "Asset" + ], + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + } + ] + } + }, "/asset": { "get": { "operationId": "getAllAssets", @@ -2295,6 +2333,14 @@ "type": "boolean" } }, + { + "name": "isArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, { "name": "skip", "required": false, @@ -3726,6 +3772,9 @@ "isFavorite": { "type": "boolean" }, + "isArchived": { + "type": "boolean" + }, "mimeType": { "type": "string", "nullable": true @@ -3771,6 +3820,7 @@ "fileModifiedAt", "updatedAt", "isFavorite", + "isArchived", "mimeType", "duration", "webpPath" @@ -4984,6 +5034,9 @@ "isFavorite": { "type": "boolean" }, + "isArchived": { + "type": "boolean" + }, "isVisible": { "type": "boolean" }, @@ -5227,6 +5280,9 @@ }, "isFavorite": { "type": "boolean" + }, + "isArchived": { + "type": "boolean" } } }, diff --git a/server/libs/domain/src/asset/response-dto/asset-response.dto.ts b/server/libs/domain/src/asset/response-dto/asset-response.dto.ts index 041aef58299e9..e78c4ddf9c1c9 100644 --- a/server/libs/domain/src/asset/response-dto/asset-response.dto.ts +++ b/server/libs/domain/src/asset/response-dto/asset-response.dto.ts @@ -19,6 +19,7 @@ export class AssetResponseDto { fileModifiedAt!: string; updatedAt!: string; isFavorite!: boolean; + isArchived!: boolean; mimeType!: string | null; duration!: string; webpPath!: string | null; @@ -43,6 +44,7 @@ export function mapAsset(entity: AssetEntity): AssetResponseDto { fileModifiedAt: entity.fileModifiedAt, updatedAt: entity.updatedAt, isFavorite: entity.isFavorite, + isArchived: entity.isArchived, mimeType: entity.mimeType, webpPath: entity.webpPath, encodedVideoPath: entity.encodedVideoPath, @@ -68,6 +70,7 @@ export function mapAssetWithoutExif(entity: AssetEntity): AssetResponseDto { fileModifiedAt: entity.fileModifiedAt, updatedAt: entity.updatedAt, isFavorite: entity.isFavorite, + isArchived: entity.isArchived, mimeType: entity.mimeType, webpPath: entity.webpPath, encodedVideoPath: entity.encodedVideoPath, diff --git a/server/libs/domain/src/search/dto/search.dto.ts b/server/libs/domain/src/search/dto/search.dto.ts index b30acb0cf73c2..1d65c7bfb2baa 100644 --- a/server/libs/domain/src/search/dto/search.dto.ts +++ b/server/libs/domain/src/search/dto/search.dto.ts @@ -28,6 +28,11 @@ export class SearchDto { @Transform(toBoolean) isFavorite?: boolean; + @IsBoolean() + @IsOptional() + @Transform(toBoolean) + isArchived?: boolean; + @IsString() @IsNotEmpty() @IsOptional() diff --git a/server/libs/domain/src/search/search.repository.ts b/server/libs/domain/src/search/search.repository.ts index ac7f211a24dc3..9878c825d022f 100644 --- a/server/libs/domain/src/search/search.repository.ts +++ b/server/libs/domain/src/search/search.repository.ts @@ -15,6 +15,7 @@ export interface SearchFilter { userId: string; type?: AssetType; isFavorite?: boolean; + isArchived?: boolean; city?: string; state?: string; country?: string; diff --git a/server/libs/domain/test/fixtures.ts b/server/libs/domain/test/fixtures.ts index 00bcfee90ef84..708dd393363c7 100644 --- a/server/libs/domain/test/fixtures.ts +++ b/server/libs/domain/test/fixtures.ts @@ -134,6 +134,7 @@ export const assetEntityStub = { updatedAt: '2023-02-23T05:06:29.716Z', mimeType: null, isFavorite: true, + isArchived: false, duration: null, isVisible: true, livePhotoVideo: null, @@ -158,6 +159,7 @@ export const assetEntityStub = { updatedAt: '2023-02-23T05:06:29.716Z', mimeType: null, isFavorite: true, + isArchived: false, duration: null, isVisible: true, livePhotoVideo: null, @@ -184,6 +186,7 @@ export const assetEntityStub = { updatedAt: '2023-02-23T05:06:29.716Z', mimeType: null, isFavorite: true, + isArchived: false, duration: null, isVisible: true, livePhotoVideo: null, @@ -355,6 +358,7 @@ const assetResponse: AssetResponseDto = { fileCreatedAt: today.toISOString(), updatedAt: today.toISOString(), isFavorite: false, + isArchived: false, mimeType: 'image/jpeg', smartInfo: { tags: [], @@ -591,6 +595,7 @@ export const sharedLinkStub = { createdAt: today.toISOString(), updatedAt: today.toISOString(), isFavorite: false, + isArchived: false, mimeType: 'image/jpeg', smartInfo: { assetId: 'id_1', diff --git a/server/libs/infra/src/entities/asset.entity.ts b/server/libs/infra/src/entities/asset.entity.ts index f47e0fec39d09..d370f06478c4b 100644 --- a/server/libs/infra/src/entities/asset.entity.ts +++ b/server/libs/infra/src/entities/asset.entity.ts @@ -67,6 +67,9 @@ export class AssetEntity { @Column({ type: 'boolean', default: false }) isFavorite!: boolean; + @Column({ type: 'boolean', default: false }) + isArchived!: boolean; + @Column({ type: 'varchar', nullable: true }) mimeType!: string | null; diff --git a/server/libs/infra/src/migrations/1680632845740-AddIsArchivedColumn.ts b/server/libs/infra/src/migrations/1680632845740-AddIsArchivedColumn.ts new file mode 100644 index 0000000000000..325e37f489a6f --- /dev/null +++ b/server/libs/infra/src/migrations/1680632845740-AddIsArchivedColumn.ts @@ -0,0 +1,14 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddIsArchivedColumn1680632845740 implements MigrationInterface { + name = 'AddIsArchivedColumn1680632845740' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assets" ADD "isArchived" boolean NOT NULL DEFAULT false`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "assets" DROP COLUMN "isArchived"`); + } + +} diff --git a/server/libs/infra/src/typesense-schemas/asset.schema.ts b/server/libs/infra/src/typesense-schemas/asset.schema.ts index 04636d4f676bd..0b778fbeae780 100644 --- a/server/libs/infra/src/typesense-schemas/asset.schema.ts +++ b/server/libs/infra/src/typesense-schemas/asset.schema.ts @@ -1,6 +1,6 @@ import { CollectionCreateSchema } from 'typesense/lib/Typesense/Collections'; -export const assetSchemaVersion = 4; +export const assetSchemaVersion = 5; export const assetSchema: CollectionCreateSchema = { name: `assets-v${assetSchemaVersion}`, fields: [ @@ -14,8 +14,6 @@ export const assetSchema: CollectionCreateSchema = { { name: 'fileModifiedAt', type: 'string', facet: false, sort: true }, { name: 'isFavorite', type: 'bool', facet: true }, { name: 'originalFileName', type: 'string', facet: false, optional: true }, - // { name: 'checksum', type: 'string', facet: true }, - // { name: 'tags', type: 'string[]', facet: true, optional: true }, // exif { name: 'exifInfo.city', type: 'string', facet: true, optional: true }, diff --git a/web/src/api/open-api/api.ts b/web/src/api/open-api/api.ts index f92dd2328ecc1..7eed6db3893d8 100644 --- a/web/src/api/open-api/api.ts +++ b/web/src/api/open-api/api.ts @@ -512,6 +512,12 @@ export interface AssetResponseDto { * @memberof AssetResponseDto */ 'isFavorite': boolean; + /** + * + * @type {boolean} + * @memberof AssetResponseDto + */ + 'isArchived': boolean; /** * * @type {string} @@ -2329,6 +2335,12 @@ export interface UpdateAssetDto { * @memberof UpdateAssetDto */ 'isFavorite'?: boolean; + /** + * + * @type {boolean} + * @memberof UpdateAssetDto + */ + 'isArchived'?: boolean; } /** * @@ -4274,12 +4286,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration /** * Get all AssetEntity belong to the user * @param {boolean} [isFavorite] + * @param {boolean} [isArchived] * @param {number} [skip] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllAssets: async (isFavorite?: boolean, skip?: number, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { + getAllAssets: async (isFavorite?: boolean, isArchived?: boolean, skip?: number, ifNoneMatch?: string, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/asset`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -4302,6 +4315,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['isFavorite'] = isFavorite; } + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + if (skip !== undefined) { localVarQueryParameter['skip'] = skip; } @@ -4312,6 +4329,41 @@ export const AssetApiAxiosParamCreator = 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} + */ + getArchivedAssetCountByUserId: async (options: AxiosRequestConfig = {}): Promise => { + const localVarPath = `/asset/stat/archive`; + // 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 cookie required + + // 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}; @@ -4873,12 +4925,13 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {string} fileExtension * @param {string} [key] * @param {File} [livePhotoData] + * @param {boolean} [isArchived] * @param {boolean} [isVisible] * @param {string} [duration] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - uploadFile: async (assetType: AssetTypeEnum, assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, key?: string, livePhotoData?: File, isVisible?: boolean, duration?: string, options: AxiosRequestConfig = {}): Promise => { + uploadFile: async (assetType: AssetTypeEnum, assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, key?: string, livePhotoData?: File, isArchived?: boolean, isVisible?: boolean, duration?: string, options: AxiosRequestConfig = {}): Promise => { // verify required parameter 'assetType' is not null or undefined assertParamExists('uploadFile', 'assetType', assetType) // verify required parameter 'assetData' is not null or undefined @@ -4951,6 +5004,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarFormParams.append('isFavorite', isFavorite as any); } + if (isArchived !== undefined) { + localVarFormParams.append('isArchived', isArchived as any); + } + if (isVisible !== undefined) { localVarFormParams.append('isVisible', isVisible as any); } @@ -5075,13 +5132,23 @@ export const AssetApiFp = function(configuration?: Configuration) { /** * Get all AssetEntity belong to the user * @param {boolean} [isFavorite] + * @param {boolean} [isArchived] * @param {number} [skip] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async getAllAssets(isFavorite?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(isFavorite, skip, ifNoneMatch, options); + async getAllAssets(isFavorite?: boolean, isArchived?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getAllAssets(isFavorite, isArchived, skip, ifNoneMatch, options); + return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async getArchivedAssetCountByUserId(options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.getArchivedAssetCountByUserId(options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, /** @@ -5230,13 +5297,14 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {string} fileExtension * @param {string} [key] * @param {File} [livePhotoData] + * @param {boolean} [isArchived] * @param {boolean} [isVisible] * @param {string} [duration] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async uploadFile(assetType: AssetTypeEnum, assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, key?: string, livePhotoData?: File, isVisible?: boolean, duration?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isVisible, duration, options); + async uploadFile(assetType: AssetTypeEnum, assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, key?: string, livePhotoData?: File, isArchived?: boolean, isVisible?: boolean, duration?: string, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isArchived, isVisible, duration, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -5330,13 +5398,22 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath /** * Get all AssetEntity belong to the user * @param {boolean} [isFavorite] + * @param {boolean} [isArchived] * @param {number} [skip] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} */ - getAllAssets(isFavorite?: boolean, skip?: number, ifNoneMatch?: string, options?: any): AxiosPromise> { - return localVarFp.getAllAssets(isFavorite, skip, ifNoneMatch, options).then((request) => request(axios, basePath)); + getAllAssets(isFavorite?: boolean, isArchived?: boolean, skip?: number, ifNoneMatch?: string, options?: any): AxiosPromise> { + return localVarFp.getAllAssets(isFavorite, isArchived, skip, ifNoneMatch, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + getArchivedAssetCountByUserId(options?: any): AxiosPromise { + return localVarFp.getArchivedAssetCountByUserId(options).then((request) => request(axios, basePath)); }, /** * Get a single asset\'s information @@ -5471,13 +5548,14 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * @param {string} fileExtension * @param {string} [key] * @param {File} [livePhotoData] + * @param {boolean} [isArchived] * @param {boolean} [isVisible] * @param {string} [duration] * @param {*} [options] Override http request option. * @throws {RequiredError} */ - uploadFile(assetType: AssetTypeEnum, assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, key?: string, livePhotoData?: File, isVisible?: boolean, duration?: string, options?: any): AxiosPromise { - return localVarFp.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isVisible, duration, options).then((request) => request(axios, basePath)); + uploadFile(assetType: AssetTypeEnum, assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, key?: string, livePhotoData?: File, isArchived?: boolean, isVisible?: boolean, duration?: string, options?: any): AxiosPromise { + return localVarFp.uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isArchived, isVisible, duration, options).then((request) => request(axios, basePath)); }, }; }; @@ -5586,14 +5664,25 @@ export class AssetApi extends BaseAPI { /** * Get all AssetEntity belong to the user * @param {boolean} [isFavorite] + * @param {boolean} [isArchived] * @param {number} [skip] * @param {string} [ifNoneMatch] ETag of data already cached on the client * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AssetApi */ - public getAllAssets(isFavorite?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).getAllAssets(isFavorite, skip, ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); + public getAllAssets(isFavorite?: boolean, isArchived?: boolean, skip?: number, ifNoneMatch?: string, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getAllAssets(isFavorite, isArchived, skip, ifNoneMatch, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof AssetApi + */ + public getArchivedAssetCountByUserId(options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).getArchivedAssetCountByUserId(options).then((request) => request(this.axios, this.basePath)); } /** @@ -5755,14 +5844,15 @@ export class AssetApi extends BaseAPI { * @param {string} fileExtension * @param {string} [key] * @param {File} [livePhotoData] + * @param {boolean} [isArchived] * @param {boolean} [isVisible] * @param {string} [duration] * @param {*} [options] Override http request option. * @throws {RequiredError} * @memberof AssetApi */ - public uploadFile(assetType: AssetTypeEnum, assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, key?: string, livePhotoData?: File, isVisible?: boolean, duration?: string, options?: AxiosRequestConfig) { - return AssetApiFp(this.configuration).uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isVisible, duration, options).then((request) => request(this.axios, this.basePath)); + public uploadFile(assetType: AssetTypeEnum, assetData: File, deviceAssetId: string, deviceId: string, fileCreatedAt: string, fileModifiedAt: string, isFavorite: boolean, fileExtension: string, key?: string, livePhotoData?: File, isArchived?: boolean, isVisible?: boolean, duration?: string, options?: AxiosRequestConfig) { + return AssetApiFp(this.configuration).uploadFile(assetType, assetData, deviceAssetId, deviceId, fileCreatedAt, fileModifiedAt, isFavorite, fileExtension, key, livePhotoData, isArchived, isVisible, duration, options).then((request) => request(this.axios, this.basePath)); } } @@ -6857,6 +6947,7 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * @param {boolean} [clip] * @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type] * @param {boolean} [isFavorite] + * @param {boolean} [isArchived] * @param {string} [exifInfoCity] * @param {string} [exifInfoState] * @param {string} [exifInfoCountry] @@ -6869,7 +6960,7 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * @param {*} [options] Override http request option. * @throws {RequiredError} */ - search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { + search: async (q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options: AxiosRequestConfig = {}): Promise => { const localVarPath = `/search`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -6908,6 +6999,10 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio localVarQueryParameter['isFavorite'] = isFavorite; } + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + if (exifInfoCity !== undefined) { localVarQueryParameter['exifInfo.city'] = exifInfoCity; } @@ -6990,6 +7085,7 @@ export const SearchApiFp = function(configuration?: Configuration) { * @param {boolean} [clip] * @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type] * @param {boolean} [isFavorite] + * @param {boolean} [isArchived] * @param {string} [exifInfoCity] * @param {string} [exifInfoState] * @param {string} [exifInfoCountry] @@ -7002,8 +7098,8 @@ export const SearchApiFp = function(configuration?: Configuration) { * @param {*} [options] Override http request option. * @throws {RequiredError} */ - async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options); + async search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options); return createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration); }, } @@ -7039,6 +7135,7 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat * @param {boolean} [clip] * @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type] * @param {boolean} [isFavorite] + * @param {boolean} [isArchived] * @param {string} [exifInfoCity] * @param {string} [exifInfoState] * @param {string} [exifInfoCountry] @@ -7051,8 +7148,8 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat * @param {*} [options] Override http request option. * @throws {RequiredError} */ - search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: any): AxiosPromise { - return localVarFp.search(q, query, clip, type, isFavorite, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options).then((request) => request(axios, basePath)); + search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: any): AxiosPromise { + return localVarFp.search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options).then((request) => request(axios, basePath)); }, }; }; @@ -7091,6 +7188,7 @@ export class SearchApi extends BaseAPI { * @param {boolean} [clip] * @param {'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER'} [type] * @param {boolean} [isFavorite] + * @param {boolean} [isArchived] * @param {string} [exifInfoCity] * @param {string} [exifInfoState] * @param {string} [exifInfoCountry] @@ -7104,8 +7202,8 @@ export class SearchApi extends BaseAPI { * @throws {RequiredError} * @memberof SearchApi */ - public search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig) { - return SearchApiFp(this.configuration).search(q, query, clip, type, isFavorite, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options).then((request) => request(this.axios, this.basePath)); + public search(q?: string, query?: string, clip?: boolean, type?: 'IMAGE' | 'VIDEO' | 'AUDIO' | 'OTHER', isFavorite?: boolean, isArchived?: boolean, exifInfoCity?: string, exifInfoState?: string, exifInfoCountry?: string, exifInfoMake?: string, exifInfoModel?: string, smartInfoObjects?: Array, smartInfoTags?: Array, recent?: boolean, motion?: boolean, options?: AxiosRequestConfig) { + return SearchApiFp(this.configuration).search(q, query, clip, type, isFavorite, isArchived, exifInfoCity, exifInfoState, exifInfoCountry, exifInfoMake, exifInfoModel, smartInfoObjects, smartInfoTags, recent, motion, options).then((request) => request(this.axios, this.basePath)); } } diff --git a/web/src/lib/components/shared-components/empty-placeholder.svelte b/web/src/lib/components/shared-components/empty-placeholder.svelte new file mode 100644 index 0000000000000..55b225ac74ade --- /dev/null +++ b/web/src/lib/components/shared-components/empty-placeholder.svelte @@ -0,0 +1,28 @@ + + +{#if actionHandler} +
+ +

{text}

+
+{:else} +
+ +

{text}

+
+{/if} diff --git a/web/src/lib/components/shared-components/side-bar/side-bar.svelte b/web/src/lib/components/shared-components/side-bar/side-bar.svelte index f8615f5f2f645..d312f05c41485 100644 --- a/web/src/lib/components/shared-components/side-bar/side-bar.svelte +++ b/web/src/lib/components/shared-components/side-bar/side-bar.svelte @@ -4,6 +4,7 @@ import AccountMultipleOutline from 'svelte-material-icons/AccountMultipleOutline.svelte'; import ImageAlbum from 'svelte-material-icons/ImageAlbum.svelte'; import ImageOutline from 'svelte-material-icons/ImageOutline.svelte'; + import ArchiveArrowDownOutline from 'svelte-material-icons/ArchiveArrowDownOutline.svelte'; import Magnify from 'svelte-material-icons/Magnify.svelte'; import StarOutline from 'svelte-material-icons/StarOutline.svelte'; import { AppRoute } from '../../../constants'; @@ -13,16 +14,17 @@ import { locale } from '$lib/stores/preferences.store'; const getAssetCount = async () => { - const { data: assetCount } = await api.assetApi.getAssetCountByUserId(); + const { data: allAssetCount } = await api.assetApi.getAssetCountByUserId(); + const { data: archivedCount } = await api.assetApi.getArchivedAssetCountByUserId(); return { - videos: assetCount.videos, - photos: assetCount.photos + videos: allAssetCount.videos - archivedCount.videos, + photos: allAssetCount.photos - archivedCount.photos }; }; const getFavoriteCount = async () => { - const { data: assets } = await api.assetApi.getAllAssets(true); + const { data: assets } = await api.assetApi.getAllAssets(true, undefined); return { favorites: assets.length @@ -37,6 +39,15 @@ owned: albumCount.owned }; }; + + const getArchivedAssetsCount = async () => { + const { data: assetCount } = await api.assetApi.getArchivedAssetCountByUserId(); + + return { + videos: assetCount.videos, + photos: assetCount.photos + }; + };