diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 3c662af41f09e..1657aa69cb1ee 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -162,7 +162,9 @@ Class | Method | HTTP request | Description *PersonApi* | [**updatePerson**](doc//PersonApi.md#updateperson) | **PUT** /person/{id} | *SearchApi* | [**getExploreData**](doc//SearchApi.md#getexploredata) | **GET** /search/explore | *SearchApi* | [**search**](doc//SearchApi.md#search) | **GET** /search | +*SearchApi* | [**searchMetadata**](doc//SearchApi.md#searchmetadata) | **GET** /search/metadata | *SearchApi* | [**searchPerson**](doc//SearchApi.md#searchperson) | **GET** /search/person | +*SearchApi* | [**searchSmart**](doc//SearchApi.md#searchsmart) | **GET** /search/smart | *ServerInfoApi* | [**getServerConfig**](doc//ServerInfoApi.md#getserverconfig) | **GET** /server-info/config | *ServerInfoApi* | [**getServerFeatures**](doc//ServerInfoApi.md#getserverfeatures) | **GET** /server-info/features | *ServerInfoApi* | [**getServerInfo**](doc//ServerInfoApi.md#getserverinfo) | **GET** /server-info | diff --git a/mobile/openapi/doc/AssetApi.md b/mobile/openapi/doc/AssetApi.md index a7ea1c07c79a6..623d88b388d50 100644 --- a/mobile/openapi/doc/AssetApi.md +++ b/mobile/openapi/doc/AssetApi.md @@ -1034,7 +1034,7 @@ 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) # **searchAssets** -> List searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withDeleted, withExif, withPeople, withStacked) +> List searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked) @@ -1093,13 +1093,14 @@ final type = ; // AssetTypeEnum | final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime | final updatedBefore = 2013-10-20T19:20:30+01:00; // DateTime | final webpPath = webpPath_example; // String | +final withArchived = true; // bool | final withDeleted = true; // bool | final withExif = true; // bool | final withPeople = true; // bool | final withStacked = true; // bool | try { - final result = api_instance.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withDeleted, withExif, withPeople, withStacked); + final result = api_instance.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked); print(result); } catch (e) { print('Exception when calling AssetApi->searchAssets: $e\n'); @@ -1146,6 +1147,7 @@ Name | Type | Description | Notes **updatedAfter** | **DateTime**| | [optional] **updatedBefore** | **DateTime**| | [optional] **webpPath** | **String**| | [optional] + **withArchived** | **bool**| | [optional] **withDeleted** | **bool**| | [optional] **withExif** | **bool**| | [optional] **withPeople** | **bool**| | [optional] diff --git a/mobile/openapi/doc/SearchApi.md b/mobile/openapi/doc/SearchApi.md index dcf453b55d2ec..40b44fb011ba8 100644 --- a/mobile/openapi/doc/SearchApi.md +++ b/mobile/openapi/doc/SearchApi.md @@ -11,7 +11,9 @@ Method | HTTP request | Description ------------- | ------------- | ------------- [**getExploreData**](SearchApi.md#getexploredata) | **GET** /search/explore | [**search**](SearchApi.md#search) | **GET** /search | +[**searchMetadata**](SearchApi.md#searchmetadata) | **GET** /search/metadata | [**searchPerson**](SearchApi.md#searchperson) | **GET** /search/person | +[**searchSmart**](SearchApi.md#searchsmart) | **GET** /search/smart | # **getExploreData** @@ -66,7 +68,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(clip, motion, q, query, recent, smart, type, withArchived) +> SearchResponseDto search(clip, motion, page, q, query, recent, size, smart, type, withArchived) @@ -91,15 +93,17 @@ import 'package:openapi/api.dart'; final api_instance = SearchApi(); final clip = true; // bool | @deprecated final motion = true; // bool | +final page = 8.14; // num | final q = q_example; // String | final query = query_example; // String | final recent = true; // bool | +final size = 8.14; // num | final smart = true; // bool | final type = type_example; // String | final withArchived = true; // bool | try { - final result = api_instance.search(clip, motion, q, query, recent, smart, type, withArchived); + final result = api_instance.search(clip, motion, page, q, query, recent, size, smart, type, withArchived); print(result); } catch (e) { print('Exception when calling SearchApi->search: $e\n'); @@ -112,9 +116,11 @@ Name | Type | Description | Notes ------------- | ------------- | ------------- | ------------- **clip** | **bool**| @deprecated | [optional] **motion** | **bool**| | [optional] + **page** | **num**| | [optional] **q** | **String**| | [optional] **query** | **String**| | [optional] **recent** | **bool**| | [optional] + **size** | **num**| | [optional] **smart** | **bool**| | [optional] **type** | **String**| | [optional] **withArchived** | **bool**| | [optional] @@ -134,6 +140,141 @@ 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) +# **searchMetadata** +> SearchResponseDto searchMetadata(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked) + + + +### 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 API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').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 = SearchApi(); +final checksum = checksum_example; // String | +final city = city_example; // String | +final country = country_example; // String | +final createdAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final createdBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final deviceAssetId = deviceAssetId_example; // String | +final deviceId = deviceId_example; // String | +final encodedVideoPath = encodedVideoPath_example; // String | +final id = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final isArchived = true; // bool | +final isEncoded = true; // bool | +final isExternal = true; // bool | +final isFavorite = true; // bool | +final isMotion = true; // bool | +final isOffline = true; // bool | +final isReadOnly = true; // bool | +final isVisible = true; // bool | +final lensModel = lensModel_example; // String | +final libraryId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final make = make_example; // String | +final model = model_example; // String | +final order = ; // AssetOrder | +final originalFileName = originalFileName_example; // String | +final originalPath = originalPath_example; // String | +final page = 8.14; // num | +final resizePath = resizePath_example; // String | +final size = 8.14; // num | +final state = state_example; // String | +final takenAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final takenBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final trashedAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final trashedBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final type = ; // AssetTypeEnum | +final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final updatedBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final webpPath = webpPath_example; // String | +final withArchived = true; // bool | +final withDeleted = true; // bool | +final withExif = true; // bool | +final withPeople = true; // bool | +final withStacked = true; // bool | + +try { + final result = api_instance.searchMetadata(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked); + print(result); +} catch (e) { + print('Exception when calling SearchApi->searchMetadata: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **checksum** | **String**| | [optional] + **city** | **String**| | [optional] + **country** | **String**| | [optional] + **createdAfter** | **DateTime**| | [optional] + **createdBefore** | **DateTime**| | [optional] + **deviceAssetId** | **String**| | [optional] + **deviceId** | **String**| | [optional] + **encodedVideoPath** | **String**| | [optional] + **id** | **String**| | [optional] + **isArchived** | **bool**| | [optional] + **isEncoded** | **bool**| | [optional] + **isExternal** | **bool**| | [optional] + **isFavorite** | **bool**| | [optional] + **isMotion** | **bool**| | [optional] + **isOffline** | **bool**| | [optional] + **isReadOnly** | **bool**| | [optional] + **isVisible** | **bool**| | [optional] + **lensModel** | **String**| | [optional] + **libraryId** | **String**| | [optional] + **make** | **String**| | [optional] + **model** | **String**| | [optional] + **order** | [**AssetOrder**](.md)| | [optional] + **originalFileName** | **String**| | [optional] + **originalPath** | **String**| | [optional] + **page** | **num**| | [optional] + **resizePath** | **String**| | [optional] + **size** | **num**| | [optional] + **state** | **String**| | [optional] + **takenAfter** | **DateTime**| | [optional] + **takenBefore** | **DateTime**| | [optional] + **trashedAfter** | **DateTime**| | [optional] + **trashedBefore** | **DateTime**| | [optional] + **type** | [**AssetTypeEnum**](.md)| | [optional] + **updatedAfter** | **DateTime**| | [optional] + **updatedBefore** | **DateTime**| | [optional] + **webpPath** | **String**| | [optional] + **withArchived** | **bool**| | [optional] + **withDeleted** | **bool**| | [optional] + **withExif** | **bool**| | [optional] + **withPeople** | **bool**| | [optional] + **withStacked** | **bool**| | [optional] + +### Return type + +[**SearchResponseDto**](SearchResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [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) + # **searchPerson** > List searchPerson(name, withHidden) @@ -191,3 +332,118 @@ 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) +# **searchSmart** +> SearchResponseDto searchSmart(query, city, country, createdAfter, createdBefore, deviceId, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, page, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif) + + + +### 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 API key authorization: api_key +//defaultApiClient.getAuthentication('api_key').apiKey = 'YOUR_API_KEY'; +// uncomment below to setup prefix (e.g. Bearer) for API key, if needed +//defaultApiClient.getAuthentication('api_key').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 = SearchApi(); +final query = query_example; // String | +final city = city_example; // String | +final country = country_example; // String | +final createdAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final createdBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final deviceId = deviceId_example; // String | +final isArchived = true; // bool | +final isEncoded = true; // bool | +final isExternal = true; // bool | +final isFavorite = true; // bool | +final isMotion = true; // bool | +final isOffline = true; // bool | +final isReadOnly = true; // bool | +final isVisible = true; // bool | +final lensModel = lensModel_example; // String | +final libraryId = 38400000-8cf0-11bd-b23e-10b96e4ef00d; // String | +final make = make_example; // String | +final model = model_example; // String | +final page = 8.14; // num | +final size = 8.14; // num | +final state = state_example; // String | +final takenAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final takenBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final trashedAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final trashedBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final type = ; // AssetTypeEnum | +final updatedAfter = 2013-10-20T19:20:30+01:00; // DateTime | +final updatedBefore = 2013-10-20T19:20:30+01:00; // DateTime | +final withArchived = true; // bool | +final withDeleted = true; // bool | +final withExif = true; // bool | + +try { + final result = api_instance.searchSmart(query, city, country, createdAfter, createdBefore, deviceId, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, page, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif); + print(result); +} catch (e) { + print('Exception when calling SearchApi->searchSmart: $e\n'); +} +``` + +### Parameters + +Name | Type | Description | Notes +------------- | ------------- | ------------- | ------------- + **query** | **String**| | + **city** | **String**| | [optional] + **country** | **String**| | [optional] + **createdAfter** | **DateTime**| | [optional] + **createdBefore** | **DateTime**| | [optional] + **deviceId** | **String**| | [optional] + **isArchived** | **bool**| | [optional] + **isEncoded** | **bool**| | [optional] + **isExternal** | **bool**| | [optional] + **isFavorite** | **bool**| | [optional] + **isMotion** | **bool**| | [optional] + **isOffline** | **bool**| | [optional] + **isReadOnly** | **bool**| | [optional] + **isVisible** | **bool**| | [optional] + **lensModel** | **String**| | [optional] + **libraryId** | **String**| | [optional] + **make** | **String**| | [optional] + **model** | **String**| | [optional] + **page** | **num**| | [optional] + **size** | **num**| | [optional] + **state** | **String**| | [optional] + **takenAfter** | **DateTime**| | [optional] + **takenBefore** | **DateTime**| | [optional] + **trashedAfter** | **DateTime**| | [optional] + **trashedBefore** | **DateTime**| | [optional] + **type** | [**AssetTypeEnum**](.md)| | [optional] + **updatedAfter** | **DateTime**| | [optional] + **updatedBefore** | **DateTime**| | [optional] + **withArchived** | **bool**| | [optional] + **withDeleted** | **bool**| | [optional] + **withExif** | **bool**| | [optional] + +### Return type + +[**SearchResponseDto**](SearchResponseDto.md) + +### Authorization + +[cookie](../README.md#cookie), [api_key](../README.md#api_key), [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) + diff --git a/mobile/openapi/doc/SearchAssetResponseDto.md b/mobile/openapi/doc/SearchAssetResponseDto.md index 2fc33feb418c8..5b55a559dc0ae 100644 --- a/mobile/openapi/doc/SearchAssetResponseDto.md +++ b/mobile/openapi/doc/SearchAssetResponseDto.md @@ -11,6 +11,7 @@ Name | Type | Description | Notes **count** | **int** | | **facets** | [**List**](SearchFacetResponseDto.md) | | [default to const []] **items** | [**List**](AssetResponseDto.md) | | [default to const []] +**nextPage** | **String** | | **total** | **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) diff --git a/mobile/openapi/lib/api/asset_api.dart b/mobile/openapi/lib/api/asset_api.dart index 7f4528c12f048..4dec34d407400 100644 --- a/mobile/openapi/lib/api/asset_api.dart +++ b/mobile/openapi/lib/api/asset_api.dart @@ -1177,6 +1177,8 @@ class AssetApi { /// /// * [String] webpPath: /// + /// * [bool] withArchived: + /// /// * [bool] withDeleted: /// /// * [bool] withExif: @@ -1184,7 +1186,7 @@ class AssetApi { /// * [bool] withPeople: /// /// * [bool] withStacked: - Future searchAssetsWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { + Future searchAssetsWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { // ignore: prefer_const_declarations final path = r'/assets'; @@ -1303,6 +1305,9 @@ class AssetApi { if (webpPath != null) { queryParams.addAll(_queryParams('', 'webpPath', webpPath)); } + if (withArchived != null) { + queryParams.addAll(_queryParams('', 'withArchived', withArchived)); + } if (withDeleted != null) { queryParams.addAll(_queryParams('', 'withDeleted', withDeleted)); } @@ -1404,6 +1409,8 @@ class AssetApi { /// /// * [String] webpPath: /// + /// * [bool] withArchived: + /// /// * [bool] withDeleted: /// /// * [bool] withExif: @@ -1411,8 +1418,8 @@ class AssetApi { /// * [bool] withPeople: /// /// * [bool] withStacked: - Future?> searchAssets({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { - final response = await searchAssetsWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, ); + Future?> searchAssets({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { + final response = await searchAssetsWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, ); 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 e2bde2a17f02a..55d737a8dbb0f 100644 --- a/mobile/openapi/lib/api/search_api.dart +++ b/mobile/openapi/lib/api/search_api.dart @@ -68,18 +68,22 @@ class SearchApi { /// /// * [bool] motion: /// + /// * [num] page: + /// /// * [String] q: /// /// * [String] query: /// /// * [bool] recent: /// + /// * [num] size: + /// /// * [bool] smart: /// /// * [String] type: /// /// * [bool] withArchived: - Future searchWithHttpInfo({ bool? clip, bool? motion, String? q, String? query, bool? recent, bool? smart, String? type, bool? withArchived, }) async { + Future searchWithHttpInfo({ bool? clip, bool? motion, num? page, String? q, String? query, bool? recent, num? size, bool? smart, String? type, bool? withArchived, }) async { // ignore: prefer_const_declarations final path = r'/search'; @@ -96,6 +100,9 @@ class SearchApi { if (motion != null) { queryParams.addAll(_queryParams('', 'motion', motion)); } + if (page != null) { + queryParams.addAll(_queryParams('', 'page', page)); + } if (q != null) { queryParams.addAll(_queryParams('', 'q', q)); } @@ -105,6 +112,9 @@ class SearchApi { if (recent != null) { queryParams.addAll(_queryParams('', 'recent', recent)); } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } if (smart != null) { queryParams.addAll(_queryParams('', 'smart', smart)); } @@ -136,19 +146,354 @@ class SearchApi { /// /// * [bool] motion: /// + /// * [num] page: + /// /// * [String] q: /// /// * [String] query: /// /// * [bool] recent: /// + /// * [num] size: + /// /// * [bool] smart: /// /// * [String] type: /// /// * [bool] withArchived: - Future search({ bool? clip, bool? motion, String? q, String? query, bool? recent, bool? smart, String? type, bool? withArchived, }) async { - final response = await searchWithHttpInfo( clip: clip, motion: motion, q: q, query: query, recent: recent, smart: smart, type: type, withArchived: withArchived, ); + Future search({ bool? clip, bool? motion, num? page, String? q, String? query, bool? recent, num? size, bool? smart, String? type, bool? withArchived, }) async { + final response = await searchWithHttpInfo( clip: clip, motion: motion, page: page, q: q, query: query, recent: recent, size: size, smart: smart, type: type, withArchived: withArchived, ); + 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), 'SearchResponseDto',) as SearchResponseDto; + + } + return null; + } + + /// Performs an HTTP 'GET /search/metadata' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] checksum: + /// + /// * [String] city: + /// + /// * [String] country: + /// + /// * [DateTime] createdAfter: + /// + /// * [DateTime] createdBefore: + /// + /// * [String] deviceAssetId: + /// + /// * [String] deviceId: + /// + /// * [String] encodedVideoPath: + /// + /// * [String] id: + /// + /// * [bool] isArchived: + /// + /// * [bool] isEncoded: + /// + /// * [bool] isExternal: + /// + /// * [bool] isFavorite: + /// + /// * [bool] isMotion: + /// + /// * [bool] isOffline: + /// + /// * [bool] isReadOnly: + /// + /// * [bool] isVisible: + /// + /// * [String] lensModel: + /// + /// * [String] libraryId: + /// + /// * [String] make: + /// + /// * [String] model: + /// + /// * [AssetOrder] order: + /// + /// * [String] originalFileName: + /// + /// * [String] originalPath: + /// + /// * [num] page: + /// + /// * [String] resizePath: + /// + /// * [num] size: + /// + /// * [String] state: + /// + /// * [DateTime] takenAfter: + /// + /// * [DateTime] takenBefore: + /// + /// * [DateTime] trashedAfter: + /// + /// * [DateTime] trashedBefore: + /// + /// * [AssetTypeEnum] type: + /// + /// * [DateTime] updatedAfter: + /// + /// * [DateTime] updatedBefore: + /// + /// * [String] webpPath: + /// + /// * [bool] withArchived: + /// + /// * [bool] withDeleted: + /// + /// * [bool] withExif: + /// + /// * [bool] withPeople: + /// + /// * [bool] withStacked: + Future searchMetadataWithHttpInfo({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { + // ignore: prefer_const_declarations + final path = r'/search/metadata'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (checksum != null) { + queryParams.addAll(_queryParams('', 'checksum', checksum)); + } + if (city != null) { + queryParams.addAll(_queryParams('', 'city', city)); + } + if (country != null) { + queryParams.addAll(_queryParams('', 'country', country)); + } + if (createdAfter != null) { + queryParams.addAll(_queryParams('', 'createdAfter', createdAfter)); + } + if (createdBefore != null) { + queryParams.addAll(_queryParams('', 'createdBefore', createdBefore)); + } + if (deviceAssetId != null) { + queryParams.addAll(_queryParams('', 'deviceAssetId', deviceAssetId)); + } + if (deviceId != null) { + queryParams.addAll(_queryParams('', 'deviceId', deviceId)); + } + if (encodedVideoPath != null) { + queryParams.addAll(_queryParams('', 'encodedVideoPath', encodedVideoPath)); + } + if (id != null) { + queryParams.addAll(_queryParams('', 'id', id)); + } + if (isArchived != null) { + queryParams.addAll(_queryParams('', 'isArchived', isArchived)); + } + if (isEncoded != null) { + queryParams.addAll(_queryParams('', 'isEncoded', isEncoded)); + } + if (isExternal != null) { + queryParams.addAll(_queryParams('', 'isExternal', isExternal)); + } + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (isMotion != null) { + queryParams.addAll(_queryParams('', 'isMotion', isMotion)); + } + if (isOffline != null) { + queryParams.addAll(_queryParams('', 'isOffline', isOffline)); + } + if (isReadOnly != null) { + queryParams.addAll(_queryParams('', 'isReadOnly', isReadOnly)); + } + if (isVisible != null) { + queryParams.addAll(_queryParams('', 'isVisible', isVisible)); + } + if (lensModel != null) { + queryParams.addAll(_queryParams('', 'lensModel', lensModel)); + } + if (libraryId != null) { + queryParams.addAll(_queryParams('', 'libraryId', libraryId)); + } + if (make != null) { + queryParams.addAll(_queryParams('', 'make', make)); + } + if (model != null) { + queryParams.addAll(_queryParams('', 'model', model)); + } + if (order != null) { + queryParams.addAll(_queryParams('', 'order', order)); + } + if (originalFileName != null) { + queryParams.addAll(_queryParams('', 'originalFileName', originalFileName)); + } + if (originalPath != null) { + queryParams.addAll(_queryParams('', 'originalPath', originalPath)); + } + if (page != null) { + queryParams.addAll(_queryParams('', 'page', page)); + } + if (resizePath != null) { + queryParams.addAll(_queryParams('', 'resizePath', resizePath)); + } + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } + if (state != null) { + queryParams.addAll(_queryParams('', 'state', state)); + } + if (takenAfter != null) { + queryParams.addAll(_queryParams('', 'takenAfter', takenAfter)); + } + if (takenBefore != null) { + queryParams.addAll(_queryParams('', 'takenBefore', takenBefore)); + } + if (trashedAfter != null) { + queryParams.addAll(_queryParams('', 'trashedAfter', trashedAfter)); + } + if (trashedBefore != null) { + queryParams.addAll(_queryParams('', 'trashedBefore', trashedBefore)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + if (updatedAfter != null) { + queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter)); + } + if (updatedBefore != null) { + queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore)); + } + if (webpPath != null) { + queryParams.addAll(_queryParams('', 'webpPath', webpPath)); + } + if (withArchived != null) { + queryParams.addAll(_queryParams('', 'withArchived', withArchived)); + } + if (withDeleted != null) { + queryParams.addAll(_queryParams('', 'withDeleted', withDeleted)); + } + if (withExif != null) { + queryParams.addAll(_queryParams('', 'withExif', withExif)); + } + if (withPeople != null) { + queryParams.addAll(_queryParams('', 'withPeople', withPeople)); + } + if (withStacked != null) { + queryParams.addAll(_queryParams('', 'withStacked', withStacked)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] checksum: + /// + /// * [String] city: + /// + /// * [String] country: + /// + /// * [DateTime] createdAfter: + /// + /// * [DateTime] createdBefore: + /// + /// * [String] deviceAssetId: + /// + /// * [String] deviceId: + /// + /// * [String] encodedVideoPath: + /// + /// * [String] id: + /// + /// * [bool] isArchived: + /// + /// * [bool] isEncoded: + /// + /// * [bool] isExternal: + /// + /// * [bool] isFavorite: + /// + /// * [bool] isMotion: + /// + /// * [bool] isOffline: + /// + /// * [bool] isReadOnly: + /// + /// * [bool] isVisible: + /// + /// * [String] lensModel: + /// + /// * [String] libraryId: + /// + /// * [String] make: + /// + /// * [String] model: + /// + /// * [AssetOrder] order: + /// + /// * [String] originalFileName: + /// + /// * [String] originalPath: + /// + /// * [num] page: + /// + /// * [String] resizePath: + /// + /// * [num] size: + /// + /// * [String] state: + /// + /// * [DateTime] takenAfter: + /// + /// * [DateTime] takenBefore: + /// + /// * [DateTime] trashedAfter: + /// + /// * [DateTime] trashedBefore: + /// + /// * [AssetTypeEnum] type: + /// + /// * [DateTime] updatedAfter: + /// + /// * [DateTime] updatedBefore: + /// + /// * [String] webpPath: + /// + /// * [bool] withArchived: + /// + /// * [bool] withDeleted: + /// + /// * [bool] withExif: + /// + /// * [bool] withPeople: + /// + /// * [bool] withStacked: + Future searchMetadata({ String? checksum, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceAssetId, String? deviceId, String? encodedVideoPath, String? id, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, AssetOrder? order, String? originalFileName, String? originalPath, num? page, String? resizePath, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, String? webpPath, bool? withArchived, bool? withDeleted, bool? withExif, bool? withPeople, bool? withStacked, }) async { + final response = await searchMetadataWithHttpInfo( checksum: checksum, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceAssetId: deviceAssetId, deviceId: deviceId, encodedVideoPath: encodedVideoPath, id: id, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, order: order, originalFileName: originalFileName, originalPath: originalPath, page: page, resizePath: resizePath, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, webpPath: webpPath, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, withPeople: withPeople, withStacked: withStacked, ); if (response.statusCode >= HttpStatus.badRequest) { throw ApiException(response.statusCode, await _decodeBodyBytes(response)); } @@ -220,4 +565,263 @@ class SearchApi { } return null; } + + /// Performs an HTTP 'GET /search/smart' operation and returns the [Response]. + /// Parameters: + /// + /// * [String] query (required): + /// + /// * [String] city: + /// + /// * [String] country: + /// + /// * [DateTime] createdAfter: + /// + /// * [DateTime] createdBefore: + /// + /// * [String] deviceId: + /// + /// * [bool] isArchived: + /// + /// * [bool] isEncoded: + /// + /// * [bool] isExternal: + /// + /// * [bool] isFavorite: + /// + /// * [bool] isMotion: + /// + /// * [bool] isOffline: + /// + /// * [bool] isReadOnly: + /// + /// * [bool] isVisible: + /// + /// * [String] lensModel: + /// + /// * [String] libraryId: + /// + /// * [String] make: + /// + /// * [String] model: + /// + /// * [num] page: + /// + /// * [num] size: + /// + /// * [String] state: + /// + /// * [DateTime] takenAfter: + /// + /// * [DateTime] takenBefore: + /// + /// * [DateTime] trashedAfter: + /// + /// * [DateTime] trashedBefore: + /// + /// * [AssetTypeEnum] type: + /// + /// * [DateTime] updatedAfter: + /// + /// * [DateTime] updatedBefore: + /// + /// * [bool] withArchived: + /// + /// * [bool] withDeleted: + /// + /// * [bool] withExif: + Future searchSmartWithHttpInfo(String query, { String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, num? page, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, bool? withArchived, bool? withDeleted, bool? withExif, }) async { + // ignore: prefer_const_declarations + final path = r'/search/smart'; + + // ignore: prefer_final_locals + Object? postBody; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + if (city != null) { + queryParams.addAll(_queryParams('', 'city', city)); + } + if (country != null) { + queryParams.addAll(_queryParams('', 'country', country)); + } + if (createdAfter != null) { + queryParams.addAll(_queryParams('', 'createdAfter', createdAfter)); + } + if (createdBefore != null) { + queryParams.addAll(_queryParams('', 'createdBefore', createdBefore)); + } + if (deviceId != null) { + queryParams.addAll(_queryParams('', 'deviceId', deviceId)); + } + if (isArchived != null) { + queryParams.addAll(_queryParams('', 'isArchived', isArchived)); + } + if (isEncoded != null) { + queryParams.addAll(_queryParams('', 'isEncoded', isEncoded)); + } + if (isExternal != null) { + queryParams.addAll(_queryParams('', 'isExternal', isExternal)); + } + if (isFavorite != null) { + queryParams.addAll(_queryParams('', 'isFavorite', isFavorite)); + } + if (isMotion != null) { + queryParams.addAll(_queryParams('', 'isMotion', isMotion)); + } + if (isOffline != null) { + queryParams.addAll(_queryParams('', 'isOffline', isOffline)); + } + if (isReadOnly != null) { + queryParams.addAll(_queryParams('', 'isReadOnly', isReadOnly)); + } + if (isVisible != null) { + queryParams.addAll(_queryParams('', 'isVisible', isVisible)); + } + if (lensModel != null) { + queryParams.addAll(_queryParams('', 'lensModel', lensModel)); + } + if (libraryId != null) { + queryParams.addAll(_queryParams('', 'libraryId', libraryId)); + } + if (make != null) { + queryParams.addAll(_queryParams('', 'make', make)); + } + if (model != null) { + queryParams.addAll(_queryParams('', 'model', model)); + } + if (page != null) { + queryParams.addAll(_queryParams('', 'page', page)); + } + queryParams.addAll(_queryParams('', 'query', query)); + if (size != null) { + queryParams.addAll(_queryParams('', 'size', size)); + } + if (state != null) { + queryParams.addAll(_queryParams('', 'state', state)); + } + if (takenAfter != null) { + queryParams.addAll(_queryParams('', 'takenAfter', takenAfter)); + } + if (takenBefore != null) { + queryParams.addAll(_queryParams('', 'takenBefore', takenBefore)); + } + if (trashedAfter != null) { + queryParams.addAll(_queryParams('', 'trashedAfter', trashedAfter)); + } + if (trashedBefore != null) { + queryParams.addAll(_queryParams('', 'trashedBefore', trashedBefore)); + } + if (type != null) { + queryParams.addAll(_queryParams('', 'type', type)); + } + if (updatedAfter != null) { + queryParams.addAll(_queryParams('', 'updatedAfter', updatedAfter)); + } + if (updatedBefore != null) { + queryParams.addAll(_queryParams('', 'updatedBefore', updatedBefore)); + } + if (withArchived != null) { + queryParams.addAll(_queryParams('', 'withArchived', withArchived)); + } + if (withDeleted != null) { + queryParams.addAll(_queryParams('', 'withDeleted', withDeleted)); + } + if (withExif != null) { + queryParams.addAll(_queryParams('', 'withExif', withExif)); + } + + const contentTypes = []; + + + return apiClient.invokeAPI( + path, + 'GET', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [String] query (required): + /// + /// * [String] city: + /// + /// * [String] country: + /// + /// * [DateTime] createdAfter: + /// + /// * [DateTime] createdBefore: + /// + /// * [String] deviceId: + /// + /// * [bool] isArchived: + /// + /// * [bool] isEncoded: + /// + /// * [bool] isExternal: + /// + /// * [bool] isFavorite: + /// + /// * [bool] isMotion: + /// + /// * [bool] isOffline: + /// + /// * [bool] isReadOnly: + /// + /// * [bool] isVisible: + /// + /// * [String] lensModel: + /// + /// * [String] libraryId: + /// + /// * [String] make: + /// + /// * [String] model: + /// + /// * [num] page: + /// + /// * [num] size: + /// + /// * [String] state: + /// + /// * [DateTime] takenAfter: + /// + /// * [DateTime] takenBefore: + /// + /// * [DateTime] trashedAfter: + /// + /// * [DateTime] trashedBefore: + /// + /// * [AssetTypeEnum] type: + /// + /// * [DateTime] updatedAfter: + /// + /// * [DateTime] updatedBefore: + /// + /// * [bool] withArchived: + /// + /// * [bool] withDeleted: + /// + /// * [bool] withExif: + Future searchSmart(String query, { String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, String? deviceId, bool? isArchived, bool? isEncoded, bool? isExternal, bool? isFavorite, bool? isMotion, bool? isOffline, bool? isReadOnly, bool? isVisible, String? lensModel, String? libraryId, String? make, String? model, num? page, num? size, String? state, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, bool? withArchived, bool? withDeleted, bool? withExif, }) async { + final response = await searchSmartWithHttpInfo(query, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, deviceId: deviceId, isArchived: isArchived, isEncoded: isEncoded, isExternal: isExternal, isFavorite: isFavorite, isMotion: isMotion, isOffline: isOffline, isReadOnly: isReadOnly, isVisible: isVisible, lensModel: lensModel, libraryId: libraryId, make: make, model: model, page: page, size: size, state: state, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, withArchived: withArchived, withDeleted: withDeleted, withExif: withExif, ); + 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), 'SearchResponseDto',) as SearchResponseDto; + + } + return null; + } } diff --git a/mobile/openapi/lib/model/search_asset_response_dto.dart b/mobile/openapi/lib/model/search_asset_response_dto.dart index 98291307d421b..abdbc5d4e35d6 100644 --- a/mobile/openapi/lib/model/search_asset_response_dto.dart +++ b/mobile/openapi/lib/model/search_asset_response_dto.dart @@ -16,6 +16,7 @@ class SearchAssetResponseDto { required this.count, this.facets = const [], this.items = const [], + required this.nextPage, required this.total, }); @@ -25,6 +26,8 @@ class SearchAssetResponseDto { List items; + String? nextPage; + int total; @override @@ -32,6 +35,7 @@ class SearchAssetResponseDto { other.count == count && _deepEquality.equals(other.facets, facets) && _deepEquality.equals(other.items, items) && + other.nextPage == nextPage && other.total == total; @override @@ -40,16 +44,22 @@ class SearchAssetResponseDto { (count.hashCode) + (facets.hashCode) + (items.hashCode) + + (nextPage == null ? 0 : nextPage!.hashCode) + (total.hashCode); @override - String toString() => 'SearchAssetResponseDto[count=$count, facets=$facets, items=$items, total=$total]'; + String toString() => 'SearchAssetResponseDto[count=$count, facets=$facets, items=$items, nextPage=$nextPage, total=$total]'; Map toJson() { final json = {}; json[r'count'] = this.count; json[r'facets'] = this.facets; json[r'items'] = this.items; + if (this.nextPage != null) { + json[r'nextPage'] = this.nextPage; + } else { + // json[r'nextPage'] = null; + } json[r'total'] = this.total; return json; } @@ -65,6 +75,7 @@ class SearchAssetResponseDto { count: mapValueOfType(json, r'count')!, facets: SearchFacetResponseDto.listFromJson(json[r'facets']), items: AssetResponseDto.listFromJson(json[r'items']), + nextPage: mapValueOfType(json, r'nextPage'), total: mapValueOfType(json, r'total')!, ); } @@ -116,6 +127,7 @@ class SearchAssetResponseDto { 'count', 'facets', 'items', + 'nextPage', 'total', }; } diff --git a/mobile/openapi/test/asset_api_test.dart b/mobile/openapi/test/asset_api_test.dart index c34c85466c999..a7a63f38e633f 100644 --- a/mobile/openapi/test/asset_api_test.dart +++ b/mobile/openapi/test/asset_api_test.dart @@ -110,7 +110,7 @@ void main() { // TODO }); - //Future> searchAssets({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async + //Future> searchAssets({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async test('test searchAssets', () async { // TODO }); diff --git a/mobile/openapi/test/search_api_test.dart b/mobile/openapi/test/search_api_test.dart index 769ad3194367b..be12c7e1f821e 100644 --- a/mobile/openapi/test/search_api_test.dart +++ b/mobile/openapi/test/search_api_test.dart @@ -22,15 +22,25 @@ void main() { // TODO }); - //Future search({ bool clip, bool motion, String q, String query, bool recent, bool smart, String type, bool withArchived }) async + //Future search({ bool clip, bool motion, num page, String q, String query, bool recent, num size, bool smart, String type, bool withArchived }) async test('test search', () async { // TODO }); + //Future searchMetadata({ String checksum, String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceAssetId, String deviceId, String encodedVideoPath, String id, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, AssetOrder order, String originalFileName, String originalPath, num page, String resizePath, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, String webpPath, bool withArchived, bool withDeleted, bool withExif, bool withPeople, bool withStacked }) async + test('test searchMetadata', () async { + // TODO + }); + //Future> searchPerson(String name, { bool withHidden }) async test('test searchPerson', () async { // TODO }); + //Future searchSmart(String query, { String city, String country, DateTime createdAfter, DateTime createdBefore, String deviceId, bool isArchived, bool isEncoded, bool isExternal, bool isFavorite, bool isMotion, bool isOffline, bool isReadOnly, bool isVisible, String lensModel, String libraryId, String make, String model, num page, num size, String state, DateTime takenAfter, DateTime takenBefore, DateTime trashedAfter, DateTime trashedBefore, AssetTypeEnum type, DateTime updatedAfter, DateTime updatedBefore, bool withArchived, bool withDeleted, bool withExif }) async + test('test searchSmart', () async { + // TODO + }); + }); } diff --git a/mobile/openapi/test/search_asset_response_dto_test.dart b/mobile/openapi/test/search_asset_response_dto_test.dart index 87a7a61e9ddf7..56e8276171759 100644 --- a/mobile/openapi/test/search_asset_response_dto_test.dart +++ b/mobile/openapi/test/search_asset_response_dto_test.dart @@ -31,6 +31,11 @@ void main() { // TODO }); + // String nextPage + test('to test the property `nextPage`', () async { + // TODO + }); + // int total test('to test the property `total`', () async { // TODO diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 4c6317c2c2dcb..501218fc8c3d4 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -2130,6 +2130,7 @@ }, "/assets": { "get": { + "deprecated": true, "operationId": "searchAssets", "parameters": [ { @@ -2430,6 +2431,14 @@ "type": "string" } }, + { + "name": "withArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, { "name": "withDeleted", "required": false, @@ -4354,6 +4363,7 @@ }, "/search": { "get": { + "deprecated": true, "operationId": "search", "parameters": [ { @@ -4374,6 +4384,14 @@ "type": "boolean" } }, + { + "name": "page", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + }, { "name": "q", "required": false, @@ -4398,6 +4416,14 @@ "type": "boolean" } }, + { + "name": "size", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + }, { "name": "smart", "required": false, @@ -4492,6 +4518,377 @@ ] } }, + "/search/metadata": { + "get": { + "operationId": "searchMetadata", + "parameters": [ + { + "name": "checksum", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "city", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "country", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "createdAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "createdBefore", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "deviceAssetId", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "deviceId", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "encodedVideoPath", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "id", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "isArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isEncoded", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isExternal", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isFavorite", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isMotion", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isOffline", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isReadOnly", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isVisible", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "lensModel", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "libraryId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "make", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "model", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "order", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/AssetOrder" + } + }, + { + "name": "originalFileName", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "originalPath", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "resizePath", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "size", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "state", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "takenAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "takenBefore", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "trashedAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "trashedBefore", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "type", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/AssetTypeEnum" + } + }, + { + "name": "updatedAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "updatedBefore", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "webpPath", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "withArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "withDeleted", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "withExif", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "withPeople", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "withStacked", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Search" + ] + } + }, "/search/person": { "get": { "operationId": "searchPerson", @@ -4544,6 +4941,296 @@ ] } }, + "/search/smart": { + "get": { + "operationId": "searchSmart", + "parameters": [ + { + "name": "city", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "country", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "createdAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "createdBefore", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "deviceId", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "isArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isEncoded", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isExternal", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isFavorite", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isMotion", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isOffline", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isReadOnly", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "isVisible", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "lensModel", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "libraryId", + "required": false, + "in": "query", + "schema": { + "format": "uuid", + "type": "string" + } + }, + { + "name": "make", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "model", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "page", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "query", + "required": true, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "size", + "required": false, + "in": "query", + "schema": { + "type": "number" + } + }, + { + "name": "state", + "required": false, + "in": "query", + "schema": { + "type": "string" + } + }, + { + "name": "takenAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "takenBefore", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "trashedAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "trashedBefore", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "type", + "required": false, + "in": "query", + "schema": { + "$ref": "#/components/schemas/AssetTypeEnum" + } + }, + { + "name": "updatedAfter", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "updatedBefore", + "required": false, + "in": "query", + "schema": { + "format": "date-time", + "type": "string" + } + }, + { + "name": "withArchived", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "withDeleted", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + }, + { + "name": "withExif", + "required": false, + "in": "query", + "schema": { + "type": "boolean" + } + } + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SearchResponseDto" + } + } + }, + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Search" + ] + } + }, "/server-info": { "get": { "operationId": "getServerInfo", @@ -8458,6 +9145,10 @@ }, "type": "array" }, + "nextPage": { + "nullable": true, + "type": "string" + }, "total": { "type": "integer" } @@ -8466,6 +9157,7 @@ "count", "facets", "items", + "nextPage", "total" ], "type": "object" diff --git a/open-api/typescript-sdk/axios-client/api.ts b/open-api/typescript-sdk/axios-client/api.ts index 542fa0580ea98..c231aa61713d0 100644 --- a/open-api/typescript-sdk/axios-client/api.ts +++ b/open-api/typescript-sdk/axios-client/api.ts @@ -2887,6 +2887,12 @@ export interface SearchAssetResponseDto { * @memberof SearchAssetResponseDto */ 'items': Array; + /** + * + * @type {string} + * @memberof SearchAssetResponseDto + */ + 'nextPage': string | null; /** * * @type {number} @@ -7760,14 +7766,16 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration * @param {string} [updatedAfter] * @param {string} [updatedBefore] * @param {string} [webpPath] + * @param {boolean} [withArchived] * @param {boolean} [withDeleted] * @param {boolean} [withExif] * @param {boolean} [withPeople] * @param {boolean} [withStacked] * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ - searchAssets: async (checksum?: string, city?: string, country?: string, createdAfter?: string, createdBefore?: string, deviceAssetId?: string, deviceId?: string, encodedVideoPath?: string, id?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, lensModel?: string, libraryId?: string, make?: string, model?: string, order?: AssetOrder, originalFileName?: string, originalPath?: string, page?: number, resizePath?: string, size?: number, state?: string, takenAfter?: string, takenBefore?: string, trashedAfter?: string, trashedBefore?: string, type?: AssetTypeEnum, updatedAfter?: string, updatedBefore?: string, webpPath?: string, withDeleted?: boolean, withExif?: boolean, withPeople?: boolean, withStacked?: boolean, options: RawAxiosRequestConfig = {}): Promise => { + searchAssets: async (checksum?: string, city?: string, country?: string, createdAfter?: string, createdBefore?: string, deviceAssetId?: string, deviceId?: string, encodedVideoPath?: string, id?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, lensModel?: string, libraryId?: string, make?: string, model?: string, order?: AssetOrder, originalFileName?: string, originalPath?: string, page?: number, resizePath?: string, size?: number, state?: string, takenAfter?: string, takenBefore?: string, trashedAfter?: string, trashedBefore?: string, type?: AssetTypeEnum, updatedAfter?: string, updatedBefore?: string, webpPath?: string, withArchived?: boolean, withDeleted?: boolean, withExif?: boolean, withPeople?: boolean, withStacked?: boolean, options: RawAxiosRequestConfig = {}): Promise => { const localVarPath = `/assets`; // use dummy base URL string because the URL constructor only accepts absolute URLs. const localVarUrlObj = new URL(localVarPath, DUMMY_BASE_URL); @@ -7949,6 +7957,10 @@ export const AssetApiAxiosParamCreator = function (configuration?: Configuration localVarQueryParameter['webpPath'] = webpPath; } + if (withArchived !== undefined) { + localVarQueryParameter['withArchived'] = withArchived; + } + if (withDeleted !== undefined) { localVarQueryParameter['withDeleted'] = withDeleted; } @@ -8585,15 +8597,17 @@ export const AssetApiFp = function(configuration?: Configuration) { * @param {string} [updatedAfter] * @param {string} [updatedBefore] * @param {string} [webpPath] + * @param {boolean} [withArchived] * @param {boolean} [withDeleted] * @param {boolean} [withExif] * @param {boolean} [withPeople] * @param {boolean} [withStacked] * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ - async searchAssets(checksum?: string, city?: string, country?: string, createdAfter?: string, createdBefore?: string, deviceAssetId?: string, deviceId?: string, encodedVideoPath?: string, id?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, lensModel?: string, libraryId?: string, make?: string, model?: string, order?: AssetOrder, originalFileName?: string, originalPath?: string, page?: number, resizePath?: string, size?: number, state?: string, takenAfter?: string, takenBefore?: string, trashedAfter?: string, trashedBefore?: string, type?: AssetTypeEnum, updatedAfter?: string, updatedBefore?: string, webpPath?: string, withDeleted?: boolean, withExif?: boolean, withPeople?: boolean, withStacked?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { - const localVarAxiosArgs = await localVarAxiosParamCreator.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withDeleted, withExif, withPeople, withStacked, options); + async searchAssets(checksum?: string, city?: string, country?: string, createdAfter?: string, createdBefore?: string, deviceAssetId?: string, deviceId?: string, encodedVideoPath?: string, id?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, lensModel?: string, libraryId?: string, make?: string, model?: string, order?: AssetOrder, originalFileName?: string, originalPath?: string, page?: number, resizePath?: string, size?: number, state?: string, takenAfter?: string, takenBefore?: string, trashedAfter?: string, trashedBefore?: string, type?: AssetTypeEnum, updatedAfter?: string, updatedBefore?: string, webpPath?: string, withArchived?: boolean, withDeleted?: boolean, withExif?: boolean, withPeople?: boolean, withStacked?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise>> { + const localVarAxiosArgs = await localVarAxiosParamCreator.searchAssets(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked, options); const index = configuration?.serverIndex ?? 0; const operationBasePath = operationServerMap['AssetApi.searchAssets']?.[index]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); @@ -8841,10 +8855,11 @@ export const AssetApiFactory = function (configuration?: Configuration, basePath * * @param {AssetApiSearchAssetsRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ searchAssets(requestParameters: AssetApiSearchAssetsRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise> { - return localVarFp.searchAssets(requestParameters.checksum, requestParameters.city, requestParameters.country, requestParameters.createdAfter, requestParameters.createdBefore, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.encodedVideoPath, requestParameters.id, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.lensModel, requestParameters.libraryId, requestParameters.make, requestParameters.model, requestParameters.order, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.page, requestParameters.resizePath, requestParameters.size, requestParameters.state, requestParameters.takenAfter, requestParameters.takenBefore, requestParameters.trashedAfter, requestParameters.trashedBefore, requestParameters.type, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.webpPath, requestParameters.withDeleted, requestParameters.withExif, requestParameters.withPeople, requestParameters.withStacked, options).then((request) => request(axios, basePath)); + return localVarFp.searchAssets(requestParameters.checksum, requestParameters.city, requestParameters.country, requestParameters.createdAfter, requestParameters.createdBefore, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.encodedVideoPath, requestParameters.id, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.lensModel, requestParameters.libraryId, requestParameters.make, requestParameters.model, requestParameters.order, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.page, requestParameters.resizePath, requestParameters.size, requestParameters.state, requestParameters.takenAfter, requestParameters.takenBefore, requestParameters.trashedAfter, requestParameters.trashedBefore, requestParameters.type, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.webpPath, requestParameters.withArchived, requestParameters.withDeleted, requestParameters.withExif, requestParameters.withPeople, requestParameters.withStacked, options).then((request) => request(axios, basePath)); }, /** * @@ -9593,6 +9608,13 @@ export interface AssetApiSearchAssetsRequest { */ readonly webpPath?: string + /** + * + * @type {boolean} + * @memberof AssetApiSearchAssets + */ + readonly withArchived?: boolean + /** * * @type {boolean} @@ -10020,11 +10042,12 @@ export class AssetApi extends BaseAPI { * * @param {AssetApiSearchAssetsRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} * @memberof AssetApi */ public searchAssets(requestParameters: AssetApiSearchAssetsRequest = {}, options?: RawAxiosRequestConfig) { - return AssetApiFp(this.configuration).searchAssets(requestParameters.checksum, requestParameters.city, requestParameters.country, requestParameters.createdAfter, requestParameters.createdBefore, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.encodedVideoPath, requestParameters.id, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.lensModel, requestParameters.libraryId, requestParameters.make, requestParameters.model, requestParameters.order, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.page, requestParameters.resizePath, requestParameters.size, requestParameters.state, requestParameters.takenAfter, requestParameters.takenBefore, requestParameters.trashedAfter, requestParameters.trashedBefore, requestParameters.type, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.webpPath, requestParameters.withDeleted, requestParameters.withExif, requestParameters.withPeople, requestParameters.withStacked, options).then((request) => request(this.axios, this.basePath)); + return AssetApiFp(this.configuration).searchAssets(requestParameters.checksum, requestParameters.city, requestParameters.country, requestParameters.createdAfter, requestParameters.createdBefore, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.encodedVideoPath, requestParameters.id, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.lensModel, requestParameters.libraryId, requestParameters.make, requestParameters.model, requestParameters.order, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.page, requestParameters.resizePath, requestParameters.size, requestParameters.state, requestParameters.takenAfter, requestParameters.takenBefore, requestParameters.trashedAfter, requestParameters.trashedBefore, requestParameters.type, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.webpPath, requestParameters.withArchived, requestParameters.withDeleted, requestParameters.withExif, requestParameters.withPeople, requestParameters.withStacked, options).then((request) => request(this.axios, this.basePath)); } /** @@ -14500,16 +14523,19 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio * * @param {boolean} [clip] @deprecated * @param {boolean} [motion] + * @param {number} [page] * @param {string} [q] * @param {string} [query] * @param {boolean} [recent] + * @param {number} [size] * @param {boolean} [smart] * @param {SearchTypeEnum} [type] * @param {boolean} [withArchived] * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ - search: async (clip?: boolean, motion?: boolean, q?: string, query?: string, recent?: boolean, smart?: boolean, type?: SearchTypeEnum, withArchived?: boolean, options: RawAxiosRequestConfig = {}): Promise => { + search: async (clip?: boolean, motion?: boolean, page?: number, q?: string, query?: string, recent?: boolean, size?: number, smart?: boolean, type?: SearchTypeEnum, withArchived?: boolean, options: RawAxiosRequestConfig = {}): 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); @@ -14539,6 +14565,10 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio localVarQueryParameter['motion'] = motion; } + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + if (q !== undefined) { localVarQueryParameter['q'] = q; } @@ -14551,6 +14581,10 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio localVarQueryParameter['recent'] = recent; } + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + if (smart !== undefined) { localVarQueryParameter['smart'] = smart; } @@ -14565,6 +14599,265 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} [checksum] + * @param {string} [city] + * @param {string} [country] + * @param {string} [createdAfter] + * @param {string} [createdBefore] + * @param {string} [deviceAssetId] + * @param {string} [deviceId] + * @param {string} [encodedVideoPath] + * @param {string} [id] + * @param {boolean} [isArchived] + * @param {boolean} [isEncoded] + * @param {boolean} [isExternal] + * @param {boolean} [isFavorite] + * @param {boolean} [isMotion] + * @param {boolean} [isOffline] + * @param {boolean} [isReadOnly] + * @param {boolean} [isVisible] + * @param {string} [lensModel] + * @param {string} [libraryId] + * @param {string} [make] + * @param {string} [model] + * @param {AssetOrder} [order] + * @param {string} [originalFileName] + * @param {string} [originalPath] + * @param {number} [page] + * @param {string} [resizePath] + * @param {number} [size] + * @param {string} [state] + * @param {string} [takenAfter] + * @param {string} [takenBefore] + * @param {string} [trashedAfter] + * @param {string} [trashedBefore] + * @param {AssetTypeEnum} [type] + * @param {string} [updatedAfter] + * @param {string} [updatedBefore] + * @param {string} [webpPath] + * @param {boolean} [withArchived] + * @param {boolean} [withDeleted] + * @param {boolean} [withExif] + * @param {boolean} [withPeople] + * @param {boolean} [withStacked] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + searchMetadata: async (checksum?: string, city?: string, country?: string, createdAfter?: string, createdBefore?: string, deviceAssetId?: string, deviceId?: string, encodedVideoPath?: string, id?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, lensModel?: string, libraryId?: string, make?: string, model?: string, order?: AssetOrder, originalFileName?: string, originalPath?: string, page?: number, resizePath?: string, size?: number, state?: string, takenAfter?: string, takenBefore?: string, trashedAfter?: string, trashedBefore?: string, type?: AssetTypeEnum, updatedAfter?: string, updatedBefore?: string, webpPath?: string, withArchived?: boolean, withDeleted?: boolean, withExif?: boolean, withPeople?: boolean, withStacked?: boolean, options: RawAxiosRequestConfig = {}): Promise => { + const localVarPath = `/search/metadata`; + // 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 api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (checksum !== undefined) { + localVarQueryParameter['checksum'] = checksum; + } + + if (city !== undefined) { + localVarQueryParameter['city'] = city; + } + + if (country !== undefined) { + localVarQueryParameter['country'] = country; + } + + if (createdAfter !== undefined) { + localVarQueryParameter['createdAfter'] = (createdAfter as any instanceof Date) ? + (createdAfter as any).toISOString() : + createdAfter; + } + + if (createdBefore !== undefined) { + localVarQueryParameter['createdBefore'] = (createdBefore as any instanceof Date) ? + (createdBefore as any).toISOString() : + createdBefore; + } + + if (deviceAssetId !== undefined) { + localVarQueryParameter['deviceAssetId'] = deviceAssetId; + } + + if (deviceId !== undefined) { + localVarQueryParameter['deviceId'] = deviceId; + } + + if (encodedVideoPath !== undefined) { + localVarQueryParameter['encodedVideoPath'] = encodedVideoPath; + } + + if (id !== undefined) { + localVarQueryParameter['id'] = id; + } + + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + + if (isEncoded !== undefined) { + localVarQueryParameter['isEncoded'] = isEncoded; + } + + if (isExternal !== undefined) { + localVarQueryParameter['isExternal'] = isExternal; + } + + if (isFavorite !== undefined) { + localVarQueryParameter['isFavorite'] = isFavorite; + } + + if (isMotion !== undefined) { + localVarQueryParameter['isMotion'] = isMotion; + } + + if (isOffline !== undefined) { + localVarQueryParameter['isOffline'] = isOffline; + } + + if (isReadOnly !== undefined) { + localVarQueryParameter['isReadOnly'] = isReadOnly; + } + + if (isVisible !== undefined) { + localVarQueryParameter['isVisible'] = isVisible; + } + + if (lensModel !== undefined) { + localVarQueryParameter['lensModel'] = lensModel; + } + + if (libraryId !== undefined) { + localVarQueryParameter['libraryId'] = libraryId; + } + + if (make !== undefined) { + localVarQueryParameter['make'] = make; + } + + if (model !== undefined) { + localVarQueryParameter['model'] = model; + } + + if (order !== undefined) { + localVarQueryParameter['order'] = order; + } + + if (originalFileName !== undefined) { + localVarQueryParameter['originalFileName'] = originalFileName; + } + + if (originalPath !== undefined) { + localVarQueryParameter['originalPath'] = originalPath; + } + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + if (resizePath !== undefined) { + localVarQueryParameter['resizePath'] = resizePath; + } + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (state !== undefined) { + localVarQueryParameter['state'] = state; + } + + if (takenAfter !== undefined) { + localVarQueryParameter['takenAfter'] = (takenAfter as any instanceof Date) ? + (takenAfter as any).toISOString() : + takenAfter; + } + + if (takenBefore !== undefined) { + localVarQueryParameter['takenBefore'] = (takenBefore as any instanceof Date) ? + (takenBefore as any).toISOString() : + takenBefore; + } + + if (trashedAfter !== undefined) { + localVarQueryParameter['trashedAfter'] = (trashedAfter as any instanceof Date) ? + (trashedAfter as any).toISOString() : + trashedAfter; + } + + if (trashedBefore !== undefined) { + localVarQueryParameter['trashedBefore'] = (trashedBefore as any instanceof Date) ? + (trashedBefore as any).toISOString() : + trashedBefore; + } + + if (type !== undefined) { + localVarQueryParameter['type'] = type; + } + + if (updatedAfter !== undefined) { + localVarQueryParameter['updatedAfter'] = (updatedAfter as any instanceof Date) ? + (updatedAfter as any).toISOString() : + updatedAfter; + } + + if (updatedBefore !== undefined) { + localVarQueryParameter['updatedBefore'] = (updatedBefore as any instanceof Date) ? + (updatedBefore as any).toISOString() : + updatedBefore; + } + + if (webpPath !== undefined) { + localVarQueryParameter['webpPath'] = webpPath; + } + + if (withArchived !== undefined) { + localVarQueryParameter['withArchived'] = withArchived; + } + + if (withDeleted !== undefined) { + localVarQueryParameter['withDeleted'] = withDeleted; + } + + if (withExif !== undefined) { + localVarQueryParameter['withExif'] = withExif; + } + + if (withPeople !== undefined) { + localVarQueryParameter['withPeople'] = withPeople; + } + + if (withStacked !== undefined) { + localVarQueryParameter['withStacked'] = withStacked; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -14615,6 +14908,217 @@ export const SearchApiAxiosParamCreator = function (configuration?: Configuratio + setSearchParams(localVarUrlObj, localVarQueryParameter); + let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; + localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; + + return { + url: toPathString(localVarUrlObj), + options: localVarRequestOptions, + }; + }, + /** + * + * @param {string} query + * @param {string} [city] + * @param {string} [country] + * @param {string} [createdAfter] + * @param {string} [createdBefore] + * @param {string} [deviceId] + * @param {boolean} [isArchived] + * @param {boolean} [isEncoded] + * @param {boolean} [isExternal] + * @param {boolean} [isFavorite] + * @param {boolean} [isMotion] + * @param {boolean} [isOffline] + * @param {boolean} [isReadOnly] + * @param {boolean} [isVisible] + * @param {string} [lensModel] + * @param {string} [libraryId] + * @param {string} [make] + * @param {string} [model] + * @param {number} [page] + * @param {number} [size] + * @param {string} [state] + * @param {string} [takenAfter] + * @param {string} [takenBefore] + * @param {string} [trashedAfter] + * @param {string} [trashedBefore] + * @param {AssetTypeEnum} [type] + * @param {string} [updatedAfter] + * @param {string} [updatedBefore] + * @param {boolean} [withArchived] + * @param {boolean} [withDeleted] + * @param {boolean} [withExif] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + searchSmart: async (query: string, city?: string, country?: string, createdAfter?: string, createdBefore?: string, deviceId?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, lensModel?: string, libraryId?: string, make?: string, model?: string, page?: number, size?: number, state?: string, takenAfter?: string, takenBefore?: string, trashedAfter?: string, trashedBefore?: string, type?: AssetTypeEnum, updatedAfter?: string, updatedBefore?: string, withArchived?: boolean, withDeleted?: boolean, withExif?: boolean, options: RawAxiosRequestConfig = {}): Promise => { + // verify required parameter 'query' is not null or undefined + assertParamExists('searchSmart', 'query', query) + const localVarPath = `/search/smart`; + // 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 api_key required + await setApiKeyToObject(localVarHeaderParameter, "x-api-key", configuration) + + // authentication bearer required + // http bearer authentication required + await setBearerAuthToObject(localVarHeaderParameter, configuration) + + if (city !== undefined) { + localVarQueryParameter['city'] = city; + } + + if (country !== undefined) { + localVarQueryParameter['country'] = country; + } + + if (createdAfter !== undefined) { + localVarQueryParameter['createdAfter'] = (createdAfter as any instanceof Date) ? + (createdAfter as any).toISOString() : + createdAfter; + } + + if (createdBefore !== undefined) { + localVarQueryParameter['createdBefore'] = (createdBefore as any instanceof Date) ? + (createdBefore as any).toISOString() : + createdBefore; + } + + if (deviceId !== undefined) { + localVarQueryParameter['deviceId'] = deviceId; + } + + if (isArchived !== undefined) { + localVarQueryParameter['isArchived'] = isArchived; + } + + if (isEncoded !== undefined) { + localVarQueryParameter['isEncoded'] = isEncoded; + } + + if (isExternal !== undefined) { + localVarQueryParameter['isExternal'] = isExternal; + } + + if (isFavorite !== undefined) { + localVarQueryParameter['isFavorite'] = isFavorite; + } + + if (isMotion !== undefined) { + localVarQueryParameter['isMotion'] = isMotion; + } + + if (isOffline !== undefined) { + localVarQueryParameter['isOffline'] = isOffline; + } + + if (isReadOnly !== undefined) { + localVarQueryParameter['isReadOnly'] = isReadOnly; + } + + if (isVisible !== undefined) { + localVarQueryParameter['isVisible'] = isVisible; + } + + if (lensModel !== undefined) { + localVarQueryParameter['lensModel'] = lensModel; + } + + if (libraryId !== undefined) { + localVarQueryParameter['libraryId'] = libraryId; + } + + if (make !== undefined) { + localVarQueryParameter['make'] = make; + } + + if (model !== undefined) { + localVarQueryParameter['model'] = model; + } + + if (page !== undefined) { + localVarQueryParameter['page'] = page; + } + + if (query !== undefined) { + localVarQueryParameter['query'] = query; + } + + if (size !== undefined) { + localVarQueryParameter['size'] = size; + } + + if (state !== undefined) { + localVarQueryParameter['state'] = state; + } + + if (takenAfter !== undefined) { + localVarQueryParameter['takenAfter'] = (takenAfter as any instanceof Date) ? + (takenAfter as any).toISOString() : + takenAfter; + } + + if (takenBefore !== undefined) { + localVarQueryParameter['takenBefore'] = (takenBefore as any instanceof Date) ? + (takenBefore as any).toISOString() : + takenBefore; + } + + if (trashedAfter !== undefined) { + localVarQueryParameter['trashedAfter'] = (trashedAfter as any instanceof Date) ? + (trashedAfter as any).toISOString() : + trashedAfter; + } + + if (trashedBefore !== undefined) { + localVarQueryParameter['trashedBefore'] = (trashedBefore as any instanceof Date) ? + (trashedBefore as any).toISOString() : + trashedBefore; + } + + if (type !== undefined) { + localVarQueryParameter['type'] = type; + } + + if (updatedAfter !== undefined) { + localVarQueryParameter['updatedAfter'] = (updatedAfter as any instanceof Date) ? + (updatedAfter as any).toISOString() : + updatedAfter; + } + + if (updatedBefore !== undefined) { + localVarQueryParameter['updatedBefore'] = (updatedBefore as any instanceof Date) ? + (updatedBefore as any).toISOString() : + updatedBefore; + } + + if (withArchived !== undefined) { + localVarQueryParameter['withArchived'] = withArchived; + } + + if (withDeleted !== undefined) { + localVarQueryParameter['withDeleted'] = withDeleted; + } + + if (withExif !== undefined) { + localVarQueryParameter['withExif'] = withExif; + } + + + setSearchParams(localVarUrlObj, localVarQueryParameter); let headersFromBaseOptions = baseOptions && baseOptions.headers ? baseOptions.headers : {}; localVarRequestOptions.headers = {...localVarHeaderParameter, ...headersFromBaseOptions, ...options.headers}; @@ -14649,21 +15153,76 @@ export const SearchApiFp = function(configuration?: Configuration) { * * @param {boolean} [clip] @deprecated * @param {boolean} [motion] + * @param {number} [page] * @param {string} [q] * @param {string} [query] * @param {boolean} [recent] + * @param {number} [size] * @param {boolean} [smart] * @param {SearchTypeEnum} [type] * @param {boolean} [withArchived] * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ - async search(clip?: boolean, motion?: boolean, q?: string, query?: string, recent?: boolean, smart?: boolean, type?: SearchTypeEnum, withArchived?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { - const localVarAxiosArgs = await localVarAxiosParamCreator.search(clip, motion, q, query, recent, smart, type, withArchived, options); + async search(clip?: boolean, motion?: boolean, page?: number, q?: string, query?: string, recent?: boolean, size?: number, smart?: boolean, type?: SearchTypeEnum, withArchived?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.search(clip, motion, page, q, query, recent, size, smart, type, withArchived, options); const index = configuration?.serverIndex ?? 0; const operationBasePath = operationServerMap['SearchApi.search']?.[index]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); }, + /** + * + * @param {string} [checksum] + * @param {string} [city] + * @param {string} [country] + * @param {string} [createdAfter] + * @param {string} [createdBefore] + * @param {string} [deviceAssetId] + * @param {string} [deviceId] + * @param {string} [encodedVideoPath] + * @param {string} [id] + * @param {boolean} [isArchived] + * @param {boolean} [isEncoded] + * @param {boolean} [isExternal] + * @param {boolean} [isFavorite] + * @param {boolean} [isMotion] + * @param {boolean} [isOffline] + * @param {boolean} [isReadOnly] + * @param {boolean} [isVisible] + * @param {string} [lensModel] + * @param {string} [libraryId] + * @param {string} [make] + * @param {string} [model] + * @param {AssetOrder} [order] + * @param {string} [originalFileName] + * @param {string} [originalPath] + * @param {number} [page] + * @param {string} [resizePath] + * @param {number} [size] + * @param {string} [state] + * @param {string} [takenAfter] + * @param {string} [takenBefore] + * @param {string} [trashedAfter] + * @param {string} [trashedBefore] + * @param {AssetTypeEnum} [type] + * @param {string} [updatedAfter] + * @param {string} [updatedBefore] + * @param {string} [webpPath] + * @param {boolean} [withArchived] + * @param {boolean} [withDeleted] + * @param {boolean} [withExif] + * @param {boolean} [withPeople] + * @param {boolean} [withStacked] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async searchMetadata(checksum?: string, city?: string, country?: string, createdAfter?: string, createdBefore?: string, deviceAssetId?: string, deviceId?: string, encodedVideoPath?: string, id?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, lensModel?: string, libraryId?: string, make?: string, model?: string, order?: AssetOrder, originalFileName?: string, originalPath?: string, page?: number, resizePath?: string, size?: number, state?: string, takenAfter?: string, takenBefore?: string, trashedAfter?: string, trashedBefore?: string, type?: AssetTypeEnum, updatedAfter?: string, updatedBefore?: string, webpPath?: string, withArchived?: boolean, withDeleted?: boolean, withExif?: boolean, withPeople?: boolean, withStacked?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.searchMetadata(checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked, options); + const index = configuration?.serverIndex ?? 0; + const operationBasePath = operationServerMap['SearchApi.searchMetadata']?.[index]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); + }, /** * * @param {string} name @@ -14677,6 +15236,48 @@ export const SearchApiFp = function(configuration?: Configuration) { const operationBasePath = operationServerMap['SearchApi.searchPerson']?.[index]?.url; return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); }, + /** + * + * @param {string} query + * @param {string} [city] + * @param {string} [country] + * @param {string} [createdAfter] + * @param {string} [createdBefore] + * @param {string} [deviceId] + * @param {boolean} [isArchived] + * @param {boolean} [isEncoded] + * @param {boolean} [isExternal] + * @param {boolean} [isFavorite] + * @param {boolean} [isMotion] + * @param {boolean} [isOffline] + * @param {boolean} [isReadOnly] + * @param {boolean} [isVisible] + * @param {string} [lensModel] + * @param {string} [libraryId] + * @param {string} [make] + * @param {string} [model] + * @param {number} [page] + * @param {number} [size] + * @param {string} [state] + * @param {string} [takenAfter] + * @param {string} [takenBefore] + * @param {string} [trashedAfter] + * @param {string} [trashedBefore] + * @param {AssetTypeEnum} [type] + * @param {string} [updatedAfter] + * @param {string} [updatedBefore] + * @param {boolean} [withArchived] + * @param {boolean} [withDeleted] + * @param {boolean} [withExif] + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + async searchSmart(query: string, city?: string, country?: string, createdAfter?: string, createdBefore?: string, deviceId?: string, isArchived?: boolean, isEncoded?: boolean, isExternal?: boolean, isFavorite?: boolean, isMotion?: boolean, isOffline?: boolean, isReadOnly?: boolean, isVisible?: boolean, lensModel?: string, libraryId?: string, make?: string, model?: string, page?: number, size?: number, state?: string, takenAfter?: string, takenBefore?: string, trashedAfter?: string, trashedBefore?: string, type?: AssetTypeEnum, updatedAfter?: string, updatedBefore?: string, withArchived?: boolean, withDeleted?: boolean, withExif?: boolean, options?: RawAxiosRequestConfig): Promise<(axios?: AxiosInstance, basePath?: string) => AxiosPromise> { + const localVarAxiosArgs = await localVarAxiosParamCreator.searchSmart(query, city, country, createdAfter, createdBefore, deviceId, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, page, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif, options); + const index = configuration?.serverIndex ?? 0; + const operationBasePath = operationServerMap['SearchApi.searchSmart']?.[index]?.url; + return (axios, basePath) => createRequestFunction(localVarAxiosArgs, globalAxios, BASE_PATH, configuration)(axios, operationBasePath || basePath); + }, } }; @@ -14699,10 +15300,20 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat * * @param {SearchApiSearchRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} */ search(requestParameters: SearchApiSearchRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { - return localVarFp.search(requestParameters.clip, requestParameters.motion, requestParameters.q, requestParameters.query, requestParameters.recent, requestParameters.smart, requestParameters.type, requestParameters.withArchived, options).then((request) => request(axios, basePath)); + return localVarFp.search(requestParameters.clip, requestParameters.motion, requestParameters.page, requestParameters.q, requestParameters.query, requestParameters.recent, requestParameters.size, requestParameters.smart, requestParameters.type, requestParameters.withArchived, options).then((request) => request(axios, basePath)); + }, + /** + * + * @param {SearchApiSearchMetadataRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + searchMetadata(requestParameters: SearchApiSearchMetadataRequest = {}, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.searchMetadata(requestParameters.checksum, requestParameters.city, requestParameters.country, requestParameters.createdAfter, requestParameters.createdBefore, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.encodedVideoPath, requestParameters.id, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.lensModel, requestParameters.libraryId, requestParameters.make, requestParameters.model, requestParameters.order, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.page, requestParameters.resizePath, requestParameters.size, requestParameters.state, requestParameters.takenAfter, requestParameters.takenBefore, requestParameters.trashedAfter, requestParameters.trashedBefore, requestParameters.type, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.webpPath, requestParameters.withArchived, requestParameters.withDeleted, requestParameters.withExif, requestParameters.withPeople, requestParameters.withStacked, options).then((request) => request(axios, basePath)); }, /** * @@ -14713,6 +15324,15 @@ export const SearchApiFactory = function (configuration?: Configuration, basePat searchPerson(requestParameters: SearchApiSearchPersonRequest, options?: RawAxiosRequestConfig): AxiosPromise> { return localVarFp.searchPerson(requestParameters.name, requestParameters.withHidden, options).then((request) => request(axios, basePath)); }, + /** + * + * @param {SearchApiSearchSmartRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + */ + searchSmart(requestParameters: SearchApiSearchSmartRequest, options?: RawAxiosRequestConfig): AxiosPromise { + return localVarFp.searchSmart(requestParameters.query, requestParameters.city, requestParameters.country, requestParameters.createdAfter, requestParameters.createdBefore, requestParameters.deviceId, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.lensModel, requestParameters.libraryId, requestParameters.make, requestParameters.model, requestParameters.page, requestParameters.size, requestParameters.state, requestParameters.takenAfter, requestParameters.takenBefore, requestParameters.trashedAfter, requestParameters.trashedBefore, requestParameters.type, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.withArchived, requestParameters.withDeleted, requestParameters.withExif, options).then((request) => request(axios, basePath)); + }, }; }; @@ -14736,6 +15356,13 @@ export interface SearchApiSearchRequest { */ readonly motion?: boolean + /** + * + * @type {number} + * @memberof SearchApiSearch + */ + readonly page?: number + /** * * @type {string} @@ -14757,6 +15384,13 @@ export interface SearchApiSearchRequest { */ readonly recent?: boolean + /** + * + * @type {number} + * @memberof SearchApiSearch + */ + readonly size?: number + /** * * @type {boolean} @@ -14779,6 +15413,300 @@ export interface SearchApiSearchRequest { readonly withArchived?: boolean } +/** + * Request parameters for searchMetadata operation in SearchApi. + * @export + * @interface SearchApiSearchMetadataRequest + */ +export interface SearchApiSearchMetadataRequest { + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly checksum?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly city?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly country?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly createdAfter?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly createdBefore?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly deviceAssetId?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly deviceId?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly encodedVideoPath?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly id?: string + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly isArchived?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly isEncoded?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly isExternal?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly isFavorite?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly isMotion?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly isOffline?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly isReadOnly?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly isVisible?: boolean + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly lensModel?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly libraryId?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly make?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly model?: string + + /** + * + * @type {AssetOrder} + * @memberof SearchApiSearchMetadata + */ + readonly order?: AssetOrder + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly originalFileName?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly originalPath?: string + + /** + * + * @type {number} + * @memberof SearchApiSearchMetadata + */ + readonly page?: number + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly resizePath?: string + + /** + * + * @type {number} + * @memberof SearchApiSearchMetadata + */ + readonly size?: number + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly state?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly takenAfter?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly takenBefore?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly trashedAfter?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly trashedBefore?: string + + /** + * + * @type {AssetTypeEnum} + * @memberof SearchApiSearchMetadata + */ + readonly type?: AssetTypeEnum + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly updatedAfter?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly updatedBefore?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchMetadata + */ + readonly webpPath?: string + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly withArchived?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly withDeleted?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly withExif?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly withPeople?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchMetadata + */ + readonly withStacked?: boolean +} + /** * Request parameters for searchPerson operation in SearchApi. * @export @@ -14800,6 +15728,230 @@ export interface SearchApiSearchPersonRequest { readonly withHidden?: boolean } +/** + * Request parameters for searchSmart operation in SearchApi. + * @export + * @interface SearchApiSearchSmartRequest + */ +export interface SearchApiSearchSmartRequest { + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly query: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly city?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly country?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly createdAfter?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly createdBefore?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly deviceId?: string + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly isArchived?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly isEncoded?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly isExternal?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly isFavorite?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly isMotion?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly isOffline?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly isReadOnly?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly isVisible?: boolean + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly lensModel?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly libraryId?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly make?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly model?: string + + /** + * + * @type {number} + * @memberof SearchApiSearchSmart + */ + readonly page?: number + + /** + * + * @type {number} + * @memberof SearchApiSearchSmart + */ + readonly size?: number + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly state?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly takenAfter?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly takenBefore?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly trashedAfter?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly trashedBefore?: string + + /** + * + * @type {AssetTypeEnum} + * @memberof SearchApiSearchSmart + */ + readonly type?: AssetTypeEnum + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly updatedAfter?: string + + /** + * + * @type {string} + * @memberof SearchApiSearchSmart + */ + readonly updatedBefore?: string + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly withArchived?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly withDeleted?: boolean + + /** + * + * @type {boolean} + * @memberof SearchApiSearchSmart + */ + readonly withExif?: boolean +} + /** * SearchApi - object-oriented interface * @export @@ -14821,11 +15973,23 @@ export class SearchApi extends BaseAPI { * * @param {SearchApiSearchRequest} requestParameters Request parameters. * @param {*} [options] Override http request option. + * @deprecated * @throws {RequiredError} * @memberof SearchApi */ public search(requestParameters: SearchApiSearchRequest = {}, options?: RawAxiosRequestConfig) { - return SearchApiFp(this.configuration).search(requestParameters.clip, requestParameters.motion, requestParameters.q, requestParameters.query, requestParameters.recent, requestParameters.smart, requestParameters.type, requestParameters.withArchived, options).then((request) => request(this.axios, this.basePath)); + return SearchApiFp(this.configuration).search(requestParameters.clip, requestParameters.motion, requestParameters.page, requestParameters.q, requestParameters.query, requestParameters.recent, requestParameters.size, requestParameters.smart, requestParameters.type, requestParameters.withArchived, options).then((request) => request(this.axios, this.basePath)); + } + + /** + * + * @param {SearchApiSearchMetadataRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SearchApi + */ + public searchMetadata(requestParameters: SearchApiSearchMetadataRequest = {}, options?: RawAxiosRequestConfig) { + return SearchApiFp(this.configuration).searchMetadata(requestParameters.checksum, requestParameters.city, requestParameters.country, requestParameters.createdAfter, requestParameters.createdBefore, requestParameters.deviceAssetId, requestParameters.deviceId, requestParameters.encodedVideoPath, requestParameters.id, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.lensModel, requestParameters.libraryId, requestParameters.make, requestParameters.model, requestParameters.order, requestParameters.originalFileName, requestParameters.originalPath, requestParameters.page, requestParameters.resizePath, requestParameters.size, requestParameters.state, requestParameters.takenAfter, requestParameters.takenBefore, requestParameters.trashedAfter, requestParameters.trashedBefore, requestParameters.type, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.webpPath, requestParameters.withArchived, requestParameters.withDeleted, requestParameters.withExif, requestParameters.withPeople, requestParameters.withStacked, options).then((request) => request(this.axios, this.basePath)); } /** @@ -14838,6 +16002,17 @@ export class SearchApi extends BaseAPI { public searchPerson(requestParameters: SearchApiSearchPersonRequest, options?: RawAxiosRequestConfig) { return SearchApiFp(this.configuration).searchPerson(requestParameters.name, requestParameters.withHidden, options).then((request) => request(this.axios, this.basePath)); } + + /** + * + * @param {SearchApiSearchSmartRequest} requestParameters Request parameters. + * @param {*} [options] Override http request option. + * @throws {RequiredError} + * @memberof SearchApi + */ + public searchSmart(requestParameters: SearchApiSearchSmartRequest, options?: RawAxiosRequestConfig) { + return SearchApiFp(this.configuration).searchSmart(requestParameters.query, requestParameters.city, requestParameters.country, requestParameters.createdAfter, requestParameters.createdBefore, requestParameters.deviceId, requestParameters.isArchived, requestParameters.isEncoded, requestParameters.isExternal, requestParameters.isFavorite, requestParameters.isMotion, requestParameters.isOffline, requestParameters.isReadOnly, requestParameters.isVisible, requestParameters.lensModel, requestParameters.libraryId, requestParameters.make, requestParameters.model, requestParameters.page, requestParameters.size, requestParameters.state, requestParameters.takenAfter, requestParameters.takenBefore, requestParameters.trashedAfter, requestParameters.trashedBefore, requestParameters.type, requestParameters.updatedAfter, requestParameters.updatedBefore, requestParameters.withArchived, requestParameters.withDeleted, requestParameters.withExif, options).then((request) => request(this.axios, this.basePath)); + } } /** diff --git a/open-api/typescript-sdk/fetch-client.ts b/open-api/typescript-sdk/fetch-client.ts index cb2c42f5dd871..afdb705c60660 100644 --- a/open-api/typescript-sdk/fetch-client.ts +++ b/open-api/typescript-sdk/fetch-client.ts @@ -588,6 +588,7 @@ export type SearchAssetResponseDto = { count: number; facets: SearchFacetResponseDto[]; items: AssetResponseDto[]; + nextPage: string | null; total: number; }; export type SearchResponseDto = { @@ -1461,7 +1462,7 @@ export function updateAsset({ id, updateAssetDto }: { body: updateAssetDto }))); } -export function searchAssets({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withDeleted, withExif, withPeople, withStacked }: { +export function searchAssets({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked }: { checksum?: string; city?: string; country?: string; @@ -1498,6 +1499,7 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef updatedAfter?: string; updatedBefore?: string; webpPath?: string; + withArchived?: boolean; withDeleted?: boolean; withExif?: boolean; withPeople?: boolean; @@ -1543,6 +1545,7 @@ export function searchAssets({ checksum, city, country, createdAfter, createdBef updatedAfter, updatedBefore, webpPath, + withArchived, withDeleted, withExif, withPeople, @@ -2047,12 +2050,14 @@ export function getPersonThumbnail({ id }: { ...opts })); } -export function search({ clip, motion, q, query, recent, smart, $type, withArchived }: { +export function search({ clip, motion, page, q, query, recent, size, smart, $type, withArchived }: { clip?: boolean; motion?: boolean; + page?: number; q?: string; query?: string; recent?: boolean; + size?: number; smart?: boolean; $type?: "IMAGE" | "VIDEO" | "AUDIO" | "OTHER"; withArchived?: boolean; @@ -2063,9 +2068,11 @@ export function search({ clip, motion, q, query, recent, smart, $type, withArchi }>(`/search${QS.query(QS.explode({ clip, motion, + page, q, query, recent, + size, smart, "type": $type, withArchived @@ -2081,6 +2088,98 @@ export function getExploreData(opts?: Oazapfts.RequestOpts) { ...opts })); } +export function searchMetadata({ checksum, city, country, createdAfter, createdBefore, deviceAssetId, deviceId, encodedVideoPath, id, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, order, originalFileName, originalPath, page, resizePath, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, webpPath, withArchived, withDeleted, withExif, withPeople, withStacked }: { + checksum?: string; + city?: string; + country?: string; + createdAfter?: string; + createdBefore?: string; + deviceAssetId?: string; + deviceId?: string; + encodedVideoPath?: string; + id?: string; + isArchived?: boolean; + isEncoded?: boolean; + isExternal?: boolean; + isFavorite?: boolean; + isMotion?: boolean; + isOffline?: boolean; + isReadOnly?: boolean; + isVisible?: boolean; + lensModel?: string; + libraryId?: string; + make?: string; + model?: string; + order?: AssetOrder; + originalFileName?: string; + originalPath?: string; + page?: number; + resizePath?: string; + size?: number; + state?: string; + takenAfter?: string; + takenBefore?: string; + trashedAfter?: string; + trashedBefore?: string; + $type?: AssetTypeEnum; + updatedAfter?: string; + updatedBefore?: string; + webpPath?: string; + withArchived?: boolean; + withDeleted?: boolean; + withExif?: boolean; + withPeople?: boolean; + withStacked?: boolean; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: SearchResponseDto; + }>(`/search/metadata${QS.query(QS.explode({ + checksum, + city, + country, + createdAfter, + createdBefore, + deviceAssetId, + deviceId, + encodedVideoPath, + id, + isArchived, + isEncoded, + isExternal, + isFavorite, + isMotion, + isOffline, + isReadOnly, + isVisible, + lensModel, + libraryId, + make, + model, + order, + originalFileName, + originalPath, + page, + resizePath, + size, + state, + takenAfter, + takenBefore, + trashedAfter, + trashedBefore, + "type": $type, + updatedAfter, + updatedBefore, + webpPath, + withArchived, + withDeleted, + withExif, + withPeople, + withStacked + }))}`, { + ...opts + })); +} export function searchPerson({ name, withHidden }: { name: string; withHidden?: boolean; @@ -2095,6 +2194,78 @@ export function searchPerson({ name, withHidden }: { ...opts })); } +export function searchSmart({ city, country, createdAfter, createdBefore, deviceId, isArchived, isEncoded, isExternal, isFavorite, isMotion, isOffline, isReadOnly, isVisible, lensModel, libraryId, make, model, page, query, size, state, takenAfter, takenBefore, trashedAfter, trashedBefore, $type, updatedAfter, updatedBefore, withArchived, withDeleted, withExif }: { + city?: string; + country?: string; + createdAfter?: string; + createdBefore?: string; + deviceId?: string; + isArchived?: boolean; + isEncoded?: boolean; + isExternal?: boolean; + isFavorite?: boolean; + isMotion?: boolean; + isOffline?: boolean; + isReadOnly?: boolean; + isVisible?: boolean; + lensModel?: string; + libraryId?: string; + make?: string; + model?: string; + page?: number; + query: string; + size?: number; + state?: string; + takenAfter?: string; + takenBefore?: string; + trashedAfter?: string; + trashedBefore?: string; + $type?: AssetTypeEnum; + updatedAfter?: string; + updatedBefore?: string; + withArchived?: boolean; + withDeleted?: boolean; + withExif?: boolean; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchJson<{ + status: 200; + data: SearchResponseDto; + }>(`/search/smart${QS.query(QS.explode({ + city, + country, + createdAfter, + createdBefore, + deviceId, + isArchived, + isEncoded, + isExternal, + isFavorite, + isMotion, + isOffline, + isReadOnly, + isVisible, + lensModel, + libraryId, + make, + model, + page, + query, + size, + state, + takenAfter, + takenBefore, + trashedAfter, + trashedBefore, + "type": $type, + updatedAfter, + updatedBefore, + withArchived, + withDeleted, + withExif + }))}`, { + ...opts + })); +} export function getServerInfo(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; diff --git a/server/e2e/api/specs/asset.e2e-spec.ts b/server/e2e/api/specs/asset.e2e-spec.ts index 6f6bc889037e8..5993a7040033d 100644 --- a/server/e2e/api/specs/asset.e2e-spec.ts +++ b/server/e2e/api/specs/asset.e2e-spec.ts @@ -169,7 +169,11 @@ describe(`${AssetController.name} (e2e)`, () => { { should: 'should reject size as a string', query: { size: 'abc' }, - expected: ['size must not be less than 1', 'size must be an integer number'], + expected: [ + 'size must not be greater than 1000', + 'size must not be less than 1', + 'size must be an integer number', + ], }, { should: 'should reject an invalid size', @@ -478,7 +482,7 @@ describe(`${AssetController.name} (e2e)`, () => { }), }, { - should: 'sohuld search by make', + should: 'should search by make', deferred: () => ({ query: { make: 'Cannon' }, assets: [asset3], diff --git a/server/e2e/api/specs/search.e2e-spec.ts b/server/e2e/api/specs/search.e2e-spec.ts index 8d17ee9304d29..74988396d73fd 100644 --- a/server/e2e/api/specs/search.e2e-spec.ts +++ b/server/e2e/api/specs/search.e2e-spec.ts @@ -1,7 +1,7 @@ import { AssetResponseDto, IAssetRepository, - ISmartInfoRepository, + ISearchRepository, LibraryResponseDto, LoginResponseDto, mapAsset, @@ -20,14 +20,14 @@ describe(`${SearchController.name}`, () => { let accessToken: string; let libraries: LibraryResponseDto[]; let assetRepository: IAssetRepository; - let smartInfoRepository: ISmartInfoRepository; + let smartInfoRepository: ISearchRepository; let asset1: AssetResponseDto; beforeAll(async () => { app = await testApp.create(); server = app.getHttpServer(); assetRepository = app.get(IAssetRepository); - smartInfoRepository = app.get(ISmartInfoRepository); + smartInfoRepository = app.get(ISearchRepository); }); afterAll(async () => { diff --git a/server/src/domain/asset/asset.service.ts b/server/src/domain/asset/asset.service.ts index e73858c31155b..d46678b8b4390 100644 --- a/server/src/domain/asset/asset.service.ts +++ b/server/src/domain/asset/asset.service.ts @@ -31,8 +31,6 @@ import { AssetBulkUpdateDto, AssetJobName, AssetJobsDto, - AssetOrder, - AssetSearchDto, AssetStatsDto, MapMarkerDto, MemoryLaneDto, @@ -92,34 +90,6 @@ export class AssetService { this.configCore = SystemConfigCore.create(configRepository); } - search(auth: AuthDto, dto: AssetSearchDto) { - let checksum: Buffer | undefined; - - if (dto.checksum) { - const encoding = dto.checksum.length === 28 ? 'base64' : 'hex'; - checksum = Buffer.from(dto.checksum, encoding); - } - - const enumToOrder = { [AssetOrder.ASC]: 'ASC', [AssetOrder.DESC]: 'DESC' } as const; - const order = dto.order ? enumToOrder[dto.order] : undefined; - - return this.assetRepository - .search({ - ...dto, - order, - checksum, - ownerId: auth.user.id, - }) - .then((assets) => - assets.map((asset) => - mapAsset(asset, { - stripMetadata: false, - withStack: true, - }), - ), - ); - } - canUploadFile({ auth, fieldName, file }: UploadRequest): true { this.access.requireUploadAccess(auth); diff --git a/server/src/domain/asset/dto/asset.dto.ts b/server/src/domain/asset/dto/asset.dto.ts index bd4cf93ba6849..0244ecd90eb25 100644 --- a/server/src/domain/asset/dto/asset.dto.ts +++ b/server/src/domain/asset/dto/asset.dto.ts @@ -1,20 +1,16 @@ -import { AssetType } from '@app/infra/entities'; -import { ApiProperty } from '@nestjs/swagger'; import { Type } from 'class-transformer'; import { IsBoolean, IsDateString, - IsEnum, IsInt, IsLatitude, IsLongitude, IsNotEmpty, IsPositive, IsString, - Min, ValidateIf, } from 'class-validator'; -import { Optional, QueryBoolean, QueryDate, ValidateUUID } from '../../domain.util'; +import { Optional, ValidateUUID } from '../../domain.util'; import { BulkIdsDto } from '../response-dto'; export class DeviceIdDto { @@ -32,152 +28,6 @@ const hasGPS = (o: { latitude: undefined; longitude: undefined }) => o.latitude !== undefined || o.longitude !== undefined; const ValidateGPS = () => ValidateIf(hasGPS); -export class AssetSearchDto { - @ValidateUUID({ optional: true }) - id?: string; - - @ValidateUUID({ optional: true }) - libraryId?: string; - - @IsString() - @Optional() - deviceAssetId?: string; - - @IsString() - @Optional() - deviceId?: string; - - @IsEnum(AssetType) - @Optional() - @ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType }) - type?: AssetType; - - @IsString() - @Optional() - checksum?: string; - - @QueryBoolean({ optional: true }) - isArchived?: boolean; - - @QueryBoolean({ optional: true }) - isEncoded?: boolean; - - @QueryBoolean({ optional: true }) - isExternal?: boolean; - - @QueryBoolean({ optional: true }) - isFavorite?: boolean; - - @QueryBoolean({ optional: true }) - isMotion?: boolean; - - @QueryBoolean({ optional: true }) - isOffline?: boolean; - - @QueryBoolean({ optional: true }) - isReadOnly?: boolean; - - @QueryBoolean({ optional: true }) - isVisible?: boolean; - - @QueryBoolean({ optional: true }) - withDeleted?: boolean; - - @QueryBoolean({ optional: true }) - withStacked?: boolean; - - @QueryBoolean({ optional: true }) - withExif?: boolean; - - @QueryBoolean({ optional: true }) - withPeople?: boolean; - - @QueryDate({ optional: true }) - createdBefore?: Date; - - @QueryDate({ optional: true }) - createdAfter?: Date; - - @QueryDate({ optional: true }) - updatedBefore?: Date; - - @QueryDate({ optional: true }) - updatedAfter?: Date; - - @QueryDate({ optional: true }) - trashedBefore?: Date; - - @QueryDate({ optional: true }) - trashedAfter?: Date; - - @QueryDate({ optional: true }) - takenBefore?: Date; - - @QueryDate({ optional: true }) - takenAfter?: Date; - - @IsString() - @Optional() - originalFileName?: string; - - @IsString() - @Optional() - originalPath?: string; - - @IsString() - @Optional() - resizePath?: string; - - @IsString() - @Optional() - webpPath?: string; - - @IsString() - @Optional() - encodedVideoPath?: string; - - @IsString() - @Optional() - city?: string; - - @IsString() - @Optional() - state?: string; - - @IsString() - @Optional() - country?: string; - - @IsString() - @Optional() - make?: string; - - @IsString() - @Optional() - model?: string; - - @IsString() - @Optional() - lensModel?: string; - - @IsEnum(AssetOrder) - @Optional() - @ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder }) - order?: AssetOrder; - - @IsInt() - @Min(1) - @Type(() => Number) - @Optional() - page?: number; - - @IsInt() - @Min(1) - @Type(() => Number) - @Optional() - size?: number; -} - export class AssetBulkUpdateDto extends BulkIdsDto { @Optional() @IsBoolean() diff --git a/server/src/domain/domain.util.ts b/server/src/domain/domain.util.ts index 43efbce255453..5fdfd7ec5ba9e 100644 --- a/server/src/domain/domain.util.ts +++ b/server/src/domain/domain.util.ts @@ -137,6 +137,17 @@ export interface PaginationOptions { skip?: number; } +export enum PaginationMode { + LIMIT_OFFSET = 'limit-offset', + SKIP_TAKE = 'skip-take', +} + +export interface PaginatedBuilderOptions { + take: number; + skip?: number; + mode?: PaginationMode; +} + export interface PaginationResult { items: T[]; hasNextPage: boolean; diff --git a/server/src/domain/person/person.service.spec.ts b/server/src/domain/person/person.service.spec.ts index 9d55abc8e655b..5da8666016080 100644 --- a/server/src/domain/person/person.service.spec.ts +++ b/server/src/domain/person/person.service.spec.ts @@ -13,7 +13,7 @@ import { newMediaRepositoryMock, newMoveRepositoryMock, newPersonRepositoryMock, - newSmartInfoRepositoryMock, + newSearchRepositoryMock, newStorageRepositoryMock, newSystemConfigRepositoryMock, personStub, @@ -31,7 +31,7 @@ import { IMediaRepository, IMoveRepository, IPersonRepository, - ISmartInfoRepository, + ISearchRepository, IStorageRepository, ISystemConfigRepository, WithoutProperty, @@ -76,7 +76,7 @@ describe(PersonService.name, () => { let moveMock: jest.Mocked; let personMock: jest.Mocked; let storageMock: jest.Mocked; - let smartInfoMock: jest.Mocked; + let searchMock: jest.Mocked; let cryptoMock: jest.Mocked; let sut: PersonService; @@ -90,7 +90,7 @@ describe(PersonService.name, () => { mediaMock = newMediaRepositoryMock(); personMock = newPersonRepositoryMock(); storageMock = newStorageRepositoryMock(); - smartInfoMock = newSmartInfoRepositoryMock(); + searchMock = newSearchRepositoryMock(); cryptoMock = newCryptoRepositoryMock(); sut = new PersonService( accessMock, @@ -102,7 +102,7 @@ describe(PersonService.name, () => { configMock, storageMock, jobMock, - smartInfoMock, + searchMock, cryptoMock, ); @@ -752,7 +752,7 @@ describe(PersonService.name, () => { it('should create a face with no person and queue recognition job', async () => { personMock.createFaces.mockResolvedValue([faceStub.face1.id]); machineLearningMock.detectFaces.mockResolvedValue([detectFaceMock]); - smartInfoMock.searchFaces.mockResolvedValue([{ face: faceStub.face1, distance: 0.7 }]); + searchMock.searchFaces.mockResolvedValue([{ face: faceStub.face1, distance: 0.7 }]); assetMock.getByIds.mockResolvedValue([assetStub.image]); const face = { assetId: 'asset-id', @@ -823,7 +823,7 @@ describe(PersonService.name, () => { configMock.load.mockResolvedValue([ { key: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES, value: 1 }, ]); - smartInfoMock.searchFaces.mockResolvedValue(faces); + searchMock.searchFaces.mockResolvedValue(faces); personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1); personMock.create.mockResolvedValue(faceStub.primaryFace1.person); @@ -850,7 +850,7 @@ describe(PersonService.name, () => { configMock.load.mockResolvedValue([ { key: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES, value: 1 }, ]); - smartInfoMock.searchFaces.mockResolvedValue(faces); + searchMock.searchFaces.mockResolvedValue(faces); personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1); personMock.create.mockResolvedValue(personStub.withName); @@ -869,14 +869,14 @@ describe(PersonService.name, () => { it('should not queue face with no matches', async () => { const faces = [{ face: faceStub.noPerson1, distance: 0 }] as FaceSearchResult[]; - smartInfoMock.searchFaces.mockResolvedValue(faces); + searchMock.searchFaces.mockResolvedValue(faces); personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1); personMock.create.mockResolvedValue(personStub.withName); await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id }); expect(jobMock.queue).not.toHaveBeenCalled(); - expect(smartInfoMock.searchFaces).toHaveBeenCalledTimes(1); + expect(searchMock.searchFaces).toHaveBeenCalledTimes(1); expect(personMock.create).not.toHaveBeenCalled(); expect(personMock.reassignFaces).not.toHaveBeenCalled(); }); @@ -890,7 +890,7 @@ describe(PersonService.name, () => { configMock.load.mockResolvedValue([ { key: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES, value: 3 }, ]); - smartInfoMock.searchFaces.mockResolvedValue(faces); + searchMock.searchFaces.mockResolvedValue(faces); personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1); personMock.create.mockResolvedValue(personStub.withName); @@ -900,7 +900,7 @@ describe(PersonService.name, () => { name: JobName.FACIAL_RECOGNITION, data: { id: faceStub.noPerson1.id, deferred: true }, }); - expect(smartInfoMock.searchFaces).toHaveBeenCalledTimes(1); + expect(searchMock.searchFaces).toHaveBeenCalledTimes(1); expect(personMock.create).not.toHaveBeenCalled(); expect(personMock.reassignFaces).not.toHaveBeenCalled(); }); @@ -914,14 +914,14 @@ describe(PersonService.name, () => { configMock.load.mockResolvedValue([ { key: SystemConfigKey.MACHINE_LEARNING_FACIAL_RECOGNITION_MIN_FACES, value: 3 }, ]); - smartInfoMock.searchFaces.mockResolvedValueOnce(faces).mockResolvedValueOnce([]); + searchMock.searchFaces.mockResolvedValueOnce(faces).mockResolvedValueOnce([]); personMock.getFaceByIdWithAssets.mockResolvedValue(faceStub.noPerson1); personMock.create.mockResolvedValue(personStub.withName); await sut.handleRecognizeFaces({ id: faceStub.noPerson1.id, deferred: true }); expect(jobMock.queue).not.toHaveBeenCalled(); - expect(smartInfoMock.searchFaces).toHaveBeenCalledTimes(2); + expect(searchMock.searchFaces).toHaveBeenCalledTimes(2); expect(personMock.create).not.toHaveBeenCalled(); expect(personMock.reassignFaces).not.toHaveBeenCalled(); }); diff --git a/server/src/domain/person/person.service.ts b/server/src/domain/person/person.service.ts index 63fc350002103..359084bf21c9a 100644 --- a/server/src/domain/person/person.service.ts +++ b/server/src/domain/person/person.service.ts @@ -20,7 +20,7 @@ import { IMediaRepository, IMoveRepository, IPersonRepository, - ISmartInfoRepository, + ISearchRepository, IStorageRepository, ISystemConfigRepository, JobItem, @@ -61,7 +61,7 @@ export class PersonService { @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IStorageRepository) private storageRepository: IStorageRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, - @Inject(ISmartInfoRepository) private smartInfoRepository: ISmartInfoRepository, + @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository, @Inject(ICryptoRepository) private cryptoRepository: ICryptoRepository, ) { this.access = AccessCore.create(accessRepository); @@ -285,15 +285,7 @@ export class PersonService { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => { return force - ? this.assetRepository.getAll(pagination, { - order: 'DESC', - withFaces: true, - withPeople: false, - withSmartInfo: false, - withSmartSearch: false, - withExif: false, - withStacked: false, - }) + ? this.assetRepository.getAll(pagination, { orderDirection: 'DESC', withFaces: true }) : this.assetRepository.getWithout(pagination, WithoutProperty.FACES); }); diff --git a/server/src/domain/repositories/asset.repository.ts b/server/src/domain/repositories/asset.repository.ts index 00a74f8293ad6..4ff58cdb1be04 100644 --- a/server/src/domain/repositories/asset.repository.ts +++ b/server/src/domain/repositories/asset.repository.ts @@ -1,4 +1,4 @@ -import { SearchExploreItem } from '@app/domain'; +import { AssetSearchOptions, SearchExploreItem } from '@app/domain'; import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity } from '@app/infra/entities'; import { FindOptionsRelations, FindOptionsSelect } from 'typeorm'; import { Paginated, PaginationOptions } from '../domain.util'; @@ -11,64 +11,6 @@ export interface AssetStatsOptions { isTrashed?: boolean; } -export interface AssetSearchOptions { - id?: string; - libraryId?: string; - deviceAssetId?: string; - deviceId?: string; - ownerId?: string; - type?: AssetType; - checksum?: Buffer; - - isArchived?: boolean; - isEncoded?: boolean; - isExternal?: boolean; - isFavorite?: boolean; - isMotion?: boolean; - isOffline?: boolean; - isReadOnly?: boolean; - isVisible?: boolean; - - withDeleted?: boolean; - withStacked?: boolean; - withExif?: boolean; - withPeople?: boolean; - withSmartInfo?: boolean; - withSmartSearch?: boolean; - withFaces?: boolean; - - createdBefore?: Date; - createdAfter?: Date; - updatedBefore?: Date; - updatedAfter?: Date; - trashedBefore?: Date; - trashedAfter?: Date; - takenBefore?: Date; - takenAfter?: Date; - - originalFileName?: string; - originalPath?: string; - resizePath?: string; - webpPath?: string; - encodedVideoPath?: string; - - city?: string; - state?: string; - country?: string; - make?: string; - model?: string; - lensModel?: string; - - /** defaults to 'DESC' */ - order?: 'ASC' | 'DESC'; - - /** defaults to 1 */ - page?: number; - - /** defaults to 250 */ - size?: number; -} - export interface LivePhotoSearchOptions { ownerId: string; livePhotoCID: string; @@ -204,7 +146,6 @@ export interface IAssetRepository { getTimeBucket(timeBucket: string, options: TimeBucketOptions): Promise; upsertExif(exif: Partial): Promise; upsertJobStatus(jobStatus: Partial): Promise; - search(options: AssetSearchOptions): Promise; getAssetIdByCity(userId: string, options: AssetExploreFieldOptions): Promise>; getAssetIdByTag(userId: string, options: AssetExploreFieldOptions): Promise>; searchMetadata(query: string, userIds: string[], options: MetadataSearchOptions): Promise; diff --git a/server/src/domain/repositories/index.ts b/server/src/domain/repositories/index.ts index 48b1d7e8e289c..636abd2bea8b5 100644 --- a/server/src/domain/repositories/index.ts +++ b/server/src/domain/repositories/index.ts @@ -19,7 +19,6 @@ export * from './person.repository'; export * from './search.repository'; export * from './server-info.repository'; export * from './shared-link.repository'; -export * from './smart-info.repository'; export * from './storage.repository'; export * from './system-config.repository'; export * from './system-metadata.repository'; diff --git a/server/src/domain/repositories/search.repository.ts b/server/src/domain/repositories/search.repository.ts index 5c3497c8ef61d..4d720f98ad1a0 100644 --- a/server/src/domain/repositories/search.repository.ts +++ b/server/src/domain/repositories/search.repository.ts @@ -1,4 +1,7 @@ -import { AssetType } from '@app/infra/entities'; +import { AssetEntity, AssetFaceEntity, AssetType, SmartInfoEntity } from '@app/infra/entities'; +import { Paginated } from '../domain.util'; + +export const ISearchRepository = 'ISearchRepository'; export enum SearchStrategy { SMART = 'SMART', @@ -54,3 +57,122 @@ export interface SearchExploreItem { fieldName: string; items: SearchExploreItemSet; } + +export type Embedding = number[]; + +export interface SearchAssetIDOptions { + checksum?: Buffer; + deviceAssetId?: string; + id?: string; +} + +export interface SearchUserIDOptions { + deviceId?: string; + libraryId?: string; + ownerId?: string; +} + +export type SearchIDOptions = SearchAssetIDOptions & SearchUserIDOptions; + +export interface SearchStatusOptions { + isArchived?: boolean; + isEncoded?: boolean; + isExternal?: boolean; + isFavorite?: boolean; + isMotion?: boolean; + isOffline?: boolean; + isReadOnly?: boolean; + isVisible?: boolean; + type?: AssetType; + withArchived?: boolean; + withDeleted?: boolean; +} + +export interface SearchOneToOneRelationOptions { + withExif?: boolean; + withSmartInfo?: boolean; +} + +export interface SearchRelationOptions extends SearchOneToOneRelationOptions { + withFaces?: boolean; + withPeople?: boolean; + withStacked?: boolean; +} + +export interface SearchDateOptions { + createdBefore?: Date; + createdAfter?: Date; + takenBefore?: Date; + takenAfter?: Date; + trashedBefore?: Date; + trashedAfter?: Date; + updatedBefore?: Date; + updatedAfter?: Date; +} + +export interface SearchPathOptions { + encodedVideoPath?: string; + originalFileName?: string; + originalPath?: string; + resizePath?: string; + webpPath?: string; +} + +export interface SearchExifOptions { + city?: string; + country?: string; + lensModel?: string; + make?: string; + model?: string; + state?: string; +} + +export interface SearchEmbeddingOptions { + embedding: Embedding; + userIds: string[]; +} + +export interface SearchOrderOptions { + orderDirection?: 'ASC' | 'DESC'; +} + +export interface SearchPaginationOptions { + page: number; + size: number; +} + +export type AssetSearchOptions = SearchDateOptions & + SearchIDOptions & + SearchExifOptions & + SearchOrderOptions & + SearchPathOptions & + SearchRelationOptions & + SearchStatusOptions; + +export type AssetSearchBuilderOptions = Omit; + +export type SmartSearchOptions = SearchDateOptions & + SearchEmbeddingOptions & + SearchExifOptions & + SearchOneToOneRelationOptions & + SearchStatusOptions & + SearchUserIDOptions; + +export interface FaceEmbeddingSearch extends SearchEmbeddingOptions { + hasPerson?: boolean; + numResults: number; + maxDistance?: number; +} + +export interface FaceSearchResult { + distance: number; + face: AssetFaceEntity; +} + +export interface ISearchRepository { + init(modelName: string): Promise; + searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions): Paginated; + searchSmart(pagination: SearchPaginationOptions, options: SmartSearchOptions): Paginated; + searchFaces(search: FaceEmbeddingSearch): Promise; + upsert(smartInfo: Partial, embedding?: Embedding): Promise; +} diff --git a/server/src/domain/repositories/smart-info.repository.ts b/server/src/domain/repositories/smart-info.repository.ts deleted file mode 100644 index acb907bc8f20b..0000000000000 --- a/server/src/domain/repositories/smart-info.repository.ts +++ /dev/null @@ -1,29 +0,0 @@ -import { AssetEntity, AssetFaceEntity, SmartInfoEntity } from '@app/infra/entities'; - -export const ISmartInfoRepository = 'ISmartInfoRepository'; - -export type Embedding = number[]; - -export interface EmbeddingSearch { - userIds: string[]; - embedding: Embedding; - numResults: number; - withArchived?: boolean; -} - -export interface FaceEmbeddingSearch extends EmbeddingSearch { - maxDistance?: number; - hasPerson?: boolean; -} - -export interface FaceSearchResult { - face: AssetFaceEntity; - distance: number; -} - -export interface ISmartInfoRepository { - init(modelName: string): Promise; - searchCLIP(search: EmbeddingSearch): Promise; - searchFaces(search: FaceEmbeddingSearch): Promise; - upsert(smartInfo: Partial, embedding?: Embedding): Promise; -} diff --git a/server/src/domain/search/dto/search.dto.ts b/server/src/domain/search/dto/search.dto.ts index 3ddcb3a32c683..a4e0396688c5c 100644 --- a/server/src/domain/search/dto/search.dto.ts +++ b/server/src/domain/search/dto/search.dto.ts @@ -1,8 +1,184 @@ +import { AssetOrder } from '@app/domain/asset/dto/asset.dto'; import { AssetType } from '@app/infra/entities'; -import { Transform } from 'class-transformer'; -import { IsBoolean, IsEnum, IsNotEmpty, IsString } from 'class-validator'; -import { Optional, toBoolean } from '../../domain.util'; +import { ApiProperty } from '@nestjs/swagger'; +import { Transform, Type } from 'class-transformer'; +import { IsBoolean, IsEnum, IsInt, IsNotEmpty, IsString, Max, Min } from 'class-validator'; +import { Optional, QueryBoolean, QueryDate, ValidateUUID, toBoolean } from '../../domain.util'; +class BaseSearchDto { + @ValidateUUID({ optional: true }) + libraryId?: string; + + @IsString() + @IsNotEmpty() + @Optional() + deviceId?: string; + + @IsEnum(AssetType) + @Optional() + @ApiProperty({ enumName: 'AssetTypeEnum', enum: AssetType }) + type?: AssetType; + + @QueryBoolean({ optional: true }) + isArchived?: boolean; + + @QueryBoolean({ optional: true }) + withArchived?: boolean; + + @QueryBoolean({ optional: true }) + isEncoded?: boolean; + + @QueryBoolean({ optional: true }) + isExternal?: boolean; + + @QueryBoolean({ optional: true }) + isFavorite?: boolean; + + @QueryBoolean({ optional: true }) + isMotion?: boolean; + + @QueryBoolean({ optional: true }) + isOffline?: boolean; + + @QueryBoolean({ optional: true }) + isReadOnly?: boolean; + + @QueryBoolean({ optional: true }) + isVisible?: boolean; + + @QueryBoolean({ optional: true }) + withDeleted?: boolean; + + @QueryBoolean({ optional: true }) + withExif?: boolean; + + @QueryDate({ optional: true }) + createdBefore?: Date; + + @QueryDate({ optional: true }) + createdAfter?: Date; + + @QueryDate({ optional: true }) + updatedBefore?: Date; + + @QueryDate({ optional: true }) + updatedAfter?: Date; + + @QueryDate({ optional: true }) + trashedBefore?: Date; + + @QueryDate({ optional: true }) + trashedAfter?: Date; + + @QueryDate({ optional: true }) + takenBefore?: Date; + + @QueryDate({ optional: true }) + takenAfter?: Date; + + @IsString() + @IsNotEmpty() + @Optional() + city?: string; + + @IsString() + @IsNotEmpty() + @Optional() + state?: string; + + @IsString() + @IsNotEmpty() + @Optional() + country?: string; + + @IsString() + @IsNotEmpty() + @Optional() + make?: string; + + @IsString() + @IsNotEmpty() + @Optional() + model?: string; + + @IsString() + @IsNotEmpty() + @Optional() + lensModel?: string; + + @IsInt() + @Min(1) + @Type(() => Number) + @Optional() + page?: number; + + @IsInt() + @Min(1) + @Max(1000) + @Type(() => Number) + @Optional() + size?: number; +} + +export class MetadataSearchDto extends BaseSearchDto { + @ValidateUUID({ optional: true }) + id?: string; + + @IsString() + @IsNotEmpty() + @Optional() + deviceAssetId?: string; + + @IsString() + @IsNotEmpty() + @Optional() + checksum?: string; + + @QueryBoolean({ optional: true }) + withStacked?: boolean; + + @QueryBoolean({ optional: true }) + withPeople?: boolean; + + @IsString() + @IsNotEmpty() + @Optional() + originalFileName?: string; + + @IsString() + @IsNotEmpty() + @Optional() + originalPath?: string; + + @IsString() + @IsNotEmpty() + @Optional() + resizePath?: string; + + @IsString() + @IsNotEmpty() + @Optional() + webpPath?: string; + + @IsString() + @IsNotEmpty() + @Optional() + encodedVideoPath?: string; + + @IsEnum(AssetOrder) + @Optional() + @ApiProperty({ enumName: 'AssetOrder', enum: AssetOrder }) + order?: AssetOrder; +} + +export class SmartSearchDto extends BaseSearchDto { + @IsString() + @IsNotEmpty() + query!: string; +} + +// TODO: remove after implementing new search filters +/** @deprecated */ export class SearchDto { @IsString() @IsNotEmpty() @@ -43,6 +219,19 @@ export class SearchDto { @Optional() @Transform(toBoolean) withArchived?: boolean; + + @IsInt() + @Min(1) + @Type(() => Number) + @Optional() + page?: number; + + @IsInt() + @Min(1) + @Max(1000) + @Type(() => Number) + @Optional() + size?: number; } export class SearchPeopleDto { diff --git a/server/src/domain/search/response-dto/search-response.dto.ts b/server/src/domain/search/response-dto/search-response.dto.ts index 724cd5854b9b5..9dd65e7cc3b4d 100644 --- a/server/src/domain/search/response-dto/search-response.dto.ts +++ b/server/src/domain/search/response-dto/search-response.dto.ts @@ -29,6 +29,7 @@ class SearchAssetResponseDto { count!: number; items!: AssetResponseDto[]; facets!: SearchFacetResponseDto[]; + nextPage!: string | null; } export class SearchResponseDto { diff --git a/server/src/domain/search/search.service.spec.ts b/server/src/domain/search/search.service.spec.ts index 86373ce2d2f76..4f8d8c8fe0ddf 100644 --- a/server/src/domain/search/search.service.spec.ts +++ b/server/src/domain/search/search.service.spec.ts @@ -6,7 +6,7 @@ import { newMachineLearningRepositoryMock, newPartnerRepositoryMock, newPersonRepositoryMock, - newSmartInfoRepositoryMock, + newSearchRepositoryMock, newSystemConfigRepositoryMock, personStub, } from '@test'; @@ -16,7 +16,7 @@ import { IMachineLearningRepository, IPartnerRepository, IPersonRepository, - ISmartInfoRepository, + ISearchRepository, ISystemConfigRepository, } from '../repositories'; import { SearchDto } from './dto'; @@ -30,7 +30,7 @@ describe(SearchService.name, () => { let configMock: jest.Mocked; let machineMock: jest.Mocked; let personMock: jest.Mocked; - let smartInfoMock: jest.Mocked; + let searchMock: jest.Mocked; let partnerMock: jest.Mocked; beforeEach(() => { @@ -38,9 +38,9 @@ describe(SearchService.name, () => { configMock = newSystemConfigRepositoryMock(); machineMock = newMachineLearningRepositoryMock(); personMock = newPersonRepositoryMock(); - smartInfoMock = newSmartInfoRepositoryMock(); + searchMock = newSearchRepositoryMock(); partnerMock = newPartnerRepositoryMock(); - sut = new SearchService(configMock, machineMock, personMock, smartInfoMock, assetMock, partnerMock); + sut = new SearchService(configMock, machineMock, personMock, searchMock, assetMock, partnerMock); }); it('should work', () => { @@ -104,6 +104,7 @@ describe(SearchService.name, () => { count: 1, items: [mapAsset(assetStub.image)], facets: [], + nextPage: null, }, }; @@ -111,13 +112,13 @@ describe(SearchService.name, () => { expect(result).toEqual(expectedResponse); expect(assetMock.searchMetadata).toHaveBeenCalledWith(dto.q, [authStub.user1.user.id], { numResults: 250 }); - expect(smartInfoMock.searchCLIP).not.toHaveBeenCalled(); + expect(searchMock.searchSmart).not.toHaveBeenCalled(); }); it('should search archived photos if `withArchived` option is true', async () => { const dto: SearchDto = { q: 'test query', clip: true, withArchived: true }; const embedding = [1, 2, 3]; - smartInfoMock.searchCLIP.mockResolvedValueOnce([assetStub.image]); + searchMock.searchSmart.mockResolvedValueOnce({ items: [assetStub.image], hasNextPage: false }); machineMock.encodeText.mockResolvedValueOnce(embedding); partnerMock.getAll.mockResolvedValueOnce([]); const expectedResponse = { @@ -132,25 +133,28 @@ describe(SearchService.name, () => { count: 1, items: [mapAsset(assetStub.image)], facets: [], + nextPage: null, }, }; const result = await sut.search(authStub.user1, dto); expect(result).toEqual(expectedResponse); - expect(smartInfoMock.searchCLIP).toHaveBeenCalledWith({ - userIds: [authStub.user1.user.id], - embedding, - numResults: 100, - withArchived: true, - }); + expect(searchMock.searchSmart).toHaveBeenCalledWith( + { page: 1, size: 100 }, + { + userIds: [authStub.user1.user.id], + embedding, + withArchived: true, + }, + ); expect(assetMock.searchMetadata).not.toHaveBeenCalled(); }); it('should search by CLIP if `clip` option is true', async () => { const dto: SearchDto = { q: 'test query', clip: true }; const embedding = [1, 2, 3]; - smartInfoMock.searchCLIP.mockResolvedValueOnce([assetStub.image]); + searchMock.searchSmart.mockResolvedValueOnce({ items: [assetStub.image], hasNextPage: false }); machineMock.encodeText.mockResolvedValueOnce(embedding); partnerMock.getAll.mockResolvedValueOnce([]); const expectedResponse = { @@ -165,18 +169,21 @@ describe(SearchService.name, () => { count: 1, items: [mapAsset(assetStub.image)], facets: [], + nextPage: null, }, }; const result = await sut.search(authStub.user1, dto); expect(result).toEqual(expectedResponse); - expect(smartInfoMock.searchCLIP).toHaveBeenCalledWith({ - userIds: [authStub.user1.user.id], - embedding, - numResults: 100, - withArchived: false, - }); + expect(searchMock.searchSmart).toHaveBeenCalledWith( + { page: 1, size: 100 }, + { + userIds: [authStub.user1.user.id], + embedding, + withArchived: false, + }, + ); expect(assetMock.searchMetadata).not.toHaveBeenCalled(); }); diff --git a/server/src/domain/search/search.service.ts b/server/src/domain/search/search.service.ts index 932a865d04f22..1438dc3be3fa2 100644 --- a/server/src/domain/search/search.service.ts +++ b/server/src/domain/search/search.service.ts @@ -1,7 +1,7 @@ import { AssetEntity } from '@app/infra/entities'; import { ImmichLogger } from '@app/infra/logger'; import { Inject, Injectable } from '@nestjs/common'; -import { AssetResponseDto, mapAsset } from '../asset'; +import { AssetOrder, AssetResponseDto, mapAsset } from '../asset'; import { AuthDto } from '../auth'; import { PersonResponseDto } from '../person'; import { @@ -9,13 +9,13 @@ import { IMachineLearningRepository, IPartnerRepository, IPersonRepository, - ISmartInfoRepository, + ISearchRepository, ISystemConfigRepository, SearchExploreItem, SearchStrategy, } from '../repositories'; import { FeatureFlag, SystemConfigCore } from '../system-config'; -import { SearchDto, SearchPeopleDto } from './dto'; +import { MetadataSearchDto, SearchDto, SearchPeopleDto, SmartSearchDto } from './dto'; import { SearchResponseDto } from './response-dto'; @Injectable() @@ -27,7 +27,7 @@ export class SearchService { @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository, @Inject(IPersonRepository) private personRepository: IPersonRepository, - @Inject(ISmartInfoRepository) private smartInfoRepository: ISmartInfoRepository, + @Inject(ISearchRepository) private searchRepository: ISearchRepository, @Inject(IAssetRepository) private assetRepository: IAssetRepository, @Inject(IPartnerRepository) private partnerRepository: IPartnerRepository, ) { @@ -55,6 +55,53 @@ export class SearchService { })); } + async searchMetadata(auth: AuthDto, dto: MetadataSearchDto): Promise { + let checksum: Buffer | undefined; + + if (dto.checksum) { + const encoding = dto.checksum.length === 28 ? 'base64' : 'hex'; + checksum = Buffer.from(dto.checksum, encoding); + } + + const page = dto.page ?? 1; + const size = dto.size || 250; + const enumToOrder = { [AssetOrder.ASC]: 'ASC', [AssetOrder.DESC]: 'DESC' } as const; + const { hasNextPage, items } = await this.searchRepository.searchMetadata( + { page, size }, + { + ...dto, + checksum, + ownerId: auth.user.id, + orderDirection: dto.order ? enumToOrder[dto.order] : 'DESC', + }, + ); + + return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null); + } + + async searchSmart(auth: AuthDto, dto: SmartSearchDto): Promise { + await this.configCore.requireFeature(FeatureFlag.SMART_SEARCH); + const { machineLearning } = await this.configCore.getConfig(); + const userIds = await this.getUserIdsToSearch(auth); + + const embedding = await this.machineLearning.encodeText( + machineLearning.url, + { text: dto.query }, + machineLearning.clip, + ); + + const page = dto.page ?? 1; + const size = dto.size || 100; + const { hasNextPage, items } = await this.searchRepository.searchSmart( + { page, size }, + { ...dto, userIds, embedding }, + ); + + return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null); + } + + // TODO: remove after implementing new search filters + /** @deprecated */ async search(auth: AuthDto, dto: SearchDto): Promise { await this.configCore.requireFeature(FeatureFlag.SEARCH); const { machineLearning } = await this.configCore.getConfig(); @@ -70,10 +117,10 @@ export class SearchService { } const userIds = await this.getUserIdsToSearch(auth); - const withArchived = dto.withArchived || false; + const page = dto.page ?? 1; + let nextPage: string | null = null; let assets: AssetEntity[] = []; - switch (strategy) { case SearchStrategy.SMART: { const embedding = await this.machineLearning.encodeText( @@ -81,36 +128,30 @@ export class SearchService { { text: query }, machineLearning.clip, ); - assets = await this.smartInfoRepository.searchCLIP({ - userIds: userIds, - embedding, - numResults: 100, - withArchived, - }); + + const { hasNextPage, items } = await this.searchRepository.searchSmart( + { page, size: dto.size || 100 }, + { + userIds, + embedding, + withArchived: !!dto.withArchived, + }, + ); + if (hasNextPage) { + nextPage = (page + 1).toString(); + } + assets = items; break; } case SearchStrategy.TEXT: { - assets = await this.assetRepository.searchMetadata(query, userIds, { numResults: 250 }); + assets = await this.assetRepository.searchMetadata(query, userIds, { numResults: dto.size || 250 }); } default: { break; } } - return { - albums: { - total: 0, - count: 0, - items: [], - facets: [], - }, - assets: { - total: assets.length, - count: assets.length, - items: assets.map((asset) => mapAsset(asset)), - facets: [], - }, - }; + return this.mapResponse(assets, nextPage); } private async getUserIdsToSearch(auth: AuthDto): Promise { @@ -122,4 +163,17 @@ export class SearchService { userIds.push(...partnersIds); return userIds; } + + private async mapResponse(assets: AssetEntity[], nextPage: string | null): Promise { + return { + albums: { total: 0, count: 0, items: [], facets: [] }, + assets: { + total: assets.length, + count: assets.length, + items: assets.map((asset) => mapAsset(asset)), + facets: [], + nextPage, + }, + }; + } } diff --git a/server/src/domain/smart-info/smart-info.service.spec.ts b/server/src/domain/smart-info/smart-info.service.spec.ts index 373f8da91c0ef..5da7b7824bb98 100644 --- a/server/src/domain/smart-info/smart-info.service.spec.ts +++ b/server/src/domain/smart-info/smart-info.service.spec.ts @@ -5,7 +5,7 @@ import { newDatabaseRepositoryMock, newJobRepositoryMock, newMachineLearningRepositoryMock, - newSmartInfoRepositoryMock, + newSearchRepositoryMock, newSystemConfigRepositoryMock, } from '@test'; import { JobName } from '../job'; @@ -14,7 +14,7 @@ import { IDatabaseRepository, IJobRepository, IMachineLearningRepository, - ISmartInfoRepository, + ISearchRepository, ISystemConfigRepository, WithoutProperty, } from '../repositories'; @@ -31,18 +31,18 @@ describe(SmartInfoService.name, () => { let assetMock: jest.Mocked; let configMock: jest.Mocked; let jobMock: jest.Mocked; - let smartMock: jest.Mocked; + let searchMock: jest.Mocked; let machineMock: jest.Mocked; let databaseMock: jest.Mocked; beforeEach(async () => { assetMock = newAssetRepositoryMock(); configMock = newSystemConfigRepositoryMock(); - smartMock = newSmartInfoRepositoryMock(); + searchMock = newSearchRepositoryMock(); jobMock = newJobRepositoryMock(); machineMock = newMachineLearningRepositoryMock(); databaseMock = newDatabaseRepositoryMock(); - sut = new SmartInfoService(assetMock, databaseMock, jobMock, machineMock, smartMock, configMock); + sut = new SmartInfoService(assetMock, databaseMock, jobMock, machineMock, searchMock, configMock); assetMock.getByIds.mockResolvedValue([asset]); }); @@ -102,12 +102,12 @@ describe(SmartInfoService.name, () => { await sut.handleEncodeClip({ id: asset.id }); - expect(smartMock.upsert).not.toHaveBeenCalled(); + expect(searchMock.upsert).not.toHaveBeenCalled(); expect(machineMock.encodeImage).not.toHaveBeenCalled(); }); it('should save the returned objects', async () => { - smartMock.upsert.mockResolvedValue(); + searchMock.upsert.mockResolvedValue(); machineMock.encodeImage.mockResolvedValue([0.01, 0.02, 0.03]); await sut.handleEncodeClip({ id: asset.id }); @@ -117,7 +117,7 @@ describe(SmartInfoService.name, () => { { imagePath: 'path/to/resize.ext' }, { enabled: true, modelName: 'ViT-B-32__openai' }, ); - expect(smartMock.upsert).toHaveBeenCalledWith( + expect(searchMock.upsert).toHaveBeenCalledWith( { assetId: 'asset-1', }, diff --git a/server/src/domain/smart-info/smart-info.service.ts b/server/src/domain/smart-info/smart-info.service.ts index 55e4b7080fba1..d193b29b510c3 100644 --- a/server/src/domain/smart-info/smart-info.service.ts +++ b/server/src/domain/smart-info/smart-info.service.ts @@ -8,7 +8,7 @@ import { IDatabaseRepository, IJobRepository, IMachineLearningRepository, - ISmartInfoRepository, + ISearchRepository, ISystemConfigRepository, WithoutProperty, } from '../repositories'; @@ -24,7 +24,7 @@ export class SmartInfoService { @Inject(IDatabaseRepository) private databaseRepository: IDatabaseRepository, @Inject(IJobRepository) private jobRepository: IJobRepository, @Inject(IMachineLearningRepository) private machineLearning: IMachineLearningRepository, - @Inject(ISmartInfoRepository) private repository: ISmartInfoRepository, + @Inject(ISearchRepository) private repository: ISearchRepository, @Inject(ISystemConfigRepository) configRepository: ISystemConfigRepository, ) { this.configCore = SystemConfigCore.create(configRepository); diff --git a/server/src/domain/system-config/system-config.service.spec.ts b/server/src/domain/system-config/system-config.service.spec.ts index e8fa3f62e85ed..8addc63a0f61f 100644 --- a/server/src/domain/system-config/system-config.service.spec.ts +++ b/server/src/domain/system-config/system-config.service.spec.ts @@ -15,7 +15,7 @@ import { ImmichLogger } from '@app/infra/logger'; import { BadRequestException } from '@nestjs/common'; import { newCommunicationRepositoryMock, newSystemConfigRepositoryMock } from '@test'; import { QueueName } from '../job'; -import { ICommunicationRepository, ISmartInfoRepository, ISystemConfigRepository, ServerEvent } from '../repositories'; +import { ICommunicationRepository, ISearchRepository, ISystemConfigRepository, ServerEvent } from '../repositories'; import { defaults, SystemConfigValidator } from './system-config.core'; import { SystemConfigService } from './system-config.service'; @@ -146,7 +146,7 @@ describe(SystemConfigService.name, () => { let sut: SystemConfigService; let configMock: jest.Mocked; let communicationMock: jest.Mocked; - let smartInfoMock: jest.Mocked; + let smartInfoMock: jest.Mocked; beforeEach(async () => { delete process.env.IMMICH_CONFIG_FILE; diff --git a/server/src/domain/system-config/system-config.service.ts b/server/src/domain/system-config/system-config.service.ts index 5bf597e35dac6..39a3ea1dfb1f0 100644 --- a/server/src/domain/system-config/system-config.service.ts +++ b/server/src/domain/system-config/system-config.service.ts @@ -6,7 +6,7 @@ import _ from 'lodash'; import { ClientEvent, ICommunicationRepository, - ISmartInfoRepository, + ISearchRepository, ISystemConfigRepository, ServerEvent, } from '../repositories'; @@ -32,7 +32,7 @@ export class SystemConfigService { constructor( @Inject(ISystemConfigRepository) private repository: ISystemConfigRepository, @Inject(ICommunicationRepository) private communicationRepository: ICommunicationRepository, - @Inject(ISmartInfoRepository) private smartInfoRepository: ISmartInfoRepository, + @Inject(ISearchRepository) private smartInfoRepository: ISearchRepository, ) { this.core = SystemConfigCore.create(repository); this.communicationRepository.on(ServerEvent.CONFIG_UPDATE, () => this.handleConfigUpdate()); diff --git a/server/src/domain/trash/trash.service.ts b/server/src/domain/trash/trash.service.ts index b1a38f72c9983..30fd6843e2e9a 100644 --- a/server/src/domain/trash/trash.service.ts +++ b/server/src/domain/trash/trash.service.ts @@ -33,7 +33,9 @@ export class TrashService { async restore(auth: AuthDto): Promise { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.assetRepository.getByUserId(pagination, auth.user.id, { trashedBefore: DateTime.now().toJSDate() }), + this.assetRepository.getByUserId(pagination, auth.user.id, { + trashedBefore: DateTime.now().toJSDate(), + }), ); for await (const assets of assetPagination) { @@ -44,7 +46,9 @@ export class TrashService { async empty(auth: AuthDto): Promise { const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) => - this.assetRepository.getByUserId(pagination, auth.user.id, { trashedBefore: DateTime.now().toJSDate() }), + this.assetRepository.getByUserId(pagination, auth.user.id, { + trashedBefore: DateTime.now().toJSDate(), + }), ); for await (const assets of assetPagination) { diff --git a/server/src/immich/controllers/asset.controller.ts b/server/src/immich/controllers/asset.controller.ts index ad29ff080d99a..57a67d33fee71 100644 --- a/server/src/immich/controllers/asset.controller.ts +++ b/server/src/immich/controllers/asset.controller.ts @@ -3,7 +3,6 @@ import { AssetBulkUpdateDto, AssetJobsDto, AssetResponseDto, - AssetSearchDto, AssetService, AssetStatsDto, AssetStatsResponseDto, @@ -14,7 +13,9 @@ import { MapMarkerResponseDto, MemoryLaneDto, MemoryLaneResponseDto, + MetadataSearchDto, RandomAssetsDto, + SearchService, TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto, @@ -23,7 +24,7 @@ import { UpdateStackParentDto, } from '@app/domain'; import { Body, Controller, Delete, Get, HttpCode, HttpStatus, Param, Post, Put, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { Auth, Authenticated, SharedLinkRoute } from '../app.guard'; import { UseValidation } from '../app.utils'; import { Route } from '../interceptors'; @@ -34,11 +35,15 @@ import { UUIDParamDto } from './dto/uuid-param.dto'; @Authenticated() @UseValidation() export class AssetsController { - constructor(private service: AssetService) {} + constructor(private searchService: SearchService) {} @Get() - searchAssets(@Auth() auth: AuthDto, @Query() dto: AssetSearchDto): Promise { - return this.service.search(auth, dto); + @ApiOperation({ deprecated: true }) + async searchAssets(@Auth() auth: AuthDto, @Query() dto: MetadataSearchDto): Promise { + const { + assets: { items }, + } = await this.searchService.searchMetadata(auth, dto); + return items; } } diff --git a/server/src/immich/controllers/search.controller.ts b/server/src/immich/controllers/search.controller.ts index 51ee900eec0b4..1cc204e8405dd 100644 --- a/server/src/immich/controllers/search.controller.ts +++ b/server/src/immich/controllers/search.controller.ts @@ -1,14 +1,16 @@ import { AuthDto, + MetadataSearchDto, PersonResponseDto, SearchDto, SearchExploreResponseDto, SearchPeopleDto, SearchResponseDto, SearchService, + SmartSearchDto, } from '@app/domain'; import { Controller, Get, Query } from '@nestjs/common'; -import { ApiTags } from '@nestjs/swagger'; +import { ApiOperation, ApiTags } from '@nestjs/swagger'; import { Auth, Authenticated } from '../app.guard'; import { UseValidation } from '../app.utils'; @@ -19,7 +21,18 @@ import { UseValidation } from '../app.utils'; export class SearchController { constructor(private service: SearchService) {} + @Get('metadata') + searchMetadata(@Auth() auth: AuthDto, @Query() dto: MetadataSearchDto): Promise { + return this.service.searchMetadata(auth, dto); + } + + @Get('smart') + searchSmart(@Auth() auth: AuthDto, @Query() dto: SmartSearchDto): Promise { + return this.service.searchSmart(auth, dto); + } + @Get() + @ApiOperation({ deprecated: true }) search(@Auth() auth: AuthDto, @Query() dto: SearchDto): Promise { return this.service.search(auth, dto); } diff --git a/server/src/infra/infra.module.ts b/server/src/infra/infra.module.ts index 93cb8fb6812b0..b36fdf6f0a57c 100644 --- a/server/src/infra/infra.module.ts +++ b/server/src/infra/infra.module.ts @@ -17,9 +17,9 @@ import { IMoveRepository, IPartnerRepository, IPersonRepository, + ISearchRepository, IServerInfoRepository, ISharedLinkRepository, - ISmartInfoRepository, IStorageRepository, ISystemConfigRepository, ISystemMetadataRepository, @@ -56,9 +56,9 @@ import { MoveRepository, PartnerRepository, PersonRepository, + SearchRepository, ServerInfoRepository, SharedLinkRepository, - SmartInfoRepository, SystemConfigRepository, SystemMetadataRepository, TagRepository, @@ -86,7 +86,7 @@ const providers: Provider[] = [ { provide: IPersonRepository, useClass: PersonRepository }, { provide: IServerInfoRepository, useClass: ServerInfoRepository }, { provide: ISharedLinkRepository, useClass: SharedLinkRepository }, - { provide: ISmartInfoRepository, useClass: SmartInfoRepository }, + { provide: ISearchRepository, useClass: SearchRepository }, { provide: IStorageRepository, useClass: FilesystemProvider }, { provide: ISystemConfigRepository, useClass: SystemConfigRepository }, { provide: ISystemMetadataRepository, useClass: SystemMetadataRepository }, diff --git a/server/src/infra/infra.utils.ts b/server/src/infra/infra.utils.ts index 1036df2afaccb..89bd319662e5a 100644 --- a/server/src/infra/infra.utils.ts +++ b/server/src/infra/infra.utils.ts @@ -1,7 +1,19 @@ -import { Paginated, PaginationOptions } from '@app/domain'; +import { AssetSearchBuilderOptions, Paginated, PaginationOptions } from '@app/domain'; import _ from 'lodash'; -import { Between, FindManyOptions, LessThanOrEqual, MoreThanOrEqual, ObjectLiteral, Repository } from 'typeorm'; -import { chunks, setUnion } from '../domain/domain.util'; +import { + Between, + Brackets, + FindManyOptions, + IsNull, + LessThanOrEqual, + MoreThanOrEqual, + Not, + ObjectLiteral, + Repository, + SelectQueryBuilder, +} from 'typeorm'; +import { PaginatedBuilderOptions, PaginationMode, PaginationResult, chunks, setUnion } from '../domain/domain.util'; +import { AssetEntity } from './entities'; import { DATABASE_PARAMETER_CHUNK_SIZE } from './infra.util'; /** @@ -18,9 +30,21 @@ export function OptionalBetween(from?: T, to?: T) { } } +export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => { + const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options; + return Number.isInteger(value) && value >= min && value <= max; +}; + +function paginationHelper(items: Entity[], take: number): PaginationResult { + const hasNextPage = items.length > take; + items.splice(take); + + return { items, hasNextPage }; +} + export async function paginate( repository: Repository, - paginationOptions: PaginationOptions, + { take, skip }: PaginationOptions, searchOptions?: FindManyOptions, ): Paginated { const items = await repository.find( @@ -28,27 +52,33 @@ export async function paginate( { ...searchOptions, // Take one more item to check if there's a next page - take: paginationOptions.take + 1, - skip: paginationOptions.skip, + take: take + 1, + skip, }, _.isUndefined, ), ); - const hasNextPage = items.length > paginationOptions.take; - items.splice(paginationOptions.take); + return paginationHelper(items, take); +} - return { items, hasNextPage }; +export async function paginatedBuilder( + qb: SelectQueryBuilder, + { take, skip, mode }: PaginatedBuilderOptions, +): Paginated { + if (mode === PaginationMode.LIMIT_OFFSET) { + qb.limit(take + 1).offset(skip); + } else { + qb.take(take + 1).skip(skip); + } + + const items = await qb.getMany(); + return paginationHelper(items, take); } export const asVector = (embedding: number[], quote = false) => quote ? `'[${embedding.join(',')}]'` : `[${embedding.join(',')}]`; -export const isValidInteger = (value: number, options: { min?: number; max?: number }): value is number => { - const { min = Number.MIN_SAFE_INTEGER, max = Number.MAX_SAFE_INTEGER } = options; - return Number.isInteger(value) && value >= min && value <= max; -}; - /** * Wraps a method that takes a collection of parameters and sequentially calls it with chunks of the collection, * to overcome the maximum number of parameters allowed by the database driver. @@ -91,3 +121,79 @@ export function ChunkedArray(options?: { paramIndex?: number }): MethodDecorator export function ChunkedSet(options?: { paramIndex?: number }): MethodDecorator { return Chunked({ ...options, mergeFn: setUnion }); } + +export function searchAssetBuilder( + builder: SelectQueryBuilder, + options: AssetSearchBuilderOptions, +): SelectQueryBuilder { + builder.andWhere( + _.omitBy( + { + createdAt: OptionalBetween(options.createdAfter, options.createdBefore), + updatedAt: OptionalBetween(options.updatedAfter, options.updatedBefore), + deletedAt: OptionalBetween(options.trashedAfter, options.trashedBefore), + fileCreatedAt: OptionalBetween(options.takenAfter, options.takenBefore), + }, + _.isUndefined, + ), + ); + + const exifInfo = _.omitBy(_.pick(options, ['city', 'country', 'lensModel', 'make', 'model', 'state']), _.isUndefined); + if (Object.keys(exifInfo).length > 0) { + builder.leftJoin(`${builder.alias}.exifInfo`, 'exifInfo'); + builder.andWhere({ exifInfo }); + } + + const id = _.pick(options, ['checksum', 'deviceAssetId', 'deviceId', 'id', 'libraryId', 'ownerId']); + builder.andWhere(_.omitBy(id, _.isUndefined)); + + const path = _.pick(options, ['encodedVideoPath', 'originalFileName', 'originalPath', 'resizePath', 'webpPath']); + builder.andWhere(_.omitBy(path, _.isUndefined)); + + const status = _.pick(options, ['isExternal', 'isFavorite', 'isOffline', 'isReadOnly', 'isVisible', 'type']); + const { isArchived, isEncoded, isMotion, withArchived } = options; + builder.andWhere( + _.omitBy( + { + ...status, + isArchived: isArchived ?? withArchived, + encodedVideoPath: isEncoded ? Not(IsNull()) : undefined, + livePhotoVideoId: isMotion ? Not(IsNull()) : undefined, + }, + _.isUndefined, + ), + ); + + if (options.withExif) { + builder.leftJoinAndSelect(`${builder.alias}.exifInfo`, 'exifInfo'); + } + + if (options.withFaces || options.withPeople) { + builder.leftJoinAndSelect(`${builder.alias}.faces`, 'faces'); + } + + if (options.withPeople) { + builder.leftJoinAndSelect(`${builder.alias}.person`, 'person'); + } + + if (options.withSmartInfo) { + builder.leftJoinAndSelect(`${builder.alias}.smartInfo`, 'smartInfo'); + } + + if (options.withStacked) { + builder + .leftJoinAndSelect(`${builder.alias}.stack`, 'stack') + .leftJoinAndSelect('stack.assets', 'stackedAssets') + .andWhere( + new Brackets((qb) => qb.where(`stack.primaryAssetId = ${builder.alias}.id`).orWhere('asset.stackId IS NULL')), + ); + } + + const withDeleted = + options.withDeleted ?? (options.trashedAfter !== undefined || options.trashedBefore !== undefined); + if (withDeleted) { + builder.withDeleted(); + } + + return builder; +} diff --git a/server/src/infra/repositories/api-key.repository.ts b/server/src/infra/repositories/api-key.repository.ts index 71226a5376ac5..b7ebc303dc03e 100644 --- a/server/src/infra/repositories/api-key.repository.ts +++ b/server/src/infra/repositories/api-key.repository.ts @@ -42,7 +42,7 @@ export class ApiKeyRepository implements IKeyRepository { return this.repository.findOne({ where: { userId, id } }); } - @GenerateSql({ params: [DummyValue.STRING] }) + @GenerateSql({ params: [DummyValue.UUID] }) getByUserId(userId: string): Promise { return this.repository.find({ where: { userId }, order: { createdAt: 'DESC' } }); } diff --git a/server/src/infra/repositories/asset.repository.ts b/server/src/infra/repositories/asset.repository.ts index 95a227b693010..215d280f4c25e 100644 --- a/server/src/infra/repositories/asset.repository.ts +++ b/server/src/infra/repositories/asset.repository.ts @@ -12,6 +12,7 @@ import { MetadataSearchOptions, MonthDay, Paginated, + PaginationMode, PaginationOptions, SearchExploreItem, TimeBucketItem, @@ -22,26 +23,21 @@ import { } from '@app/domain'; import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; -import _ from 'lodash'; import { DateTime } from 'luxon'; import path from 'node:path'; import { - And, Brackets, FindOptionsRelations, FindOptionsSelect, FindOptionsWhere, In, IsNull, - LessThan, Not, Repository, } from 'typeorm'; import { AssetEntity, AssetJobStatusEntity, AssetType, ExifEntity, SmartInfoEntity } from '../entities'; import { DummyValue, GenerateSql } from '../infra.util'; -import { Chunked, ChunkedArray, OptionalBetween, paginate } from '../infra.utils'; - -const DEFAULT_SEARCH_SIZE = 250; +import { Chunked, ChunkedArray, OptionalBetween, paginate, paginatedBuilder, searchAssetBuilder } from '../infra.utils'; const truncateMap: Record = { [TimeBucketSize.DAY]: 'day', @@ -70,142 +66,6 @@ export class AssetRepository implements IAssetRepository { await this.jobStatusRepository.upsert(jobStatus, { conflictPaths: ['assetId'] }); } - search(options: AssetSearchOptions): Promise { - const { - id, - libraryId, - deviceAssetId, - type, - checksum, - ownerId, - - isVisible, - isFavorite, - isExternal, - isReadOnly, - isOffline, - isArchived, - isMotion, - isEncoded, - - createdBefore, - createdAfter, - updatedBefore, - updatedAfter, - trashedBefore, - trashedAfter, - takenBefore, - takenAfter, - - originalFileName, - originalPath, - resizePath, - webpPath, - encodedVideoPath, - - city, - state, - country, - make, - model, - lensModel, - - withDeleted: _withDeleted, - withExif: _withExif, - withStacked, - withPeople, - withSmartInfo, - - order, - } = options; - - const withDeleted = _withDeleted ?? (trashedAfter !== undefined || trashedBefore !== undefined); - - const page = Math.max(options.page || 1, 1); - const size = Math.min(options.size || DEFAULT_SEARCH_SIZE, DEFAULT_SEARCH_SIZE); - - const exifWhere = _.omitBy( - { - city, - state, - country, - make, - model, - lensModel, - }, - _.isUndefined, - ); - - const withExif = Object.keys(exifWhere).length > 0 || _withExif; - - const where: FindOptionsWhere = _.omitBy( - { - ownerId, - id, - libraryId, - deviceAssetId, - type, - checksum, - isVisible, - isFavorite, - isExternal, - isReadOnly, - isOffline, - isArchived, - livePhotoVideoId: isMotion && Not(IsNull()), - originalFileName, - originalPath, - resizePath, - webpPath, - encodedVideoPath: encodedVideoPath ?? (isEncoded && Not(IsNull())), - createdAt: OptionalBetween(createdAfter, createdBefore), - updatedAt: OptionalBetween(updatedAfter, updatedBefore), - deletedAt: OptionalBetween(trashedAfter, trashedBefore), - fileCreatedAt: OptionalBetween(takenAfter, takenBefore), - exifInfo: Object.keys(exifWhere).length > 0 ? exifWhere : undefined, - }, - _.isUndefined, - ); - - const builder = this.repository.createQueryBuilder('asset'); - - if (withExif) { - if (_withExif) { - builder.leftJoinAndSelect('asset.exifInfo', 'exifInfo'); - } else { - builder.leftJoin('asset.exifInfo', 'exifInfo'); - } - } - - if (withPeople) { - builder.leftJoinAndSelect('asset.faces', 'faces'); - builder.leftJoinAndSelect('faces.person', 'person'); - } - - if (withSmartInfo) { - builder.leftJoinAndSelect('asset.smartInfo', 'smartInfo'); - } - - if (withDeleted) { - builder.withDeleted(); - } - - builder.where(where); - - if (withStacked) { - builder - .leftJoinAndSelect('asset.stack', 'stack') - .leftJoinAndSelect('stack.assets', 'stackedAssets') - .andWhere(new Brackets((qb) => qb.where('stack.primaryAssetId = asset.id').orWhere('asset.stackId IS NULL'))); - } - - return builder - .skip(size * (page - 1)) - .take(size) - .orderBy('asset.fileCreatedAt', order ?? 'DESC') - .getMany(); - } - create(asset: AssetCreate): Promise { return this.repository.save(asset); } @@ -316,17 +176,7 @@ export class AssetRepository implements IAssetRepository { } getByUserId(pagination: PaginationOptions, userId: string, options: AssetSearchOptions = {}): Paginated { - return paginate(this.repository, pagination, { - where: { - ownerId: userId, - isVisible: options.isVisible, - deletedAt: options.trashedBefore ? And(Not(IsNull()), LessThan(options.trashedBefore)) : undefined, - }, - relations: { - exifInfo: true, - }, - withDeleted: !!options.trashedBefore, - }); + return this.getAll(pagination, { ...options, id: userId }); } @GenerateSql({ params: [[DummyValue.UUID]] }) @@ -345,24 +195,13 @@ export class AssetRepository implements IAssetRepository { } getAll(pagination: PaginationOptions, options: AssetSearchOptions = {}): Paginated { - return paginate(this.repository, pagination, { - where: { - isVisible: options.isVisible, - type: options.type, - deletedAt: options.trashedBefore ? And(Not(IsNull()), LessThan(options.trashedBefore)) : undefined, - }, - relations: { - exifInfo: options.withExif !== false, - smartInfo: options.withSmartInfo !== false, - tags: options.withSmartInfo !== false, - faces: options.withFaces !== false, - smartSearch: options.withSmartInfo === true, - }, - withDeleted: options.withDeleted ?? !!options.trashedBefore, - order: { - // Ensures correct order when paginating - createdAt: options.order ?? 'ASC', - }, + let builder = this.repository.createQueryBuilder('asset'); + builder = searchAssetBuilder(builder, options); + builder.orderBy('asset.createdAt', options.orderDirection ?? 'ASC'); + return paginatedBuilder(builder, { + mode: PaginationMode.SKIP_TAKE, + skip: pagination.skip, + take: pagination.take, }); } @@ -435,7 +274,7 @@ export class AssetRepository implements IAssetRepository { await this.repository.remove(asset); } - @GenerateSql({ params: [[DummyValue.UUID], DummyValue.BUFFER] }) + @GenerateSql({ params: [DummyValue.UUID, DummyValue.BUFFER] }) getByChecksum(userId: string, checksum: Buffer): Promise { return this.repository.findOne({ where: { ownerId: userId, checksum } }); } diff --git a/server/src/infra/repositories/index.ts b/server/src/infra/repositories/index.ts index 21703ec8c8ce8..d684f6b00406d 100644 --- a/server/src/infra/repositories/index.ts +++ b/server/src/infra/repositories/index.ts @@ -17,9 +17,9 @@ export * from './metadata.repository'; export * from './move.repository'; export * from './partner.repository'; export * from './person.repository'; +export * from './search.repository'; export * from './server-info.repository'; export * from './shared-link.repository'; -export * from './smart-info.repository'; export * from './system-config.repository'; export * from './system-metadata.repository'; export * from './tag.repository'; diff --git a/server/src/infra/repositories/smart-info.repository.ts b/server/src/infra/repositories/search.repository.ts similarity index 68% rename from server/src/infra/repositories/smart-info.repository.ts rename to server/src/infra/repositories/search.repository.ts index f74fd4232d9e5..7d0421b05d17c 100644 --- a/server/src/infra/repositories/smart-info.repository.ts +++ b/server/src/infra/repositories/search.repository.ts @@ -1,10 +1,15 @@ import { + AssetSearchOptions, DatabaseExtension, Embedding, - EmbeddingSearch, FaceEmbeddingSearch, FaceSearchResult, - ISmartInfoRepository, + ISearchRepository, + Paginated, + PaginationMode, + PaginationResult, + SearchPaginationOptions, + SmartSearchOptions, } from '@app/domain'; import { getCLIPModelInfo } from '@app/domain/smart-info/smart-info.constant'; import { AssetEntity, AssetFaceEntity, SmartInfoEntity, SmartSearchEntity } from '@app/infra/entities'; @@ -14,11 +19,11 @@ import { InjectRepository } from '@nestjs/typeorm'; import { Repository } from 'typeorm'; import { vectorExt } from '../database.config'; import { DummyValue, GenerateSql } from '../infra.util'; -import { asVector, isValidInteger } from '../infra.utils'; +import { asVector, isValidInteger, paginatedBuilder, searchAssetBuilder } from '../infra.utils'; @Injectable() -export class SmartInfoRepository implements ISmartInfoRepository { - private logger = new ImmichLogger(SmartInfoRepository.name); +export class SearchRepository implements ISearchRepository { + private logger = new ImmichLogger(SearchRepository.name); private faceColumns: string[]; constructor( @@ -35,48 +40,74 @@ export class SmartInfoRepository implements ISmartInfoRepository { async init(modelName: string): Promise { const { dimSize } = getCLIPModelInfo(modelName); - if (dimSize == null) { - throw new Error(`Invalid CLIP model name: ${modelName}`); - } + const curDimSize = await this.getDimSize(); + this.logger.verbose(`Current database CLIP dimension size is ${curDimSize}`); - const currentDimSize = await this.getDimSize(); - this.logger.verbose(`Current database CLIP dimension size is ${currentDimSize}`); - - if (dimSize != currentDimSize) { - this.logger.log(`Dimension size of model ${modelName} is ${dimSize}, but database expects ${currentDimSize}.`); + if (dimSize != curDimSize) { + this.logger.log(`Dimension size of model ${modelName} is ${dimSize}, but database expects ${curDimSize}.`); await this.updateDimSize(dimSize); } } @GenerateSql({ - params: [{ userIds: [DummyValue.UUID], embedding: Array.from({ length: 512 }, Math.random), numResults: 100 }], + params: [ + { page: 1, size: 100 }, + { + takenAfter: DummyValue.DATE, + lensModel: DummyValue.STRING, + ownerId: DummyValue.UUID, + withStacked: true, + isFavorite: true, + }, + ], }) - async searchCLIP({ userIds, embedding, numResults, withArchived }: EmbeddingSearch): Promise { - if (!isValidInteger(numResults, { min: 1 })) { - throw new Error(`Invalid value for 'numResults': ${numResults}`); - } + async searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions): Paginated { + let builder = this.assetRepository.createQueryBuilder('asset'); + builder = searchAssetBuilder(builder, options); - // setting this too low messes with prefilter recall - numResults = Math.max(numResults, 64); + builder.orderBy('asset.fileCreatedAt', options.orderDirection ?? 'DESC'); + + return paginatedBuilder(builder, { + mode: PaginationMode.SKIP_TAKE, + skip: (pagination.page - 1) * pagination.size, + take: pagination.size, + }); + } + + @GenerateSql({ + params: [ + { page: 1, size: 100 }, + { + takenAfter: DummyValue.DATE, + embedding: Array.from({ length: 512 }, Math.random), + lensModel: DummyValue.STRING, + withStacked: true, + isFavorite: true, + userIds: [DummyValue.UUID], + }, + ], + }) + async searchSmart( + pagination: SearchPaginationOptions, + { embedding, userIds, ...options }: SmartSearchOptions, + ): Paginated { + let results: PaginationResult = { items: [], hasNextPage: false }; - let results: AssetEntity[] = []; await this.assetRepository.manager.transaction(async (manager) => { - const query = manager - .createQueryBuilder(AssetEntity, 'a') - .innerJoin('a.smartSearch', 's') - .leftJoinAndSelect('a.exifInfo', 'e') - .where('a.ownerId IN (:...userIds )') - .orderBy('s.embedding <=> :embedding') + let builder = manager.createQueryBuilder(AssetEntity, 'asset'); + builder = searchAssetBuilder(builder, options); + builder + .innerJoin('asset.smartSearch', 'search') + .andWhere('asset.ownerId IN (:...userIds )') + .orderBy('search.embedding <=> :embedding') .setParameters({ userIds, embedding: asVector(embedding) }); - if (!withArchived) { - query.andWhere('a.isArchived = false'); - } - query.andWhere('a.isVisible = true').andWhere('a.fileCreatedAt < NOW()'); - query.limit(numResults); - - await manager.query(this.getRuntimeConfig(numResults)); - results = await query.getMany(); + await manager.query(this.getRuntimeConfig(pagination.size)); + results = await paginatedBuilder(builder, { + mode: PaginationMode.LIMIT_OFFSET, + skip: (pagination.page - 1) * pagination.size, + take: pagination.size, + }); }); return results; @@ -135,7 +166,6 @@ export class SmartInfoRepository implements ISmartInfoRepository { .where('res.distance <= :maxDistance', { maxDistance }) .getRawMany(); }); - return results.map((row) => ({ face: this.assetFaceRepository.create(row), distance: row.distance, @@ -163,17 +193,14 @@ export class SmartInfoRepository implements ISmartInfoRepository { throw new Error(`Invalid CLIP dimension size: ${dimSize}`); } - const currentDimSize = await this.getDimSize(); - if (currentDimSize === dimSize) { + const curDimSize = await this.getDimSize(); + if (curDimSize === dimSize) { return; } this.logger.log(`Updating database CLIP dimension size to ${dimSize}.`); await this.smartSearchRepository.manager.transaction(async (manager) => { - if (vectorExt === DatabaseExtension.VECTORS) { - await manager.query(`SET vectors.pgvector_compatibility=on`); - } await manager.query(`DROP TABLE smart_search`); await manager.query(` @@ -182,12 +209,15 @@ export class SmartInfoRepository implements ISmartInfoRepository { embedding vector(${dimSize}) NOT NULL )`); await manager.query(` - CREATE INDEX IF NOT EXISTS clip_index ON smart_search - USING hnsw (embedding vector_cosine_ops) - WITH (ef_construction = 300, m = 16)`); + CREATE INDEX clip_index ON smart_search + USING vectors (embedding vector_cos_ops) WITH (options = $$ + [indexing.hnsw] + m = 16 + ef_construction = 300 + $$)`); }); - this.logger.log(`Successfully updated database CLIP dimension size from ${currentDimSize} to ${dimSize}.`); + this.logger.log(`Successfully updated database CLIP dimension size from ${curDimSize} to ${dimSize}.`); } private async getDimSize(): Promise { diff --git a/server/src/infra/sql-generator/index.ts b/server/src/infra/sql-generator/index.ts index 348762d9571e8..0b10c018c12ef 100644 --- a/server/src/infra/sql-generator/index.ts +++ b/server/src/infra/sql-generator/index.ts @@ -19,8 +19,8 @@ import { MoveRepository, PartnerRepository, PersonRepository, + SearchRepository, SharedLinkRepository, - SmartInfoRepository, SystemConfigRepository, SystemMetadataRepository, TagRepository, @@ -41,7 +41,7 @@ const repositories = [ PartnerRepository, PersonRepository, SharedLinkRepository, - SmartInfoRepository, + SearchRepository, SystemConfigRepository, SystemMetadataRepository, TagRepository, @@ -142,7 +142,7 @@ class SqlGenerator { this.sqlLogger.clear(); // errors still generate sql, which is all we care about - await target.apply(instance, params).catch(() => null); + await target.apply(instance, params).catch((error: Error) => console.error(`${queryLabel} error: ${error}`)); if (this.sqlLogger.queries.length === 0) { console.warn(`No queries recorded for ${queryLabel}`); diff --git a/server/src/infra/sql/search.repository.sql b/server/src/infra/sql/search.repository.sql new file mode 100644 index 0000000000000..538a85409441f --- /dev/null +++ b/server/src/infra/sql/search.repository.sql @@ -0,0 +1,234 @@ +-- NOTE: This file is auto generated by ./sql-generator + +-- SearchRepository.searchMetadata +SELECT DISTINCT + "distinctAlias"."asset_id" AS "ids_asset_id", + "distinctAlias"."asset_fileCreatedAt" +FROM + ( + SELECT + "asset"."id" AS "asset_id", + "asset"."deviceAssetId" AS "asset_deviceAssetId", + "asset"."ownerId" AS "asset_ownerId", + "asset"."libraryId" AS "asset_libraryId", + "asset"."deviceId" AS "asset_deviceId", + "asset"."type" AS "asset_type", + "asset"."originalPath" AS "asset_originalPath", + "asset"."resizePath" AS "asset_resizePath", + "asset"."webpPath" AS "asset_webpPath", + "asset"."thumbhash" AS "asset_thumbhash", + "asset"."encodedVideoPath" AS "asset_encodedVideoPath", + "asset"."createdAt" AS "asset_createdAt", + "asset"."updatedAt" AS "asset_updatedAt", + "asset"."deletedAt" AS "asset_deletedAt", + "asset"."fileCreatedAt" AS "asset_fileCreatedAt", + "asset"."localDateTime" AS "asset_localDateTime", + "asset"."fileModifiedAt" AS "asset_fileModifiedAt", + "asset"."isFavorite" AS "asset_isFavorite", + "asset"."isArchived" AS "asset_isArchived", + "asset"."isExternal" AS "asset_isExternal", + "asset"."isReadOnly" AS "asset_isReadOnly", + "asset"."isOffline" AS "asset_isOffline", + "asset"."checksum" AS "asset_checksum", + "asset"."duration" AS "asset_duration", + "asset"."isVisible" AS "asset_isVisible", + "asset"."livePhotoVideoId" AS "asset_livePhotoVideoId", + "asset"."originalFileName" AS "asset_originalFileName", + "asset"."sidecarPath" AS "asset_sidecarPath", + "asset"."stackId" AS "asset_stackId", + "stack"."id" AS "stack_id", + "stack"."primaryAssetId" AS "stack_primaryAssetId", + "stackedAssets"."id" AS "stackedAssets_id", + "stackedAssets"."deviceAssetId" AS "stackedAssets_deviceAssetId", + "stackedAssets"."ownerId" AS "stackedAssets_ownerId", + "stackedAssets"."libraryId" AS "stackedAssets_libraryId", + "stackedAssets"."deviceId" AS "stackedAssets_deviceId", + "stackedAssets"."type" AS "stackedAssets_type", + "stackedAssets"."originalPath" AS "stackedAssets_originalPath", + "stackedAssets"."resizePath" AS "stackedAssets_resizePath", + "stackedAssets"."webpPath" AS "stackedAssets_webpPath", + "stackedAssets"."thumbhash" AS "stackedAssets_thumbhash", + "stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath", + "stackedAssets"."createdAt" AS "stackedAssets_createdAt", + "stackedAssets"."updatedAt" AS "stackedAssets_updatedAt", + "stackedAssets"."deletedAt" AS "stackedAssets_deletedAt", + "stackedAssets"."fileCreatedAt" AS "stackedAssets_fileCreatedAt", + "stackedAssets"."localDateTime" AS "stackedAssets_localDateTime", + "stackedAssets"."fileModifiedAt" AS "stackedAssets_fileModifiedAt", + "stackedAssets"."isFavorite" AS "stackedAssets_isFavorite", + "stackedAssets"."isArchived" AS "stackedAssets_isArchived", + "stackedAssets"."isExternal" AS "stackedAssets_isExternal", + "stackedAssets"."isReadOnly" AS "stackedAssets_isReadOnly", + "stackedAssets"."isOffline" AS "stackedAssets_isOffline", + "stackedAssets"."checksum" AS "stackedAssets_checksum", + "stackedAssets"."duration" AS "stackedAssets_duration", + "stackedAssets"."isVisible" AS "stackedAssets_isVisible", + "stackedAssets"."livePhotoVideoId" AS "stackedAssets_livePhotoVideoId", + "stackedAssets"."originalFileName" AS "stackedAssets_originalFileName", + "stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath", + "stackedAssets"."stackId" AS "stackedAssets_stackId" + FROM + "assets" "asset" + LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id" + LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId" + LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id" + AND ("stackedAssets"."deletedAt" IS NULL) + WHERE + ( + "asset"."fileCreatedAt" >= $1 + AND "exifInfo"."lensModel" = $2 + AND "asset"."ownerId" = $3 + AND 1 = 1 + AND "asset"."isFavorite" = $4 + AND ( + "stack"."primaryAssetId" = "asset"."id" + OR "asset"."stackId" IS NULL + ) + ) + AND ("asset"."deletedAt" IS NULL) + ) "distinctAlias" +ORDER BY + "distinctAlias"."asset_fileCreatedAt" DESC, + "asset_id" ASC +LIMIT + 101 + +-- SearchRepository.searchSmart +START TRANSACTION +SET + LOCAL vectors.enable_prefilter = on; + +SET + LOCAL vectors.search_mode = vbase; + +SET + LOCAL vectors.hnsw_ef_search = 100; +SELECT + "asset"."id" AS "asset_id", + "asset"."deviceAssetId" AS "asset_deviceAssetId", + "asset"."ownerId" AS "asset_ownerId", + "asset"."libraryId" AS "asset_libraryId", + "asset"."deviceId" AS "asset_deviceId", + "asset"."type" AS "asset_type", + "asset"."originalPath" AS "asset_originalPath", + "asset"."resizePath" AS "asset_resizePath", + "asset"."webpPath" AS "asset_webpPath", + "asset"."thumbhash" AS "asset_thumbhash", + "asset"."encodedVideoPath" AS "asset_encodedVideoPath", + "asset"."createdAt" AS "asset_createdAt", + "asset"."updatedAt" AS "asset_updatedAt", + "asset"."deletedAt" AS "asset_deletedAt", + "asset"."fileCreatedAt" AS "asset_fileCreatedAt", + "asset"."localDateTime" AS "asset_localDateTime", + "asset"."fileModifiedAt" AS "asset_fileModifiedAt", + "asset"."isFavorite" AS "asset_isFavorite", + "asset"."isArchived" AS "asset_isArchived", + "asset"."isExternal" AS "asset_isExternal", + "asset"."isReadOnly" AS "asset_isReadOnly", + "asset"."isOffline" AS "asset_isOffline", + "asset"."checksum" AS "asset_checksum", + "asset"."duration" AS "asset_duration", + "asset"."isVisible" AS "asset_isVisible", + "asset"."livePhotoVideoId" AS "asset_livePhotoVideoId", + "asset"."originalFileName" AS "asset_originalFileName", + "asset"."sidecarPath" AS "asset_sidecarPath", + "asset"."stackId" AS "asset_stackId", + "stack"."id" AS "stack_id", + "stack"."primaryAssetId" AS "stack_primaryAssetId", + "stackedAssets"."id" AS "stackedAssets_id", + "stackedAssets"."deviceAssetId" AS "stackedAssets_deviceAssetId", + "stackedAssets"."ownerId" AS "stackedAssets_ownerId", + "stackedAssets"."libraryId" AS "stackedAssets_libraryId", + "stackedAssets"."deviceId" AS "stackedAssets_deviceId", + "stackedAssets"."type" AS "stackedAssets_type", + "stackedAssets"."originalPath" AS "stackedAssets_originalPath", + "stackedAssets"."resizePath" AS "stackedAssets_resizePath", + "stackedAssets"."webpPath" AS "stackedAssets_webpPath", + "stackedAssets"."thumbhash" AS "stackedAssets_thumbhash", + "stackedAssets"."encodedVideoPath" AS "stackedAssets_encodedVideoPath", + "stackedAssets"."createdAt" AS "stackedAssets_createdAt", + "stackedAssets"."updatedAt" AS "stackedAssets_updatedAt", + "stackedAssets"."deletedAt" AS "stackedAssets_deletedAt", + "stackedAssets"."fileCreatedAt" AS "stackedAssets_fileCreatedAt", + "stackedAssets"."localDateTime" AS "stackedAssets_localDateTime", + "stackedAssets"."fileModifiedAt" AS "stackedAssets_fileModifiedAt", + "stackedAssets"."isFavorite" AS "stackedAssets_isFavorite", + "stackedAssets"."isArchived" AS "stackedAssets_isArchived", + "stackedAssets"."isExternal" AS "stackedAssets_isExternal", + "stackedAssets"."isReadOnly" AS "stackedAssets_isReadOnly", + "stackedAssets"."isOffline" AS "stackedAssets_isOffline", + "stackedAssets"."checksum" AS "stackedAssets_checksum", + "stackedAssets"."duration" AS "stackedAssets_duration", + "stackedAssets"."isVisible" AS "stackedAssets_isVisible", + "stackedAssets"."livePhotoVideoId" AS "stackedAssets_livePhotoVideoId", + "stackedAssets"."originalFileName" AS "stackedAssets_originalFileName", + "stackedAssets"."sidecarPath" AS "stackedAssets_sidecarPath", + "stackedAssets"."stackId" AS "stackedAssets_stackId" +FROM + "assets" "asset" + LEFT JOIN "exif" "exifInfo" ON "exifInfo"."assetId" = "asset"."id" + LEFT JOIN "asset_stack" "stack" ON "stack"."id" = "asset"."stackId" + LEFT JOIN "assets" "stackedAssets" ON "stackedAssets"."stackId" = "stack"."id" + AND ("stackedAssets"."deletedAt" IS NULL) + INNER JOIN "smart_search" "search" ON "search"."assetId" = "asset"."id" +WHERE + ( + "asset"."fileCreatedAt" >= $1 + AND "exifInfo"."lensModel" = $2 + AND 1 = 1 + AND 1 = 1 + AND "asset"."isFavorite" = $3 + AND ( + "stack"."primaryAssetId" = "asset"."id" + OR "asset"."stackId" IS NULL + ) + AND "asset"."ownerId" IN ($4) + ) + AND ("asset"."deletedAt" IS NULL) +ORDER BY + "search"."embedding" <= > $5 ASC +LIMIT + 101 +COMMIT + +-- SearchRepository.searchFaces +START TRANSACTION +SET + LOCAL vectors.enable_prefilter = on; + +SET + LOCAL vectors.search_mode = vbase; + +SET + LOCAL vectors.hnsw_ef_search = 100; +WITH + "cte" AS ( + SELECT + "faces"."id" AS "id", + "faces"."assetId" AS "assetId", + "faces"."personId" AS "personId", + "faces"."imageWidth" AS "imageWidth", + "faces"."imageHeight" AS "imageHeight", + "faces"."boundingBoxX1" AS "boundingBoxX1", + "faces"."boundingBoxY1" AS "boundingBoxY1", + "faces"."boundingBoxX2" AS "boundingBoxX2", + "faces"."boundingBoxY2" AS "boundingBoxY2", + "faces"."embedding" <= > $1 AS "distance" + FROM + "asset_faces" "faces" + INNER JOIN "assets" "asset" ON "asset"."id" = "faces"."assetId" + AND ("asset"."deletedAt" IS NULL) + WHERE + "asset"."ownerId" IN ($2) + ORDER BY + "faces"."embedding" <= > $1 ASC + LIMIT + 100 + ) +SELECT + res.* +FROM + "cte" "res" +WHERE + res.distance <= $3 +COMMIT diff --git a/server/src/infra/sql/smart.info.repository.sql b/server/src/infra/sql/smart.info.repository.sql deleted file mode 100644 index 3151aede73a59..0000000000000 --- a/server/src/infra/sql/smart.info.repository.sql +++ /dev/null @@ -1,129 +0,0 @@ --- NOTE: This file is auto generated by ./sql-generator - --- SmartInfoRepository.searchCLIP -START TRANSACTION -SET - LOCAL vectors.enable_prefilter = on; - -SET - LOCAL vectors.search_mode = vbase; - -SET - LOCAL vectors.hnsw_ef_search = 100; -SELECT - "a"."id" AS "a_id", - "a"."deviceAssetId" AS "a_deviceAssetId", - "a"."ownerId" AS "a_ownerId", - "a"."libraryId" AS "a_libraryId", - "a"."deviceId" AS "a_deviceId", - "a"."type" AS "a_type", - "a"."originalPath" AS "a_originalPath", - "a"."resizePath" AS "a_resizePath", - "a"."webpPath" AS "a_webpPath", - "a"."thumbhash" AS "a_thumbhash", - "a"."encodedVideoPath" AS "a_encodedVideoPath", - "a"."createdAt" AS "a_createdAt", - "a"."updatedAt" AS "a_updatedAt", - "a"."deletedAt" AS "a_deletedAt", - "a"."fileCreatedAt" AS "a_fileCreatedAt", - "a"."localDateTime" AS "a_localDateTime", - "a"."fileModifiedAt" AS "a_fileModifiedAt", - "a"."isFavorite" AS "a_isFavorite", - "a"."isArchived" AS "a_isArchived", - "a"."isExternal" AS "a_isExternal", - "a"."isReadOnly" AS "a_isReadOnly", - "a"."isOffline" AS "a_isOffline", - "a"."checksum" AS "a_checksum", - "a"."duration" AS "a_duration", - "a"."isVisible" AS "a_isVisible", - "a"."livePhotoVideoId" AS "a_livePhotoVideoId", - "a"."originalFileName" AS "a_originalFileName", - "a"."sidecarPath" AS "a_sidecarPath", - "a"."stackId" AS "a_stackId", - "e"."assetId" AS "e_assetId", - "e"."description" AS "e_description", - "e"."exifImageWidth" AS "e_exifImageWidth", - "e"."exifImageHeight" AS "e_exifImageHeight", - "e"."fileSizeInByte" AS "e_fileSizeInByte", - "e"."orientation" AS "e_orientation", - "e"."dateTimeOriginal" AS "e_dateTimeOriginal", - "e"."modifyDate" AS "e_modifyDate", - "e"."timeZone" AS "e_timeZone", - "e"."latitude" AS "e_latitude", - "e"."longitude" AS "e_longitude", - "e"."projectionType" AS "e_projectionType", - "e"."city" AS "e_city", - "e"."livePhotoCID" AS "e_livePhotoCID", - "e"."autoStackId" AS "e_autoStackId", - "e"."state" AS "e_state", - "e"."country" AS "e_country", - "e"."make" AS "e_make", - "e"."model" AS "e_model", - "e"."lensModel" AS "e_lensModel", - "e"."fNumber" AS "e_fNumber", - "e"."focalLength" AS "e_focalLength", - "e"."iso" AS "e_iso", - "e"."exposureTime" AS "e_exposureTime", - "e"."profileDescription" AS "e_profileDescription", - "e"."colorspace" AS "e_colorspace", - "e"."bitsPerSample" AS "e_bitsPerSample", - "e"."fps" AS "e_fps" -FROM - "assets" "a" - INNER JOIN "smart_search" "s" ON "s"."assetId" = "a"."id" - LEFT JOIN "exif" "e" ON "e"."assetId" = "a"."id" -WHERE - ( - "a"."ownerId" IN ($1) - AND "a"."isArchived" = false - AND "a"."isVisible" = true - AND "a"."fileCreatedAt" < NOW() - ) - AND ("a"."deletedAt" IS NULL) -ORDER BY - "s"."embedding" <= > $2 ASC -LIMIT - 100 -COMMIT - --- SmartInfoRepository.searchFaces -START TRANSACTION -SET - LOCAL vectors.enable_prefilter = on; - -SET - LOCAL vectors.search_mode = vbase; - -SET - LOCAL vectors.hnsw_ef_search = 100; -WITH - "cte" AS ( - SELECT - "faces"."id" AS "id", - "faces"."assetId" AS "assetId", - "faces"."personId" AS "personId", - "faces"."imageWidth" AS "imageWidth", - "faces"."imageHeight" AS "imageHeight", - "faces"."boundingBoxX1" AS "boundingBoxX1", - "faces"."boundingBoxY1" AS "boundingBoxY1", - "faces"."boundingBoxX2" AS "boundingBoxX2", - "faces"."boundingBoxY2" AS "boundingBoxY2", - "faces"."embedding" <= > $1 AS "distance" - FROM - "asset_faces" "faces" - INNER JOIN "assets" "asset" ON "asset"."id" = "faces"."assetId" - AND ("asset"."deletedAt" IS NULL) - WHERE - "asset"."ownerId" IN ($2) - ORDER BY - "faces"."embedding" <= > $1 ASC - LIMIT - 100 - ) -SELECT - res.* -FROM - "cte" "res" -WHERE - res.distance <= $3 -COMMIT diff --git a/server/test/repositories/asset.repository.mock.ts b/server/test/repositories/asset.repository.mock.ts index 9d778883de980..1c98b78c9edd7 100644 --- a/server/test/repositories/asset.repository.mock.ts +++ b/server/test/repositories/asset.repository.mock.ts @@ -32,7 +32,6 @@ export const newAssetRepositoryMock = (): jest.Mocked => { getTimeBuckets: jest.fn(), restoreAll: jest.fn(), softDeleteAll: jest.fn(), - search: jest.fn(), getAssetIdByCity: jest.fn(), getAssetIdByTag: jest.fn(), searchMetadata: jest.fn(), diff --git a/server/test/repositories/index.ts b/server/test/repositories/index.ts index e31a3a1c452b1..90fd1326b4adc 100644 --- a/server/test/repositories/index.ts +++ b/server/test/repositories/index.ts @@ -15,8 +15,8 @@ export * from './metadata.repository.mock'; export * from './move.repository.mock'; export * from './partner.repository.mock'; export * from './person.repository.mock'; +export * from './search.repository.mock'; export * from './shared-link.repository.mock'; -export * from './smart-info.repository.mock'; export * from './storage.repository.mock'; export * from './system-config.repository.mock'; export * from './system-info.repository.mock'; diff --git a/server/test/repositories/search.repository.mock.ts b/server/test/repositories/search.repository.mock.ts new file mode 100644 index 0000000000000..e0bdab269a02c --- /dev/null +++ b/server/test/repositories/search.repository.mock.ts @@ -0,0 +1,11 @@ +import { ISearchRepository } from '@app/domain'; + +export const newSearchRepositoryMock = (): jest.Mocked => { + return { + init: jest.fn(), + searchMetadata: jest.fn(), + searchSmart: jest.fn(), + searchFaces: jest.fn(), + upsert: jest.fn(), + }; +}; diff --git a/server/test/repositories/smart-info.repository.mock.ts b/server/test/repositories/smart-info.repository.mock.ts deleted file mode 100644 index c7bc4f5c56b3b..0000000000000 --- a/server/test/repositories/smart-info.repository.mock.ts +++ /dev/null @@ -1,10 +0,0 @@ -import { ISmartInfoRepository } from '@app/domain'; - -export const newSmartInfoRepositoryMock = (): jest.Mocked => { - return { - init: jest.fn(), - searchCLIP: jest.fn(), - searchFaces: jest.fn(), - upsert: jest.fn(), - }; -}; diff --git a/web/src/lib/components/asset-viewer/intersection-observer.svelte b/web/src/lib/components/asset-viewer/intersection-observer.svelte index f1fbc4aa206e0..df89a2ed7d6c3 100644 --- a/web/src/lib/components/asset-viewer/intersection-observer.svelte +++ b/web/src/lib/components/asset-viewer/intersection-observer.svelte @@ -9,7 +9,7 @@ export let right = 0; export let root: HTMLElement | null = null; - let intersecting = false; + export let intersecting = false; let container: HTMLDivElement; const dispatch = createEventDispatcher<{ hidden: HTMLDivElement; diff --git a/web/src/lib/components/assets/thumbnail/thumbnail.svelte b/web/src/lib/components/assets/thumbnail/thumbnail.svelte index de540b3208a97..1df6a0d9d5765 100644 --- a/web/src/lib/components/assets/thumbnail/thumbnail.svelte +++ b/web/src/lib/components/assets/thumbnail/thumbnail.svelte @@ -37,6 +37,7 @@ export let readonly = false; export let showArchiveIcon = false; export let showStackedIcon = true; + export let intersecting = false; let className = ''; export { className as class }; @@ -85,7 +86,7 @@ }; - +
onMouseEnter()} - on:mouseleave={() => onMouseLeave()} + on:mouseenter={onMouseEnter} + on:mouseleave={onMouseLeave} on:click={thumbnailClickedHandler} on:keydown={thumbnailKeyDownHandler} > diff --git a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte index 669d3ec81f04b..a3bc54c45430b 100644 --- a/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte +++ b/web/src/lib/components/shared-components/gallery-viewer/gallery-viewer.svelte @@ -8,6 +8,10 @@ import { getThumbnailSize } from '$lib/utils/thumbnail-util'; import { assetViewingStore } from '$lib/stores/asset-viewing.store'; import { onDestroy } from 'svelte'; + import { createEventDispatcher } from 'svelte'; + import type { BucketPosition } from '$lib/stores/assets.store'; + + const dispatch = createEventDispatcher<{ intersected: { container: HTMLDivElement; position: BucketPosition } }>(); export let assets: AssetResponseDto[]; export let selectedAssets: Set = new Set(); @@ -18,7 +22,6 @@ let selectedAsset: AssetResponseDto; let currentViewAssetIndex = 0; - let viewWidth: number; $: thumbnailSize = getThumbnailSize(assets.length, viewWidth); @@ -88,7 +91,7 @@ {#if assets.length > 0}
- {#each assets as asset (asset.id)} + {#each assets as asset, i (asset.id)}
(isMultiSelectionMode ? selectAssetHandler(e) : viewAssetHandler(e))} on:select={selectAssetHandler} + on:intersected={(event) => + i === Math.max(1, assets.length - 7) ? dispatch('intersected', event.detail) : undefined} selected={selectedAssets.has(asset)} {showArchiveIcon} /> diff --git a/web/src/lib/components/shared-components/search-bar/search-bar.svelte b/web/src/lib/components/shared-components/search-bar/search-bar.svelte index 8ed9d55fa97a1..8dfba184ad205 100644 --- a/web/src/lib/components/shared-components/search-bar/search-bar.svelte +++ b/web/src/lib/components/shared-components/search-bar/search-bar.svelte @@ -32,6 +32,7 @@ const parameters = new URLSearchParams({ q: searchValue, smart: smartSearch, + take: '100', }); showHistory = false; diff --git a/web/src/routes/(user)/search/+page.svelte b/web/src/routes/(user)/search/+page.svelte index 42f9af0f0ff39..ea068a71377aa 100644 --- a/web/src/routes/(user)/search/+page.svelte +++ b/web/src/routes/(user)/search/+page.svelte @@ -14,7 +14,6 @@ import ControlAppBar from '$lib/components/shared-components/control-app-bar.svelte'; import GalleryViewer from '$lib/components/shared-components/gallery-viewer/gallery-viewer.svelte'; import SearchBar from '$lib/components/shared-components/search-bar/search-bar.svelte'; - import type { AssetResponseDto } from '@api'; import type { PageData } from './$types'; import Icon from '$lib/components/elements/icon.svelte'; import CircleIconButton from '$lib/components/elements/buttons/circle-icon-button.svelte'; @@ -27,15 +26,20 @@ import { preventRaceConditionSearchBar } from '$lib/stores/search.store'; import { shouldIgnoreShortcut } from '$lib/utils/shortcut'; import { mdiArrowLeft, mdiDotsVertical, mdiImageOffOutline, mdiPlus, mdiSelectAll } from '@mdi/js'; + import type { AssetResponseDto, SearchResponseDto } from '@immich/sdk'; + import { authenticate } from '$lib/utils/auth'; + import { api } from '@api'; export let data: PageData; + const MAX_ASSET_COUNT = 5000; let { isViewing: showAssetViewer } = assetViewingStore; // The GalleryViewer pushes it's own history state, which causes weird // behavior for history.back(). To prevent that we store the previous page // manually and navigate back to that. let previousRoute = AppRoute.EXPLORE as string; + $: curPage = data.results?.assets.nextPage; $: albums = data.results?.albums.items; const onKeyboardPress = (event: KeyboardEvent) => handleKeyboardPress(event); @@ -107,6 +111,33 @@ const handleSelectAll = () => { selectedAssets = new Set(searchResultAssets); }; + + export const loadNextPage = async () => { + if (curPage == null || !term || (searchResultAssets && searchResultAssets.length >= MAX_ASSET_COUNT)) { + return; + } + + await authenticate(); + let results: SearchResponseDto | null = null; + $page.url.searchParams.set('page', curPage.toString()); + const res = await api.searchApi.search({}, { params: $page.url.searchParams }); + if (searchResultAssets) { + searchResultAssets.push(...res.data.assets.items); + } else { + searchResultAssets = res.data.assets.items; + } + + const assets = { + ...res.data.assets, + items: searchResultAssets, + }; + results = { + assets, + albums: res.data.albums, + }; + + data.results = results; + };
@@ -164,7 +195,12 @@
{#if searchResultAssets && searchResultAssets.length > 0}
- +
{:else}
diff --git a/web/src/routes/(user)/search/+page.ts b/web/src/routes/(user)/search/+page.ts index b6cbac101af62..75b9df487f1e7 100644 --- a/web/src/routes/(user)/search/+page.ts +++ b/web/src/routes/(user)/search/+page.ts @@ -1,5 +1,5 @@ import { authenticate } from '$lib/utils/auth'; -import { type SearchResponseDto, api } from '@api'; +import { type AssetResponseDto, type SearchResponseDto, api } from '@api'; import type { PageLoad } from './$types'; import { QueryParameter } from '$lib/constants'; @@ -10,8 +10,18 @@ export const load = (async (data) => { url.searchParams.get(QueryParameter.SEARCH_TERM) || url.searchParams.get(QueryParameter.QUERY) || undefined; let results: SearchResponseDto | null = null; if (term) { - const { data } = await api.searchApi.search({}, { params: url.searchParams }); - results = data; + const res = await api.searchApi.search({}, { params: data.url.searchParams }); + let items: AssetResponseDto[] = (data as unknown as { results: SearchResponseDto }).results?.assets.items; + if (items) { + items.push(...res.data.assets.items); + } else { + items = res.data.assets.items; + } + const assets = { ...res.data.assets, items }; + results = { + assets, + albums: res.data.albums, + }; } return {