mirror of
https://github.com/immich-app/immich.git
synced 2026-07-25 14:00:45 +03:00
Compare commits
16 Commits
fix/androi
...
refactor/s
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c2b6e911e8 | ||
|
|
7a5f8d954a | ||
|
|
43b7c669e4 | ||
|
|
0231169135 | ||
|
|
1a13dff3c1 | ||
|
|
3a7bb760d3 | ||
|
|
702a14cd76 | ||
|
|
ae428c20a8 | ||
|
|
55aa05b4c9 | ||
|
|
fc4edf8d06 | ||
|
|
4f5e3cc234 | ||
|
|
2f83615195 | ||
|
|
cbb565b7b7 | ||
|
|
3bd580e37d | ||
|
|
f24a128319 | ||
|
|
36dfc98527 |
@@ -65,6 +65,7 @@ class _DriftPersonNameEditFormState extends ConsumerState<DriftPersonBirthdayEdi
|
||||
child: ClipRRect(
|
||||
borderRadius: const BorderRadius.all(Radius.circular(16.0)),
|
||||
child: ScrollDatePicker(
|
||||
viewType: datePickerColumnOrder(DateFormat.yMd(context.locale.toLanguageTag()).pattern),
|
||||
options: DatePickerOptions(
|
||||
backgroundColor: context.colorScheme.surfaceContainerHigh,
|
||||
itemExtent: 50,
|
||||
@@ -118,3 +119,18 @@ class _DriftPersonNameEditFormState extends ConsumerState<DriftPersonBirthdayEdi
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
List<DatePickerViewType>? datePickerColumnOrder(String? pattern) {
|
||||
if (pattern == null) {
|
||||
return null;
|
||||
}
|
||||
final positions = {
|
||||
DatePickerViewType.year: pattern.indexOf('y'),
|
||||
DatePickerViewType.month: pattern.indexOf('M'),
|
||||
DatePickerViewType.day: pattern.indexOf('d'),
|
||||
};
|
||||
if (positions.values.any((position) => position < 0)) {
|
||||
return null;
|
||||
}
|
||||
return positions.keys.toList()..sort((a, b) => positions[a]!.compareTo(positions[b]!));
|
||||
}
|
||||
|
||||
19
mobile/openapi/README.md
generated
19
mobile/openapi/README.md
generated
@@ -148,6 +148,7 @@ Class | Method | HTTP request | Description
|
||||
*DeprecatedApi* | [**createPartnerDeprecated**](doc//DeprecatedApi.md#createpartnerdeprecated) | **POST** /partners/{id} | Create a partner
|
||||
*DeprecatedApi* | [**getQueuesLegacy**](doc//DeprecatedApi.md#getqueueslegacy) | **GET** /jobs | Retrieve queue counts and status
|
||||
*DeprecatedApi* | [**runQueueCommandLegacy**](doc//DeprecatedApi.md#runqueuecommandlegacy) | **PUT** /jobs/{name} | Run jobs
|
||||
*DeprecatedApi* | [**searchLargeAssets**](doc//DeprecatedApi.md#searchlargeassets) | **POST** /search/large-assets | Search large assets
|
||||
*DeprecatedApi* | [**updateApiKey**](doc//DeprecatedApi.md#updateapikey) | **PUT** /api-keys/{id} | Update an API key
|
||||
*DeprecatedApi* | [**updateAsset**](doc//DeprecatedApi.md#updateasset) | **PUT** /assets/{id} | Update an asset
|
||||
*DeprecatedApi* | [**updateAssets**](doc//DeprecatedApi.md#updateassets) | **PUT** /assets | Update assets
|
||||
@@ -414,6 +415,7 @@ Class | Method | HTTP request | Description
|
||||
- [AudioCodec](doc//AudioCodec.md)
|
||||
- [AuthStatusResponseDto](doc//AuthStatusResponseDto.md)
|
||||
- [AvatarUpdate](doc//AvatarUpdate.md)
|
||||
- [BoolFilter](doc//BoolFilter.md)
|
||||
- [BulkIdErrorReason](doc//BulkIdErrorReason.md)
|
||||
- [BulkIdResponseDto](doc//BulkIdResponseDto.md)
|
||||
- [BulkIdsDto](doc//BulkIdsDto.md)
|
||||
@@ -435,6 +437,8 @@ Class | Method | HTTP request | Description
|
||||
- [DatabaseBackupDeleteDto](doc//DatabaseBackupDeleteDto.md)
|
||||
- [DatabaseBackupDto](doc//DatabaseBackupDto.md)
|
||||
- [DatabaseBackupListResponseDto](doc//DatabaseBackupListResponseDto.md)
|
||||
- [DateFilter](doc//DateFilter.md)
|
||||
- [DateFilterNullable](doc//DateFilterNullable.md)
|
||||
- [DownloadArchiveDto](doc//DownloadArchiveDto.md)
|
||||
- [DownloadArchiveInfo](doc//DownloadArchiveInfo.md)
|
||||
- [DownloadInfoDto](doc//DownloadInfoDto.md)
|
||||
@@ -447,12 +451,17 @@ Class | Method | HTTP request | Description
|
||||
- [DuplicateResponseDto](doc//DuplicateResponseDto.md)
|
||||
- [EmailNotificationsResponse](doc//EmailNotificationsResponse.md)
|
||||
- [EmailNotificationsUpdate](doc//EmailNotificationsUpdate.md)
|
||||
- [EnumFilterAssetType](doc//EnumFilterAssetType.md)
|
||||
- [EnumFilterAssetVisibility](doc//EnumFilterAssetVisibility.md)
|
||||
- [ExifResponseDto](doc//ExifResponseDto.md)
|
||||
- [FaceDto](doc//FaceDto.md)
|
||||
- [FacialRecognitionConfig](doc//FacialRecognitionConfig.md)
|
||||
- [FoldersResponse](doc//FoldersResponse.md)
|
||||
- [FoldersUpdate](doc//FoldersUpdate.md)
|
||||
- [HlsVideoResolution](doc//HlsVideoResolution.md)
|
||||
- [IdFilter](doc//IdFilter.md)
|
||||
- [IdFilterNullable](doc//IdFilterNullable.md)
|
||||
- [IdsFilter](doc//IdsFilter.md)
|
||||
- [ImageFormat](doc//ImageFormat.md)
|
||||
- [IntegrityReport](doc//IntegrityReport.md)
|
||||
- [IntegrityReportResponseDto](doc//IntegrityReportResponseDto.md)
|
||||
@@ -497,6 +506,8 @@ Class | Method | HTTP request | Description
|
||||
- [NotificationType](doc//NotificationType.md)
|
||||
- [NotificationUpdateAllDto](doc//NotificationUpdateAllDto.md)
|
||||
- [NotificationUpdateDto](doc//NotificationUpdateDto.md)
|
||||
- [NumberFilter](doc//NumberFilter.md)
|
||||
- [NumberFilterNullable](doc//NumberFilterNullable.md)
|
||||
- [OAuthAuthorizeResponseDto](doc//OAuthAuthorizeResponseDto.md)
|
||||
- [OAuthCallbackDto](doc//OAuthCallbackDto.md)
|
||||
- [OAuthConfigDto](doc//OAuthConfigDto.md)
|
||||
@@ -559,6 +570,10 @@ Class | Method | HTTP request | Description
|
||||
- [SearchExploreResponseDto](doc//SearchExploreResponseDto.md)
|
||||
- [SearchFacetCountResponseDto](doc//SearchFacetCountResponseDto.md)
|
||||
- [SearchFacetResponseDto](doc//SearchFacetResponseDto.md)
|
||||
- [SearchFilter](doc//SearchFilter.md)
|
||||
- [SearchFilterBranch](doc//SearchFilterBranch.md)
|
||||
- [SearchOrder](doc//SearchOrder.md)
|
||||
- [SearchOrderField](doc//SearchOrderField.md)
|
||||
- [SearchResponseDto](doc//SearchResponseDto.md)
|
||||
- [SearchStatisticsResponseDto](doc//SearchStatisticsResponseDto.md)
|
||||
- [SearchSuggestionType](doc//SearchSuggestionType.md)
|
||||
@@ -593,6 +608,10 @@ Class | Method | HTTP request | Description
|
||||
- [StackUpdateDto](doc//StackUpdateDto.md)
|
||||
- [StatisticsSearchDto](doc//StatisticsSearchDto.md)
|
||||
- [StorageFolder](doc//StorageFolder.md)
|
||||
- [StringFilter](doc//StringFilter.md)
|
||||
- [StringFilterNullable](doc//StringFilterNullable.md)
|
||||
- [StringPatternFilter](doc//StringPatternFilter.md)
|
||||
- [StringSimilarityFilter](doc//StringSimilarityFilter.md)
|
||||
- [SyncAckDeleteDto](doc//SyncAckDeleteDto.md)
|
||||
- [SyncAckDto](doc//SyncAckDto.md)
|
||||
- [SyncAckSetDto](doc//SyncAckSetDto.md)
|
||||
|
||||
18
mobile/openapi/lib/api.dart
generated
18
mobile/openapi/lib/api.dart
generated
@@ -135,6 +135,7 @@ part 'model/asset_visibility.dart';
|
||||
part 'model/audio_codec.dart';
|
||||
part 'model/auth_status_response_dto.dart';
|
||||
part 'model/avatar_update.dart';
|
||||
part 'model/bool_filter.dart';
|
||||
part 'model/bulk_id_error_reason.dart';
|
||||
part 'model/bulk_id_response_dto.dart';
|
||||
part 'model/bulk_ids_dto.dart';
|
||||
@@ -156,6 +157,8 @@ part 'model/database_backup_config.dart';
|
||||
part 'model/database_backup_delete_dto.dart';
|
||||
part 'model/database_backup_dto.dart';
|
||||
part 'model/database_backup_list_response_dto.dart';
|
||||
part 'model/date_filter.dart';
|
||||
part 'model/date_filter_nullable.dart';
|
||||
part 'model/download_archive_dto.dart';
|
||||
part 'model/download_archive_info.dart';
|
||||
part 'model/download_info_dto.dart';
|
||||
@@ -168,12 +171,17 @@ part 'model/duplicate_resolve_group_dto.dart';
|
||||
part 'model/duplicate_response_dto.dart';
|
||||
part 'model/email_notifications_response.dart';
|
||||
part 'model/email_notifications_update.dart';
|
||||
part 'model/enum_filter_asset_type.dart';
|
||||
part 'model/enum_filter_asset_visibility.dart';
|
||||
part 'model/exif_response_dto.dart';
|
||||
part 'model/face_dto.dart';
|
||||
part 'model/facial_recognition_config.dart';
|
||||
part 'model/folders_response.dart';
|
||||
part 'model/folders_update.dart';
|
||||
part 'model/hls_video_resolution.dart';
|
||||
part 'model/id_filter.dart';
|
||||
part 'model/id_filter_nullable.dart';
|
||||
part 'model/ids_filter.dart';
|
||||
part 'model/image_format.dart';
|
||||
part 'model/integrity_report.dart';
|
||||
part 'model/integrity_report_response_dto.dart';
|
||||
@@ -218,6 +226,8 @@ part 'model/notification_level.dart';
|
||||
part 'model/notification_type.dart';
|
||||
part 'model/notification_update_all_dto.dart';
|
||||
part 'model/notification_update_dto.dart';
|
||||
part 'model/number_filter.dart';
|
||||
part 'model/number_filter_nullable.dart';
|
||||
part 'model/o_auth_authorize_response_dto.dart';
|
||||
part 'model/o_auth_callback_dto.dart';
|
||||
part 'model/o_auth_config_dto.dart';
|
||||
@@ -280,6 +290,10 @@ part 'model/search_explore_item.dart';
|
||||
part 'model/search_explore_response_dto.dart';
|
||||
part 'model/search_facet_count_response_dto.dart';
|
||||
part 'model/search_facet_response_dto.dart';
|
||||
part 'model/search_filter.dart';
|
||||
part 'model/search_filter_branch.dart';
|
||||
part 'model/search_order.dart';
|
||||
part 'model/search_order_field.dart';
|
||||
part 'model/search_response_dto.dart';
|
||||
part 'model/search_statistics_response_dto.dart';
|
||||
part 'model/search_suggestion_type.dart';
|
||||
@@ -314,6 +328,10 @@ part 'model/stack_response_dto.dart';
|
||||
part 'model/stack_update_dto.dart';
|
||||
part 'model/statistics_search_dto.dart';
|
||||
part 'model/storage_folder.dart';
|
||||
part 'model/string_filter.dart';
|
||||
part 'model/string_filter_nullable.dart';
|
||||
part 'model/string_pattern_filter.dart';
|
||||
part 'model/string_similarity_filter.dart';
|
||||
part 'model/sync_ack_delete_dto.dart';
|
||||
part 'model/sync_ack_dto.dart';
|
||||
part 'model/sync_ack_set_dto.dart';
|
||||
|
||||
332
mobile/openapi/lib/api/deprecated_api.dart
generated
332
mobile/openapi/lib/api/deprecated_api.dart
generated
@@ -185,6 +185,338 @@ class DeprecatedApi {
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Search large assets
|
||||
///
|
||||
/// Search for assets that are considered large based on specified criteria.
|
||||
///
|
||||
/// Note: This method returns the HTTP [Response].
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [List<String>] albumIds:
|
||||
/// Filter by album IDs
|
||||
///
|
||||
/// * [String] city:
|
||||
/// Filter by city name
|
||||
///
|
||||
/// * [String] country:
|
||||
/// Filter by country name
|
||||
///
|
||||
/// * [DateTime] createdAfter:
|
||||
/// Filter by creation date (after)
|
||||
///
|
||||
/// * [DateTime] createdBefore:
|
||||
/// Filter by creation date (before)
|
||||
///
|
||||
/// * [bool] isEncoded:
|
||||
/// Filter by encoded status
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] isMotion:
|
||||
/// Filter by motion photo status
|
||||
///
|
||||
/// * [bool] isNotInAlbum:
|
||||
/// Filter assets not in any album
|
||||
///
|
||||
/// * [bool] isOffline:
|
||||
/// Filter by offline status
|
||||
///
|
||||
/// * [String] lensModel:
|
||||
/// Filter by lens model
|
||||
///
|
||||
/// * [String] libraryId:
|
||||
/// Library ID to filter by
|
||||
///
|
||||
/// * [String] make:
|
||||
/// Filter by camera make
|
||||
///
|
||||
/// * [int] minFileSize:
|
||||
/// Minimum file size in bytes
|
||||
///
|
||||
/// * [String] model:
|
||||
/// Filter by camera model
|
||||
///
|
||||
/// * [String] ocr:
|
||||
/// Filter by OCR text content
|
||||
///
|
||||
/// * [List<String>] personIds:
|
||||
/// Filter by person IDs
|
||||
///
|
||||
/// * [int] rating:
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of results to return
|
||||
///
|
||||
/// * [String] state:
|
||||
/// Filter by state/province name
|
||||
///
|
||||
/// * [List<String>] tagIds:
|
||||
/// Filter by tag IDs
|
||||
///
|
||||
/// * [DateTime] takenAfter:
|
||||
/// Filter by taken date (after)
|
||||
///
|
||||
/// * [DateTime] takenBefore:
|
||||
/// Filter by taken date (before)
|
||||
///
|
||||
/// * [DateTime] trashedAfter:
|
||||
/// Filter by trash date (after)
|
||||
///
|
||||
/// * [DateTime] trashedBefore:
|
||||
/// Filter by trash date (before)
|
||||
///
|
||||
/// * [AssetTypeEnum] type:
|
||||
///
|
||||
/// * [DateTime] updatedAfter:
|
||||
/// Filter by update date (after)
|
||||
///
|
||||
/// * [DateTime] updatedBefore:
|
||||
/// Filter by update date (before)
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
/// Include deleted assets
|
||||
///
|
||||
/// * [bool] withExif:
|
||||
/// Include EXIF data in response
|
||||
Future<Response> searchLargeAssetsWithHttpInfo({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, Future<void>? abortTrigger, }) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final apiPath = r'/search/large-assets';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
if (albumIds != null) {
|
||||
queryParams.addAll(_queryParams('multi', 'albumIds', albumIds));
|
||||
}
|
||||
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 (isEncoded != null) {
|
||||
queryParams.addAll(_queryParams('', 'isEncoded', isEncoded));
|
||||
}
|
||||
if (isFavorite != null) {
|
||||
queryParams.addAll(_queryParams('', 'isFavorite', isFavorite));
|
||||
}
|
||||
if (isMotion != null) {
|
||||
queryParams.addAll(_queryParams('', 'isMotion', isMotion));
|
||||
}
|
||||
if (isNotInAlbum != null) {
|
||||
queryParams.addAll(_queryParams('', 'isNotInAlbum', isNotInAlbum));
|
||||
}
|
||||
if (isOffline != null) {
|
||||
queryParams.addAll(_queryParams('', 'isOffline', isOffline));
|
||||
}
|
||||
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 (minFileSize != null) {
|
||||
queryParams.addAll(_queryParams('', 'minFileSize', minFileSize));
|
||||
}
|
||||
if (model != null) {
|
||||
queryParams.addAll(_queryParams('', 'model', model));
|
||||
}
|
||||
if (ocr != null) {
|
||||
queryParams.addAll(_queryParams('', 'ocr', ocr));
|
||||
}
|
||||
if (personIds != null) {
|
||||
queryParams.addAll(_queryParams('multi', 'personIds', personIds));
|
||||
}
|
||||
if (rating != null) {
|
||||
queryParams.addAll(_queryParams('', 'rating', rating));
|
||||
}
|
||||
if (size != null) {
|
||||
queryParams.addAll(_queryParams('', 'size', size));
|
||||
}
|
||||
if (state != null) {
|
||||
queryParams.addAll(_queryParams('', 'state', state));
|
||||
}
|
||||
if (tagIds != null) {
|
||||
queryParams.addAll(_queryParams('multi', 'tagIds', tagIds));
|
||||
}
|
||||
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 (visibility != null) {
|
||||
queryParams.addAll(_queryParams('', 'visibility', visibility));
|
||||
}
|
||||
if (withDeleted != null) {
|
||||
queryParams.addAll(_queryParams('', 'withDeleted', withDeleted));
|
||||
}
|
||||
if (withExif != null) {
|
||||
queryParams.addAll(_queryParams('', 'withExif', withExif));
|
||||
}
|
||||
|
||||
const contentTypes = <String>[];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
apiPath,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
abortTrigger: abortTrigger,
|
||||
);
|
||||
}
|
||||
|
||||
/// Search large assets
|
||||
///
|
||||
/// Search for assets that are considered large based on specified criteria.
|
||||
///
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [List<String>] albumIds:
|
||||
/// Filter by album IDs
|
||||
///
|
||||
/// * [String] city:
|
||||
/// Filter by city name
|
||||
///
|
||||
/// * [String] country:
|
||||
/// Filter by country name
|
||||
///
|
||||
/// * [DateTime] createdAfter:
|
||||
/// Filter by creation date (after)
|
||||
///
|
||||
/// * [DateTime] createdBefore:
|
||||
/// Filter by creation date (before)
|
||||
///
|
||||
/// * [bool] isEncoded:
|
||||
/// Filter by encoded status
|
||||
///
|
||||
/// * [bool] isFavorite:
|
||||
/// Filter by favorite status
|
||||
///
|
||||
/// * [bool] isMotion:
|
||||
/// Filter by motion photo status
|
||||
///
|
||||
/// * [bool] isNotInAlbum:
|
||||
/// Filter assets not in any album
|
||||
///
|
||||
/// * [bool] isOffline:
|
||||
/// Filter by offline status
|
||||
///
|
||||
/// * [String] lensModel:
|
||||
/// Filter by lens model
|
||||
///
|
||||
/// * [String] libraryId:
|
||||
/// Library ID to filter by
|
||||
///
|
||||
/// * [String] make:
|
||||
/// Filter by camera make
|
||||
///
|
||||
/// * [int] minFileSize:
|
||||
/// Minimum file size in bytes
|
||||
///
|
||||
/// * [String] model:
|
||||
/// Filter by camera model
|
||||
///
|
||||
/// * [String] ocr:
|
||||
/// Filter by OCR text content
|
||||
///
|
||||
/// * [List<String>] personIds:
|
||||
/// Filter by person IDs
|
||||
///
|
||||
/// * [int] rating:
|
||||
/// Filter by rating [1-5], or null for unrated
|
||||
///
|
||||
/// * [int] size:
|
||||
/// Number of results to return
|
||||
///
|
||||
/// * [String] state:
|
||||
/// Filter by state/province name
|
||||
///
|
||||
/// * [List<String>] tagIds:
|
||||
/// Filter by tag IDs
|
||||
///
|
||||
/// * [DateTime] takenAfter:
|
||||
/// Filter by taken date (after)
|
||||
///
|
||||
/// * [DateTime] takenBefore:
|
||||
/// Filter by taken date (before)
|
||||
///
|
||||
/// * [DateTime] trashedAfter:
|
||||
/// Filter by trash date (after)
|
||||
///
|
||||
/// * [DateTime] trashedBefore:
|
||||
/// Filter by trash date (before)
|
||||
///
|
||||
/// * [AssetTypeEnum] type:
|
||||
///
|
||||
/// * [DateTime] updatedAfter:
|
||||
/// Filter by update date (after)
|
||||
///
|
||||
/// * [DateTime] updatedBefore:
|
||||
/// Filter by update date (before)
|
||||
///
|
||||
/// * [AssetVisibility] visibility:
|
||||
///
|
||||
/// * [bool] withDeleted:
|
||||
/// Include deleted assets
|
||||
///
|
||||
/// * [bool] withExif:
|
||||
/// Include EXIF data in response
|
||||
Future<List<AssetResponseDto>?> searchLargeAssets({ List<String>? albumIds, String? city, String? country, DateTime? createdAfter, DateTime? createdBefore, bool? isEncoded, bool? isFavorite, bool? isMotion, bool? isNotInAlbum, bool? isOffline, String? lensModel, String? libraryId, String? make, int? minFileSize, String? model, String? ocr, List<String>? personIds, int? rating, int? size, String? state, List<String>? tagIds, DateTime? takenAfter, DateTime? takenBefore, DateTime? trashedAfter, DateTime? trashedBefore, AssetTypeEnum? type, DateTime? updatedAfter, DateTime? updatedBefore, AssetVisibility? visibility, bool? withDeleted, bool? withExif, Future<void>? abortTrigger, }) async {
|
||||
final response = await searchLargeAssetsWithHttpInfo(albumIds: albumIds, city: city, country: country, createdAfter: createdAfter, createdBefore: createdBefore, isEncoded: isEncoded, isFavorite: isFavorite, isMotion: isMotion, isNotInAlbum: isNotInAlbum, isOffline: isOffline, lensModel: lensModel, libraryId: libraryId, make: make, minFileSize: minFileSize, model: model, ocr: ocr, personIds: personIds, rating: rating, size: size, state: state, tagIds: tagIds, takenAfter: takenAfter, takenBefore: takenBefore, trashedAfter: trashedAfter, trashedBefore: trashedBefore, type: type, updatedAfter: updatedAfter, updatedBefore: updatedBefore, visibility: visibility, withDeleted: withDeleted, withExif: withExif, abortTrigger: abortTrigger,);
|
||||
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) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
|
||||
.cast<AssetResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Update an API key
|
||||
///
|
||||
/// Updates the name and permissions of an API key by its ID. The current user must own this API key.
|
||||
|
||||
36
mobile/openapi/lib/api_client.dart
generated
36
mobile/openapi/lib/api_client.dart
generated
@@ -315,6 +315,8 @@ class ApiClient {
|
||||
return AuthStatusResponseDto.fromJson(value);
|
||||
case 'AvatarUpdate':
|
||||
return AvatarUpdate.fromJson(value);
|
||||
case 'BoolFilter':
|
||||
return BoolFilter.fromJson(value);
|
||||
case 'BulkIdErrorReason':
|
||||
return BulkIdErrorReasonTypeTransformer().decode(value);
|
||||
case 'BulkIdResponseDto':
|
||||
@@ -357,6 +359,10 @@ class ApiClient {
|
||||
return DatabaseBackupDto.fromJson(value);
|
||||
case 'DatabaseBackupListResponseDto':
|
||||
return DatabaseBackupListResponseDto.fromJson(value);
|
||||
case 'DateFilter':
|
||||
return DateFilter.fromJson(value);
|
||||
case 'DateFilterNullable':
|
||||
return DateFilterNullable.fromJson(value);
|
||||
case 'DownloadArchiveDto':
|
||||
return DownloadArchiveDto.fromJson(value);
|
||||
case 'DownloadArchiveInfo':
|
||||
@@ -381,6 +387,10 @@ class ApiClient {
|
||||
return EmailNotificationsResponse.fromJson(value);
|
||||
case 'EmailNotificationsUpdate':
|
||||
return EmailNotificationsUpdate.fromJson(value);
|
||||
case 'EnumFilterAssetType':
|
||||
return EnumFilterAssetType.fromJson(value);
|
||||
case 'EnumFilterAssetVisibility':
|
||||
return EnumFilterAssetVisibility.fromJson(value);
|
||||
case 'ExifResponseDto':
|
||||
return ExifResponseDto.fromJson(value);
|
||||
case 'FaceDto':
|
||||
@@ -393,6 +403,12 @@ class ApiClient {
|
||||
return FoldersUpdate.fromJson(value);
|
||||
case 'HlsVideoResolution':
|
||||
return HlsVideoResolutionTypeTransformer().decode(value);
|
||||
case 'IdFilter':
|
||||
return IdFilter.fromJson(value);
|
||||
case 'IdFilterNullable':
|
||||
return IdFilterNullable.fromJson(value);
|
||||
case 'IdsFilter':
|
||||
return IdsFilter.fromJson(value);
|
||||
case 'ImageFormat':
|
||||
return ImageFormatTypeTransformer().decode(value);
|
||||
case 'IntegrityReport':
|
||||
@@ -481,6 +497,10 @@ class ApiClient {
|
||||
return NotificationUpdateAllDto.fromJson(value);
|
||||
case 'NotificationUpdateDto':
|
||||
return NotificationUpdateDto.fromJson(value);
|
||||
case 'NumberFilter':
|
||||
return NumberFilter.fromJson(value);
|
||||
case 'NumberFilterNullable':
|
||||
return NumberFilterNullable.fromJson(value);
|
||||
case 'OAuthAuthorizeResponseDto':
|
||||
return OAuthAuthorizeResponseDto.fromJson(value);
|
||||
case 'OAuthCallbackDto':
|
||||
@@ -605,6 +625,14 @@ class ApiClient {
|
||||
return SearchFacetCountResponseDto.fromJson(value);
|
||||
case 'SearchFacetResponseDto':
|
||||
return SearchFacetResponseDto.fromJson(value);
|
||||
case 'SearchFilter':
|
||||
return SearchFilter.fromJson(value);
|
||||
case 'SearchFilterBranch':
|
||||
return SearchFilterBranch.fromJson(value);
|
||||
case 'SearchOrder':
|
||||
return SearchOrder.fromJson(value);
|
||||
case 'SearchOrderField':
|
||||
return SearchOrderFieldTypeTransformer().decode(value);
|
||||
case 'SearchResponseDto':
|
||||
return SearchResponseDto.fromJson(value);
|
||||
case 'SearchStatisticsResponseDto':
|
||||
@@ -673,6 +701,14 @@ class ApiClient {
|
||||
return StatisticsSearchDto.fromJson(value);
|
||||
case 'StorageFolder':
|
||||
return StorageFolderTypeTransformer().decode(value);
|
||||
case 'StringFilter':
|
||||
return StringFilter.fromJson(value);
|
||||
case 'StringFilterNullable':
|
||||
return StringFilterNullable.fromJson(value);
|
||||
case 'StringPatternFilter':
|
||||
return StringPatternFilter.fromJson(value);
|
||||
case 'StringSimilarityFilter':
|
||||
return StringSimilarityFilter.fromJson(value);
|
||||
case 'SyncAckDeleteDto':
|
||||
return SyncAckDeleteDto.fromJson(value);
|
||||
case 'SyncAckDto':
|
||||
|
||||
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
@@ -172,6 +172,9 @@ String parameterToString(dynamic value) {
|
||||
if (value is ReleaseType) {
|
||||
return ReleaseTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is SearchOrderField) {
|
||||
return SearchOrderFieldTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is SearchSuggestionType) {
|
||||
return SearchSuggestionTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
||||
99
mobile/openapi/lib/model/bool_filter.dart
generated
Normal file
99
mobile/openapi/lib/model/bool_filter.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class BoolFilter {
|
||||
/// Returns a new [BoolFilter] instance.
|
||||
BoolFilter({
|
||||
required this.eq,
|
||||
});
|
||||
|
||||
bool eq;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is BoolFilter &&
|
||||
other.eq == eq;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'BoolFilter[eq=$eq]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'eq'] = this.eq;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [BoolFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static BoolFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "BoolFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return BoolFilter(
|
||||
eq: mapValueOfType<bool>(json, r'eq')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<BoolFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <BoolFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = BoolFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, BoolFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, BoolFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = BoolFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of BoolFilter-objects as value to a dart map
|
||||
static Map<String, List<BoolFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<BoolFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = BoolFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'eq',
|
||||
};
|
||||
}
|
||||
|
||||
199
mobile/openapi/lib/model/date_filter.dart
generated
Normal file
199
mobile/openapi/lib/model/date_filter.dart
generated
Normal file
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class DateFilter {
|
||||
/// Returns a new [DateFilter] instance.
|
||||
DateFilter({
|
||||
this.eq = const Optional.absent(),
|
||||
this.gt = const Optional.absent(),
|
||||
this.gte = const Optional.absent(),
|
||||
this.lt = const Optional.absent(),
|
||||
this.lte = const Optional.absent(),
|
||||
this.ne = const Optional.absent(),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> eq;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> gt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> gte;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> lt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> lte;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> ne;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is DateFilter &&
|
||||
other.eq == eq &&
|
||||
other.gt == gt &&
|
||||
other.gte == gte &&
|
||||
other.lt == lt &&
|
||||
other.lte == lte &&
|
||||
other.ne == ne;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(gt == null ? 0 : gt!.hashCode) +
|
||||
(gte == null ? 0 : gte!.hashCode) +
|
||||
(lt == null ? 0 : lt!.hashCode) +
|
||||
(lte == null ? 0 : lte!.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'DateFilter[eq=$eq, gt=$gt, gte=$gte, lt=$lt, lte=$lte, ne=$ne]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.gt.isPresent) {
|
||||
final value = this.gt.value;
|
||||
json[r'gt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.gte.isPresent) {
|
||||
final value = this.gte.value;
|
||||
json[r'gte'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.lt.isPresent) {
|
||||
final value = this.lt.value;
|
||||
json[r'lt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.lte.isPresent) {
|
||||
final value = this.lte.value;
|
||||
json[r'lte'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [DateFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static DateFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "DateFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return DateFilter(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(mapDateTime(json, r'eq', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
gt: json.containsKey(r'gt') ? Optional.present(mapDateTime(json, r'gt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
gte: json.containsKey(r'gte') ? Optional.present(mapDateTime(json, r'gte', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
lt: json.containsKey(r'lt') ? Optional.present(mapDateTime(json, r'lt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
lte: json.containsKey(r'lte') ? Optional.present(mapDateTime(json, r'lte', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(mapDateTime(json, r'ne', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<DateFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <DateFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = DateFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, DateFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, DateFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = DateFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of DateFilter-objects as value to a dart map
|
||||
static Map<String, List<DateFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<DateFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = DateFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
187
mobile/openapi/lib/model/date_filter_nullable.dart
generated
Normal file
187
mobile/openapi/lib/model/date_filter_nullable.dart
generated
Normal file
@@ -0,0 +1,187 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class DateFilterNullable {
|
||||
/// Returns a new [DateFilterNullable] instance.
|
||||
DateFilterNullable({
|
||||
this.eq = const Optional.absent(),
|
||||
this.gt = const Optional.absent(),
|
||||
this.gte = const Optional.absent(),
|
||||
this.lt = const Optional.absent(),
|
||||
this.lte = const Optional.absent(),
|
||||
this.ne = const Optional.absent(),
|
||||
});
|
||||
|
||||
Optional<DateTime?> eq;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> gt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> gte;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> lt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateTime?> lte;
|
||||
|
||||
Optional<DateTime?> ne;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is DateFilterNullable &&
|
||||
other.eq == eq &&
|
||||
other.gt == gt &&
|
||||
other.gte == gte &&
|
||||
other.lt == lt &&
|
||||
other.lte == lte &&
|
||||
other.ne == ne;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(gt == null ? 0 : gt!.hashCode) +
|
||||
(gte == null ? 0 : gte!.hashCode) +
|
||||
(lt == null ? 0 : lt!.hashCode) +
|
||||
(lte == null ? 0 : lte!.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'DateFilterNullable[eq=$eq, gt=$gt, gte=$gte, lt=$lt, lte=$lte, ne=$ne]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.gt.isPresent) {
|
||||
final value = this.gt.value;
|
||||
json[r'gt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.gte.isPresent) {
|
||||
final value = this.gte.value;
|
||||
json[r'gte'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.lt.isPresent) {
|
||||
final value = this.lt.value;
|
||||
json[r'lt'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.lte.isPresent) {
|
||||
final value = this.lte.value;
|
||||
json[r'lte'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value == null ? null : (_isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [DateFilterNullable] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static DateFilterNullable? fromJson(dynamic value) {
|
||||
upgradeDto(value, "DateFilterNullable");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return DateFilterNullable(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(mapDateTime(json, r'eq', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
gt: json.containsKey(r'gt') ? Optional.present(mapDateTime(json, r'gt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
gte: json.containsKey(r'gte') ? Optional.present(mapDateTime(json, r'gte', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
lt: json.containsKey(r'lt') ? Optional.present(mapDateTime(json, r'lt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
lte: json.containsKey(r'lte') ? Optional.present(mapDateTime(json, r'lte', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(mapDateTime(json, r'ne', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<DateFilterNullable> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <DateFilterNullable>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = DateFilterNullable.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, DateFilterNullable> mapFromJson(dynamic json) {
|
||||
final map = <String, DateFilterNullable>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = DateFilterNullable.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of DateFilterNullable-objects as value to a dart map
|
||||
static Map<String, List<DateFilterNullable>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<DateFilterNullable>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = DateFilterNullable.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
143
mobile/openapi/lib/model/enum_filter_asset_type.dart
generated
Normal file
143
mobile/openapi/lib/model/enum_filter_asset_type.dart
generated
Normal file
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EnumFilterAssetType {
|
||||
/// Returns a new [EnumFilterAssetType] instance.
|
||||
EnumFilterAssetType({
|
||||
this.eq = const Optional.absent(),
|
||||
this.in_ = const Optional.present(const []),
|
||||
this.ne = const Optional.absent(),
|
||||
this.notIn = const Optional.present(const []),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<AssetTypeEnum?> eq;
|
||||
|
||||
Optional<List<AssetTypeEnum>?> in_;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<AssetTypeEnum?> ne;
|
||||
|
||||
Optional<List<AssetTypeEnum>?> notIn;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EnumFilterAssetType &&
|
||||
other.eq == eq &&
|
||||
_deepEquality.equals(other.in_, in_) &&
|
||||
other.ne == ne &&
|
||||
_deepEquality.equals(other.notIn, notIn);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(in_.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode) +
|
||||
(notIn.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EnumFilterAssetType[eq=$eq, in_=$in_, ne=$ne, notIn=$notIn]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.in_.isPresent) {
|
||||
final value = this.in_.value;
|
||||
json[r'in'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
if (this.notIn.isPresent) {
|
||||
final value = this.notIn.value;
|
||||
json[r'notIn'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EnumFilterAssetType] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EnumFilterAssetType? fromJson(dynamic value) {
|
||||
upgradeDto(value, "EnumFilterAssetType");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EnumFilterAssetType(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(AssetTypeEnum.fromJson(json[r'eq'])) : const Optional.absent(),
|
||||
in_: json.containsKey(r'in') ? Optional.present(AssetTypeEnum.listFromJson(json[r'in'])) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(AssetTypeEnum.fromJson(json[r'ne'])) : const Optional.absent(),
|
||||
notIn: json.containsKey(r'notIn') ? Optional.present(AssetTypeEnum.listFromJson(json[r'notIn'])) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EnumFilterAssetType> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EnumFilterAssetType>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EnumFilterAssetType.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EnumFilterAssetType> mapFromJson(dynamic json) {
|
||||
final map = <String, EnumFilterAssetType>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EnumFilterAssetType.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EnumFilterAssetType-objects as value to a dart map
|
||||
static Map<String, List<EnumFilterAssetType>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EnumFilterAssetType>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EnumFilterAssetType.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
143
mobile/openapi/lib/model/enum_filter_asset_visibility.dart
generated
Normal file
143
mobile/openapi/lib/model/enum_filter_asset_visibility.dart
generated
Normal file
@@ -0,0 +1,143 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class EnumFilterAssetVisibility {
|
||||
/// Returns a new [EnumFilterAssetVisibility] instance.
|
||||
EnumFilterAssetVisibility({
|
||||
this.eq = const Optional.absent(),
|
||||
this.in_ = const Optional.present(const []),
|
||||
this.ne = const Optional.absent(),
|
||||
this.notIn = const Optional.present(const []),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<AssetVisibility?> eq;
|
||||
|
||||
Optional<List<AssetVisibility>?> in_;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<AssetVisibility?> ne;
|
||||
|
||||
Optional<List<AssetVisibility>?> notIn;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is EnumFilterAssetVisibility &&
|
||||
other.eq == eq &&
|
||||
_deepEquality.equals(other.in_, in_) &&
|
||||
other.ne == ne &&
|
||||
_deepEquality.equals(other.notIn, notIn);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(in_.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode) +
|
||||
(notIn.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'EnumFilterAssetVisibility[eq=$eq, in_=$in_, ne=$ne, notIn=$notIn]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.in_.isPresent) {
|
||||
final value = this.in_.value;
|
||||
json[r'in'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
if (this.notIn.isPresent) {
|
||||
final value = this.notIn.value;
|
||||
json[r'notIn'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [EnumFilterAssetVisibility] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static EnumFilterAssetVisibility? fromJson(dynamic value) {
|
||||
upgradeDto(value, "EnumFilterAssetVisibility");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return EnumFilterAssetVisibility(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(AssetVisibility.fromJson(json[r'eq'])) : const Optional.absent(),
|
||||
in_: json.containsKey(r'in') ? Optional.present(AssetVisibility.listFromJson(json[r'in'])) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(AssetVisibility.fromJson(json[r'ne'])) : const Optional.absent(),
|
||||
notIn: json.containsKey(r'notIn') ? Optional.present(AssetVisibility.listFromJson(json[r'notIn'])) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<EnumFilterAssetVisibility> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <EnumFilterAssetVisibility>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = EnumFilterAssetVisibility.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, EnumFilterAssetVisibility> mapFromJson(dynamic json) {
|
||||
final map = <String, EnumFilterAssetVisibility>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = EnumFilterAssetVisibility.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of EnumFilterAssetVisibility-objects as value to a dart map
|
||||
static Map<String, List<EnumFilterAssetVisibility>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<EnumFilterAssetVisibility>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = EnumFilterAssetVisibility.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
123
mobile/openapi/lib/model/id_filter.dart
generated
Normal file
123
mobile/openapi/lib/model/id_filter.dart
generated
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class IdFilter {
|
||||
/// Returns a new [IdFilter] instance.
|
||||
IdFilter({
|
||||
this.eq = const Optional.absent(),
|
||||
this.ne = const Optional.absent(),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> eq;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> ne;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is IdFilter &&
|
||||
other.eq == eq &&
|
||||
other.ne == ne;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'IdFilter[eq=$eq, ne=$ne]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [IdFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static IdFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "IdFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return IdFilter(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(mapValueOfType<String>(json, r'eq')) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(mapValueOfType<String>(json, r'ne')) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<IdFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <IdFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = IdFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, IdFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, IdFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = IdFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of IdFilter-objects as value to a dart map
|
||||
static Map<String, List<IdFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<IdFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = IdFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
111
mobile/openapi/lib/model/id_filter_nullable.dart
generated
Normal file
111
mobile/openapi/lib/model/id_filter_nullable.dart
generated
Normal file
@@ -0,0 +1,111 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class IdFilterNullable {
|
||||
/// Returns a new [IdFilterNullable] instance.
|
||||
IdFilterNullable({
|
||||
this.eq = const Optional.absent(),
|
||||
this.ne = const Optional.absent(),
|
||||
});
|
||||
|
||||
Optional<String?> eq;
|
||||
|
||||
Optional<String?> ne;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is IdFilterNullable &&
|
||||
other.eq == eq &&
|
||||
other.ne == ne;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'IdFilterNullable[eq=$eq, ne=$ne]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [IdFilterNullable] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static IdFilterNullable? fromJson(dynamic value) {
|
||||
upgradeDto(value, "IdFilterNullable");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return IdFilterNullable(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(mapValueOfType<String>(json, r'eq')) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(mapValueOfType<String>(json, r'ne')) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<IdFilterNullable> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <IdFilterNullable>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = IdFilterNullable.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, IdFilterNullable> mapFromJson(dynamic json) {
|
||||
final map = <String, IdFilterNullable>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = IdFilterNullable.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of IdFilterNullable-objects as value to a dart map
|
||||
static Map<String, List<IdFilterNullable>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<IdFilterNullable>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = IdFilterNullable.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
127
mobile/openapi/lib/model/ids_filter.dart
generated
Normal file
127
mobile/openapi/lib/model/ids_filter.dart
generated
Normal file
@@ -0,0 +1,127 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class IdsFilter {
|
||||
/// Returns a new [IdsFilter] instance.
|
||||
IdsFilter({
|
||||
this.all = const Optional.present(const []),
|
||||
this.any = const Optional.present(const []),
|
||||
this.none = const Optional.present(const []),
|
||||
});
|
||||
|
||||
Optional<List<String>?> all;
|
||||
|
||||
Optional<List<String>?> any;
|
||||
|
||||
Optional<List<String>?> none;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is IdsFilter &&
|
||||
_deepEquality.equals(other.all, all) &&
|
||||
_deepEquality.equals(other.any, any) &&
|
||||
_deepEquality.equals(other.none, none);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(all.hashCode) +
|
||||
(any.hashCode) +
|
||||
(none.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'IdsFilter[all=$all, any=$any, none=$none]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.all.isPresent) {
|
||||
final value = this.all.value;
|
||||
json[r'all'] = value;
|
||||
}
|
||||
if (this.any.isPresent) {
|
||||
final value = this.any.value;
|
||||
json[r'any'] = value;
|
||||
}
|
||||
if (this.none.isPresent) {
|
||||
final value = this.none.value;
|
||||
json[r'none'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [IdsFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static IdsFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "IdsFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return IdsFilter(
|
||||
all: json.containsKey(r'all') ? Optional.present(json[r'all'] is Iterable
|
||||
? (json[r'all'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
any: json.containsKey(r'any') ? Optional.present(json[r'any'] is Iterable
|
||||
? (json[r'any'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
none: json.containsKey(r'none') ? Optional.present(json[r'none'] is Iterable
|
||||
? (json[r'none'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<IdsFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <IdsFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = IdsFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, IdsFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, IdsFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = IdsFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of IdsFilter-objects as value to a dart map
|
||||
static Map<String, List<IdsFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<IdsFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = IdsFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
51
mobile/openapi/lib/model/metadata_search_dto.dart
generated
51
mobile/openapi/lib/model/metadata_search_dto.dart
generated
@@ -19,8 +19,10 @@ class MetadataSearchDto {
|
||||
this.country = const Optional.absent(),
|
||||
this.createdAfter = const Optional.absent(),
|
||||
this.createdBefore = const Optional.absent(),
|
||||
this.cursor = const Optional.absent(),
|
||||
this.description = const Optional.absent(),
|
||||
this.encodedVideoPath = const Optional.absent(),
|
||||
this.filter = const Optional.absent(),
|
||||
this.id = const Optional.absent(),
|
||||
this.isEncoded = const Optional.absent(),
|
||||
this.isFavorite = const Optional.absent(),
|
||||
@@ -33,6 +35,7 @@ class MetadataSearchDto {
|
||||
this.model = const Optional.absent(),
|
||||
this.ocr = const Optional.absent(),
|
||||
this.order = const Optional.absent(),
|
||||
this.orderBy = const Optional.absent(),
|
||||
this.originalFileName = const Optional.absent(),
|
||||
this.originalPath = const Optional.absent(),
|
||||
this.page = const Optional.absent(),
|
||||
@@ -93,6 +96,15 @@ class MetadataSearchDto {
|
||||
///
|
||||
Optional<DateTime?> createdBefore;
|
||||
|
||||
/// Cursor for the next page of results
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> cursor;
|
||||
|
||||
/// Filter by description text
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -111,6 +123,14 @@ class MetadataSearchDto {
|
||||
///
|
||||
Optional<String?> encodedVideoPath;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<SearchFilter?> filter;
|
||||
|
||||
/// Filter by asset ID
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -194,6 +214,14 @@ class MetadataSearchDto {
|
||||
///
|
||||
Optional<AssetOrder?> order;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<SearchOrder?> orderBy;
|
||||
|
||||
/// Filter by original file name
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -383,8 +411,10 @@ class MetadataSearchDto {
|
||||
other.country == country &&
|
||||
other.createdAfter == createdAfter &&
|
||||
other.createdBefore == createdBefore &&
|
||||
other.cursor == cursor &&
|
||||
other.description == description &&
|
||||
other.encodedVideoPath == encodedVideoPath &&
|
||||
other.filter == filter &&
|
||||
other.id == id &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
@@ -397,6 +427,7 @@ class MetadataSearchDto {
|
||||
other.model == model &&
|
||||
other.ocr == ocr &&
|
||||
other.order == order &&
|
||||
other.orderBy == orderBy &&
|
||||
other.originalFileName == originalFileName &&
|
||||
other.originalPath == originalPath &&
|
||||
other.page == page &&
|
||||
@@ -429,8 +460,10 @@ class MetadataSearchDto {
|
||||
(country == null ? 0 : country!.hashCode) +
|
||||
(createdAfter == null ? 0 : createdAfter!.hashCode) +
|
||||
(createdBefore == null ? 0 : createdBefore!.hashCode) +
|
||||
(cursor == null ? 0 : cursor!.hashCode) +
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||
(filter == null ? 0 : filter!.hashCode) +
|
||||
(id == null ? 0 : id!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
@@ -443,6 +476,7 @@ class MetadataSearchDto {
|
||||
(model == null ? 0 : model!.hashCode) +
|
||||
(ocr == null ? 0 : ocr!.hashCode) +
|
||||
(order == null ? 0 : order!.hashCode) +
|
||||
(orderBy == null ? 0 : orderBy!.hashCode) +
|
||||
(originalFileName == null ? 0 : originalFileName!.hashCode) +
|
||||
(originalPath == null ? 0 : originalPath!.hashCode) +
|
||||
(page == null ? 0 : page!.hashCode) +
|
||||
@@ -467,7 +501,7 @@ class MetadataSearchDto {
|
||||
(withStacked == null ? 0 : withStacked!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'MetadataSearchDto[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, encodedVideoPath=$encodedVideoPath, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, order=$order, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
String toString() => 'MetadataSearchDto[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, cursor=$cursor, description=$description, encodedVideoPath=$encodedVideoPath, filter=$filter, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, order=$order, orderBy=$orderBy, originalFileName=$originalFileName, originalPath=$originalPath, page=$page, personIds=$personIds, previewPath=$previewPath, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, thumbnailPath=$thumbnailPath, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -499,6 +533,10 @@ class MetadataSearchDto {
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.cursor.isPresent) {
|
||||
final value = this.cursor.value;
|
||||
json[r'cursor'] = value;
|
||||
}
|
||||
if (this.description.isPresent) {
|
||||
final value = this.description.value;
|
||||
json[r'description'] = value;
|
||||
@@ -507,6 +545,10 @@ class MetadataSearchDto {
|
||||
final value = this.encodedVideoPath.value;
|
||||
json[r'encodedVideoPath'] = value;
|
||||
}
|
||||
if (this.filter.isPresent) {
|
||||
final value = this.filter.value;
|
||||
json[r'filter'] = value;
|
||||
}
|
||||
if (this.id.isPresent) {
|
||||
final value = this.id.value;
|
||||
json[r'id'] = value;
|
||||
@@ -555,6 +597,10 @@ class MetadataSearchDto {
|
||||
final value = this.order.value;
|
||||
json[r'order'] = value;
|
||||
}
|
||||
if (this.orderBy.isPresent) {
|
||||
final value = this.orderBy.value;
|
||||
json[r'orderBy'] = value;
|
||||
}
|
||||
if (this.originalFileName.isPresent) {
|
||||
final value = this.originalFileName.value;
|
||||
json[r'originalFileName'] = value;
|
||||
@@ -675,8 +721,10 @@ class MetadataSearchDto {
|
||||
country: json.containsKey(r'country') ? Optional.present(mapValueOfType<String>(json, r'country')) : const Optional.absent(),
|
||||
createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
cursor: json.containsKey(r'cursor') ? Optional.present(mapValueOfType<String>(json, r'cursor')) : const Optional.absent(),
|
||||
description: json.containsKey(r'description') ? Optional.present(mapValueOfType<String>(json, r'description')) : const Optional.absent(),
|
||||
encodedVideoPath: json.containsKey(r'encodedVideoPath') ? Optional.present(mapValueOfType<String>(json, r'encodedVideoPath')) : const Optional.absent(),
|
||||
filter: json.containsKey(r'filter') ? Optional.present(SearchFilter.fromJson(json[r'filter'])) : const Optional.absent(),
|
||||
id: json.containsKey(r'id') ? Optional.present(mapValueOfType<String>(json, r'id')) : const Optional.absent(),
|
||||
isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType<bool>(json, r'isEncoded')) : const Optional.absent(),
|
||||
isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType<bool>(json, r'isFavorite')) : const Optional.absent(),
|
||||
@@ -689,6 +737,7 @@ class MetadataSearchDto {
|
||||
model: json.containsKey(r'model') ? Optional.present(mapValueOfType<String>(json, r'model')) : const Optional.absent(),
|
||||
ocr: json.containsKey(r'ocr') ? Optional.present(mapValueOfType<String>(json, r'ocr')) : const Optional.absent(),
|
||||
order: json.containsKey(r'order') ? Optional.present(AssetOrder.fromJson(json[r'order'])) : const Optional.absent(),
|
||||
orderBy: json.containsKey(r'orderBy') ? Optional.present(SearchOrder.fromJson(json[r'orderBy'])) : const Optional.absent(),
|
||||
originalFileName: json.containsKey(r'originalFileName') ? Optional.present(mapValueOfType<String>(json, r'originalFileName')) : const Optional.absent(),
|
||||
originalPath: json.containsKey(r'originalPath') ? Optional.present(mapValueOfType<String>(json, r'originalPath')) : const Optional.absent(),
|
||||
page: json.containsKey(r'page') ? Optional.present(json[r'page'] == null ? null : int.parse('${json[r'page']}')) : const Optional.absent(),
|
||||
|
||||
211
mobile/openapi/lib/model/number_filter.dart
generated
Normal file
211
mobile/openapi/lib/model/number_filter.dart
generated
Normal file
@@ -0,0 +1,211 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class NumberFilter {
|
||||
/// Returns a new [NumberFilter] instance.
|
||||
NumberFilter({
|
||||
this.eq = const Optional.absent(),
|
||||
this.gt = const Optional.absent(),
|
||||
this.gte = const Optional.absent(),
|
||||
this.in_ = const Optional.present(const []),
|
||||
this.lt = const Optional.absent(),
|
||||
this.lte = const Optional.absent(),
|
||||
this.ne = const Optional.absent(),
|
||||
this.notIn = const Optional.present(const []),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> eq;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> gt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> gte;
|
||||
|
||||
Optional<List<num>?> in_;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> lt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> lte;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> ne;
|
||||
|
||||
Optional<List<num>?> notIn;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is NumberFilter &&
|
||||
other.eq == eq &&
|
||||
other.gt == gt &&
|
||||
other.gte == gte &&
|
||||
_deepEquality.equals(other.in_, in_) &&
|
||||
other.lt == lt &&
|
||||
other.lte == lte &&
|
||||
other.ne == ne &&
|
||||
_deepEquality.equals(other.notIn, notIn);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(gt == null ? 0 : gt!.hashCode) +
|
||||
(gte == null ? 0 : gte!.hashCode) +
|
||||
(in_.hashCode) +
|
||||
(lt == null ? 0 : lt!.hashCode) +
|
||||
(lte == null ? 0 : lte!.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode) +
|
||||
(notIn.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'NumberFilter[eq=$eq, gt=$gt, gte=$gte, in_=$in_, lt=$lt, lte=$lte, ne=$ne, notIn=$notIn]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.gt.isPresent) {
|
||||
final value = this.gt.value;
|
||||
json[r'gt'] = value;
|
||||
}
|
||||
if (this.gte.isPresent) {
|
||||
final value = this.gte.value;
|
||||
json[r'gte'] = value;
|
||||
}
|
||||
if (this.in_.isPresent) {
|
||||
final value = this.in_.value;
|
||||
json[r'in'] = value;
|
||||
}
|
||||
if (this.lt.isPresent) {
|
||||
final value = this.lt.value;
|
||||
json[r'lt'] = value;
|
||||
}
|
||||
if (this.lte.isPresent) {
|
||||
final value = this.lte.value;
|
||||
json[r'lte'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
if (this.notIn.isPresent) {
|
||||
final value = this.notIn.value;
|
||||
json[r'notIn'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [NumberFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static NumberFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "NumberFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return NumberFilter(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(json[r'eq'] == null ? null : num.parse('${json[r'eq']}')) : const Optional.absent(),
|
||||
gt: json.containsKey(r'gt') ? Optional.present(json[r'gt'] == null ? null : num.parse('${json[r'gt']}')) : const Optional.absent(),
|
||||
gte: json.containsKey(r'gte') ? Optional.present(json[r'gte'] == null ? null : num.parse('${json[r'gte']}')) : const Optional.absent(),
|
||||
in_: json.containsKey(r'in') ? Optional.present(json[r'in'] is Iterable
|
||||
? (json[r'in'] as Iterable).cast<num>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
lt: json.containsKey(r'lt') ? Optional.present(json[r'lt'] == null ? null : num.parse('${json[r'lt']}')) : const Optional.absent(),
|
||||
lte: json.containsKey(r'lte') ? Optional.present(json[r'lte'] == null ? null : num.parse('${json[r'lte']}')) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(json[r'ne'] == null ? null : num.parse('${json[r'ne']}')) : const Optional.absent(),
|
||||
notIn: json.containsKey(r'notIn') ? Optional.present(json[r'notIn'] is Iterable
|
||||
? (json[r'notIn'] as Iterable).cast<num>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<NumberFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <NumberFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = NumberFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, NumberFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, NumberFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = NumberFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of NumberFilter-objects as value to a dart map
|
||||
static Map<String, List<NumberFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<NumberFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = NumberFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
199
mobile/openapi/lib/model/number_filter_nullable.dart
generated
Normal file
199
mobile/openapi/lib/model/number_filter_nullable.dart
generated
Normal file
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class NumberFilterNullable {
|
||||
/// Returns a new [NumberFilterNullable] instance.
|
||||
NumberFilterNullable({
|
||||
this.eq = const Optional.absent(),
|
||||
this.gt = const Optional.absent(),
|
||||
this.gte = const Optional.absent(),
|
||||
this.in_ = const Optional.present(const []),
|
||||
this.lt = const Optional.absent(),
|
||||
this.lte = const Optional.absent(),
|
||||
this.ne = const Optional.absent(),
|
||||
this.notIn = const Optional.present(const []),
|
||||
});
|
||||
|
||||
Optional<num?> eq;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> gt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> gte;
|
||||
|
||||
Optional<List<num>?> in_;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> lt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<num?> lte;
|
||||
|
||||
Optional<num?> ne;
|
||||
|
||||
Optional<List<num>?> notIn;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is NumberFilterNullable &&
|
||||
other.eq == eq &&
|
||||
other.gt == gt &&
|
||||
other.gte == gte &&
|
||||
_deepEquality.equals(other.in_, in_) &&
|
||||
other.lt == lt &&
|
||||
other.lte == lte &&
|
||||
other.ne == ne &&
|
||||
_deepEquality.equals(other.notIn, notIn);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(gt == null ? 0 : gt!.hashCode) +
|
||||
(gte == null ? 0 : gte!.hashCode) +
|
||||
(in_.hashCode) +
|
||||
(lt == null ? 0 : lt!.hashCode) +
|
||||
(lte == null ? 0 : lte!.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode) +
|
||||
(notIn.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'NumberFilterNullable[eq=$eq, gt=$gt, gte=$gte, in_=$in_, lt=$lt, lte=$lte, ne=$ne, notIn=$notIn]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.gt.isPresent) {
|
||||
final value = this.gt.value;
|
||||
json[r'gt'] = value;
|
||||
}
|
||||
if (this.gte.isPresent) {
|
||||
final value = this.gte.value;
|
||||
json[r'gte'] = value;
|
||||
}
|
||||
if (this.in_.isPresent) {
|
||||
final value = this.in_.value;
|
||||
json[r'in'] = value;
|
||||
}
|
||||
if (this.lt.isPresent) {
|
||||
final value = this.lt.value;
|
||||
json[r'lt'] = value;
|
||||
}
|
||||
if (this.lte.isPresent) {
|
||||
final value = this.lte.value;
|
||||
json[r'lte'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
if (this.notIn.isPresent) {
|
||||
final value = this.notIn.value;
|
||||
json[r'notIn'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [NumberFilterNullable] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static NumberFilterNullable? fromJson(dynamic value) {
|
||||
upgradeDto(value, "NumberFilterNullable");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return NumberFilterNullable(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(json[r'eq'] == null ? null : num.parse('${json[r'eq']}')) : const Optional.absent(),
|
||||
gt: json.containsKey(r'gt') ? Optional.present(json[r'gt'] == null ? null : num.parse('${json[r'gt']}')) : const Optional.absent(),
|
||||
gte: json.containsKey(r'gte') ? Optional.present(json[r'gte'] == null ? null : num.parse('${json[r'gte']}')) : const Optional.absent(),
|
||||
in_: json.containsKey(r'in') ? Optional.present(json[r'in'] is Iterable
|
||||
? (json[r'in'] as Iterable).cast<num>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
lt: json.containsKey(r'lt') ? Optional.present(json[r'lt'] == null ? null : num.parse('${json[r'lt']}')) : const Optional.absent(),
|
||||
lte: json.containsKey(r'lte') ? Optional.present(json[r'lte'] == null ? null : num.parse('${json[r'lte']}')) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(json[r'ne'] == null ? null : num.parse('${json[r'ne']}')) : const Optional.absent(),
|
||||
notIn: json.containsKey(r'notIn') ? Optional.present(json[r'notIn'] is Iterable
|
||||
? (json[r'notIn'] as Iterable).cast<num>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<NumberFilterNullable> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <NumberFilterNullable>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = NumberFilterNullable.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, NumberFilterNullable> mapFromJson(dynamic json) {
|
||||
final map = <String, NumberFilterNullable>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = NumberFilterNullable.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of NumberFilterNullable-objects as value to a dart map
|
||||
static Map<String, List<NumberFilterNullable>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<NumberFilterNullable>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = NumberFilterNullable.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
18
mobile/openapi/lib/model/random_search_dto.dart
generated
18
mobile/openapi/lib/model/random_search_dto.dart
generated
@@ -18,6 +18,7 @@ class RandomSearchDto {
|
||||
this.country = const Optional.absent(),
|
||||
this.createdAfter = const Optional.absent(),
|
||||
this.createdBefore = const Optional.absent(),
|
||||
this.filter = const Optional.absent(),
|
||||
this.isEncoded = const Optional.absent(),
|
||||
this.isFavorite = const Optional.absent(),
|
||||
this.isMotion = const Optional.absent(),
|
||||
@@ -74,6 +75,14 @@ class RandomSearchDto {
|
||||
///
|
||||
Optional<DateTime?> createdBefore;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<SearchFilter?> filter;
|
||||
|
||||
/// Filter by encoded status
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -280,6 +289,7 @@ class RandomSearchDto {
|
||||
other.country == country &&
|
||||
other.createdAfter == createdAfter &&
|
||||
other.createdBefore == createdBefore &&
|
||||
other.filter == filter &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
@@ -316,6 +326,7 @@ class RandomSearchDto {
|
||||
(country == null ? 0 : country!.hashCode) +
|
||||
(createdAfter == null ? 0 : createdAfter!.hashCode) +
|
||||
(createdBefore == null ? 0 : createdBefore!.hashCode) +
|
||||
(filter == null ? 0 : filter!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
@@ -345,7 +356,7 @@ class RandomSearchDto {
|
||||
(withStacked == null ? 0 : withStacked!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
String toString() => 'RandomSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, filter=$filter, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif, withPeople=$withPeople, withStacked=$withStacked]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -373,6 +384,10 @@ class RandomSearchDto {
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.filter.isPresent) {
|
||||
final value = this.filter.value;
|
||||
json[r'filter'] = value;
|
||||
}
|
||||
if (this.isEncoded.isPresent) {
|
||||
final value = this.isEncoded.value;
|
||||
json[r'isEncoded'] = value;
|
||||
@@ -512,6 +527,7 @@ class RandomSearchDto {
|
||||
country: json.containsKey(r'country') ? Optional.present(mapValueOfType<String>(json, r'country')) : const Optional.absent(),
|
||||
createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
filter: json.containsKey(r'filter') ? Optional.present(SearchFilter.fromJson(json[r'filter'])) : const Optional.absent(),
|
||||
isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType<bool>(json, r'isEncoded')) : const Optional.absent(),
|
||||
isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType<bool>(json, r'isFavorite')) : const Optional.absent(),
|
||||
isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType<bool>(json, r'isMotion')) : const Optional.absent(),
|
||||
|
||||
@@ -16,6 +16,7 @@ class SearchAssetResponseDto {
|
||||
required this.count,
|
||||
this.facets = const [],
|
||||
this.items = const [],
|
||||
required this.nextCursor,
|
||||
required this.nextPage,
|
||||
required this.total,
|
||||
});
|
||||
@@ -30,6 +31,9 @@ class SearchAssetResponseDto {
|
||||
|
||||
List<AssetResponseDto> items;
|
||||
|
||||
/// Cursor for the next page of results
|
||||
String? nextCursor;
|
||||
|
||||
/// Next page token
|
||||
String? nextPage;
|
||||
|
||||
@@ -44,6 +48,7 @@ class SearchAssetResponseDto {
|
||||
other.count == count &&
|
||||
_deepEquality.equals(other.facets, facets) &&
|
||||
_deepEquality.equals(other.items, items) &&
|
||||
other.nextCursor == nextCursor &&
|
||||
other.nextPage == nextPage &&
|
||||
other.total == total;
|
||||
|
||||
@@ -53,17 +58,23 @@ class SearchAssetResponseDto {
|
||||
(count.hashCode) +
|
||||
(facets.hashCode) +
|
||||
(items.hashCode) +
|
||||
(nextCursor == null ? 0 : nextCursor!.hashCode) +
|
||||
(nextPage == null ? 0 : nextPage!.hashCode) +
|
||||
(total.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SearchAssetResponseDto[count=$count, facets=$facets, items=$items, nextPage=$nextPage, total=$total]';
|
||||
String toString() => 'SearchAssetResponseDto[count=$count, facets=$facets, items=$items, nextCursor=$nextCursor, nextPage=$nextPage, total=$total]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'count'] = this.count;
|
||||
json[r'facets'] = this.facets;
|
||||
json[r'items'] = this.items;
|
||||
if (this.nextCursor != null) {
|
||||
json[r'nextCursor'] = this.nextCursor;
|
||||
} else {
|
||||
json[r'nextCursor'] = null;
|
||||
}
|
||||
if (this.nextPage != null) {
|
||||
json[r'nextPage'] = this.nextPage;
|
||||
} else {
|
||||
@@ -85,6 +96,7 @@ class SearchAssetResponseDto {
|
||||
count: mapValueOfType<int>(json, r'count')!,
|
||||
facets: SearchFacetResponseDto.listFromJson(json[r'facets']),
|
||||
items: AssetResponseDto.listFromJson(json[r'items']),
|
||||
nextCursor: mapValueOfType<String>(json, r'nextCursor'),
|
||||
nextPage: mapValueOfType<String>(json, r'nextPage'),
|
||||
total: mapValueOfType<int>(json, r'total')!,
|
||||
);
|
||||
@@ -137,6 +149,7 @@ class SearchAssetResponseDto {
|
||||
'count',
|
||||
'facets',
|
||||
'items',
|
||||
'nextCursor',
|
||||
'nextPage',
|
||||
'total',
|
||||
};
|
||||
|
||||
613
mobile/openapi/lib/model/search_filter.dart
generated
Normal file
613
mobile/openapi/lib/model/search_filter.dart
generated
Normal file
@@ -0,0 +1,613 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class SearchFilter {
|
||||
/// Returns a new [SearchFilter] instance.
|
||||
SearchFilter({
|
||||
this.albumIds = const Optional.absent(),
|
||||
this.checksum = const Optional.absent(),
|
||||
this.city = const Optional.absent(),
|
||||
this.country = const Optional.absent(),
|
||||
this.createdAt = const Optional.absent(),
|
||||
this.description = const Optional.absent(),
|
||||
this.encodedVideoPath = const Optional.absent(),
|
||||
this.fileSizeInBytes = const Optional.absent(),
|
||||
this.hasAlbums = const Optional.absent(),
|
||||
this.hasPeople = const Optional.absent(),
|
||||
this.hasTags = const Optional.absent(),
|
||||
this.id = const Optional.absent(),
|
||||
this.isEncoded = const Optional.absent(),
|
||||
this.isFavorite = const Optional.absent(),
|
||||
this.isMotion = const Optional.absent(),
|
||||
this.isOffline = const Optional.absent(),
|
||||
this.lensModel = const Optional.absent(),
|
||||
this.libraryId = const Optional.absent(),
|
||||
this.make = const Optional.absent(),
|
||||
this.model = const Optional.absent(),
|
||||
this.ocr = const Optional.absent(),
|
||||
this.or = const Optional.present(const []),
|
||||
this.originalFileName = const Optional.absent(),
|
||||
this.originalPath = const Optional.absent(),
|
||||
this.personIds = const Optional.absent(),
|
||||
this.rating = const Optional.absent(),
|
||||
this.state = const Optional.absent(),
|
||||
this.tagIds = const Optional.absent(),
|
||||
this.takenAt = const Optional.absent(),
|
||||
this.trashedAt = const Optional.absent(),
|
||||
this.type = const Optional.absent(),
|
||||
this.updatedAt = const Optional.absent(),
|
||||
this.visibility = const Optional.absent(),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdsFilter?> albumIds;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilter?> checksum;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> city;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> country;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateFilter?> createdAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringPatternFilter?> description;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilter?> encodedVideoPath;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<NumberFilter?> fileSizeInBytes;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> hasAlbums;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> hasPeople;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> hasTags;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdFilter?> id;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> isEncoded;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> isFavorite;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> isMotion;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> isOffline;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> lensModel;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdFilterNullable?> libraryId;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> make;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> model;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringSimilarityFilter?> ocr;
|
||||
|
||||
Optional<List<SearchFilterBranch>?> or;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringPatternFilter?> originalFileName;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringPatternFilter?> originalPath;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdsFilter?> personIds;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<NumberFilterNullable?> rating;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> state;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdsFilter?> tagIds;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateFilter?> takenAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateFilterNullable?> trashedAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<EnumFilterAssetType?> type;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateFilter?> updatedAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<EnumFilterAssetVisibility?> visibility;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SearchFilter &&
|
||||
other.albumIds == albumIds &&
|
||||
other.checksum == checksum &&
|
||||
other.city == city &&
|
||||
other.country == country &&
|
||||
other.createdAt == createdAt &&
|
||||
other.description == description &&
|
||||
other.encodedVideoPath == encodedVideoPath &&
|
||||
other.fileSizeInBytes == fileSizeInBytes &&
|
||||
other.hasAlbums == hasAlbums &&
|
||||
other.hasPeople == hasPeople &&
|
||||
other.hasTags == hasTags &&
|
||||
other.id == id &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
other.isOffline == isOffline &&
|
||||
other.lensModel == lensModel &&
|
||||
other.libraryId == libraryId &&
|
||||
other.make == make &&
|
||||
other.model == model &&
|
||||
other.ocr == ocr &&
|
||||
_deepEquality.equals(other.or, or) &&
|
||||
other.originalFileName == originalFileName &&
|
||||
other.originalPath == originalPath &&
|
||||
other.personIds == personIds &&
|
||||
other.rating == rating &&
|
||||
other.state == state &&
|
||||
other.tagIds == tagIds &&
|
||||
other.takenAt == takenAt &&
|
||||
other.trashedAt == trashedAt &&
|
||||
other.type == type &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.visibility == visibility;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(albumIds == null ? 0 : albumIds!.hashCode) +
|
||||
(checksum == null ? 0 : checksum!.hashCode) +
|
||||
(city == null ? 0 : city!.hashCode) +
|
||||
(country == null ? 0 : country!.hashCode) +
|
||||
(createdAt == null ? 0 : createdAt!.hashCode) +
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||
(fileSizeInBytes == null ? 0 : fileSizeInBytes!.hashCode) +
|
||||
(hasAlbums == null ? 0 : hasAlbums!.hashCode) +
|
||||
(hasPeople == null ? 0 : hasPeople!.hashCode) +
|
||||
(hasTags == null ? 0 : hasTags!.hashCode) +
|
||||
(id == null ? 0 : id!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
(isOffline == null ? 0 : isOffline!.hashCode) +
|
||||
(lensModel == null ? 0 : lensModel!.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
(make == null ? 0 : make!.hashCode) +
|
||||
(model == null ? 0 : model!.hashCode) +
|
||||
(ocr == null ? 0 : ocr!.hashCode) +
|
||||
(or.hashCode) +
|
||||
(originalFileName == null ? 0 : originalFileName!.hashCode) +
|
||||
(originalPath == null ? 0 : originalPath!.hashCode) +
|
||||
(personIds == null ? 0 : personIds!.hashCode) +
|
||||
(rating == null ? 0 : rating!.hashCode) +
|
||||
(state == null ? 0 : state!.hashCode) +
|
||||
(tagIds == null ? 0 : tagIds!.hashCode) +
|
||||
(takenAt == null ? 0 : takenAt!.hashCode) +
|
||||
(trashedAt == null ? 0 : trashedAt!.hashCode) +
|
||||
(type == null ? 0 : type!.hashCode) +
|
||||
(updatedAt == null ? 0 : updatedAt!.hashCode) +
|
||||
(visibility == null ? 0 : visibility!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SearchFilter[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAt=$createdAt, description=$description, encodedVideoPath=$encodedVideoPath, fileSizeInBytes=$fileSizeInBytes, hasAlbums=$hasAlbums, hasPeople=$hasPeople, hasTags=$hasTags, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, or=$or, originalFileName=$originalFileName, originalPath=$originalPath, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAt=$takenAt, trashedAt=$trashedAt, type=$type, updatedAt=$updatedAt, visibility=$visibility]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.albumIds.isPresent) {
|
||||
final value = this.albumIds.value;
|
||||
json[r'albumIds'] = value;
|
||||
}
|
||||
if (this.checksum.isPresent) {
|
||||
final value = this.checksum.value;
|
||||
json[r'checksum'] = value;
|
||||
}
|
||||
if (this.city.isPresent) {
|
||||
final value = this.city.value;
|
||||
json[r'city'] = value;
|
||||
}
|
||||
if (this.country.isPresent) {
|
||||
final value = this.country.value;
|
||||
json[r'country'] = value;
|
||||
}
|
||||
if (this.createdAt.isPresent) {
|
||||
final value = this.createdAt.value;
|
||||
json[r'createdAt'] = value;
|
||||
}
|
||||
if (this.description.isPresent) {
|
||||
final value = this.description.value;
|
||||
json[r'description'] = value;
|
||||
}
|
||||
if (this.encodedVideoPath.isPresent) {
|
||||
final value = this.encodedVideoPath.value;
|
||||
json[r'encodedVideoPath'] = value;
|
||||
}
|
||||
if (this.fileSizeInBytes.isPresent) {
|
||||
final value = this.fileSizeInBytes.value;
|
||||
json[r'fileSizeInBytes'] = value;
|
||||
}
|
||||
if (this.hasAlbums.isPresent) {
|
||||
final value = this.hasAlbums.value;
|
||||
json[r'hasAlbums'] = value;
|
||||
}
|
||||
if (this.hasPeople.isPresent) {
|
||||
final value = this.hasPeople.value;
|
||||
json[r'hasPeople'] = value;
|
||||
}
|
||||
if (this.hasTags.isPresent) {
|
||||
final value = this.hasTags.value;
|
||||
json[r'hasTags'] = value;
|
||||
}
|
||||
if (this.id.isPresent) {
|
||||
final value = this.id.value;
|
||||
json[r'id'] = value;
|
||||
}
|
||||
if (this.isEncoded.isPresent) {
|
||||
final value = this.isEncoded.value;
|
||||
json[r'isEncoded'] = value;
|
||||
}
|
||||
if (this.isFavorite.isPresent) {
|
||||
final value = this.isFavorite.value;
|
||||
json[r'isFavorite'] = value;
|
||||
}
|
||||
if (this.isMotion.isPresent) {
|
||||
final value = this.isMotion.value;
|
||||
json[r'isMotion'] = value;
|
||||
}
|
||||
if (this.isOffline.isPresent) {
|
||||
final value = this.isOffline.value;
|
||||
json[r'isOffline'] = value;
|
||||
}
|
||||
if (this.lensModel.isPresent) {
|
||||
final value = this.lensModel.value;
|
||||
json[r'lensModel'] = value;
|
||||
}
|
||||
if (this.libraryId.isPresent) {
|
||||
final value = this.libraryId.value;
|
||||
json[r'libraryId'] = value;
|
||||
}
|
||||
if (this.make.isPresent) {
|
||||
final value = this.make.value;
|
||||
json[r'make'] = value;
|
||||
}
|
||||
if (this.model.isPresent) {
|
||||
final value = this.model.value;
|
||||
json[r'model'] = value;
|
||||
}
|
||||
if (this.ocr.isPresent) {
|
||||
final value = this.ocr.value;
|
||||
json[r'ocr'] = value;
|
||||
}
|
||||
if (this.or.isPresent) {
|
||||
final value = this.or.value;
|
||||
json[r'or'] = value;
|
||||
}
|
||||
if (this.originalFileName.isPresent) {
|
||||
final value = this.originalFileName.value;
|
||||
json[r'originalFileName'] = value;
|
||||
}
|
||||
if (this.originalPath.isPresent) {
|
||||
final value = this.originalPath.value;
|
||||
json[r'originalPath'] = value;
|
||||
}
|
||||
if (this.personIds.isPresent) {
|
||||
final value = this.personIds.value;
|
||||
json[r'personIds'] = value;
|
||||
}
|
||||
if (this.rating.isPresent) {
|
||||
final value = this.rating.value;
|
||||
json[r'rating'] = value;
|
||||
}
|
||||
if (this.state.isPresent) {
|
||||
final value = this.state.value;
|
||||
json[r'state'] = value;
|
||||
}
|
||||
if (this.tagIds.isPresent) {
|
||||
final value = this.tagIds.value;
|
||||
json[r'tagIds'] = value;
|
||||
}
|
||||
if (this.takenAt.isPresent) {
|
||||
final value = this.takenAt.value;
|
||||
json[r'takenAt'] = value;
|
||||
}
|
||||
if (this.trashedAt.isPresent) {
|
||||
final value = this.trashedAt.value;
|
||||
json[r'trashedAt'] = value;
|
||||
}
|
||||
if (this.type.isPresent) {
|
||||
final value = this.type.value;
|
||||
json[r'type'] = value;
|
||||
}
|
||||
if (this.updatedAt.isPresent) {
|
||||
final value = this.updatedAt.value;
|
||||
json[r'updatedAt'] = value;
|
||||
}
|
||||
if (this.visibility.isPresent) {
|
||||
final value = this.visibility.value;
|
||||
json[r'visibility'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SearchFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SearchFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SearchFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SearchFilter(
|
||||
albumIds: json.containsKey(r'albumIds') ? Optional.present(IdsFilter.fromJson(json[r'albumIds'])) : const Optional.absent(),
|
||||
checksum: json.containsKey(r'checksum') ? Optional.present(StringFilter.fromJson(json[r'checksum'])) : const Optional.absent(),
|
||||
city: json.containsKey(r'city') ? Optional.present(StringFilterNullable.fromJson(json[r'city'])) : const Optional.absent(),
|
||||
country: json.containsKey(r'country') ? Optional.present(StringFilterNullable.fromJson(json[r'country'])) : const Optional.absent(),
|
||||
createdAt: json.containsKey(r'createdAt') ? Optional.present(DateFilter.fromJson(json[r'createdAt'])) : const Optional.absent(),
|
||||
description: json.containsKey(r'description') ? Optional.present(StringPatternFilter.fromJson(json[r'description'])) : const Optional.absent(),
|
||||
encodedVideoPath: json.containsKey(r'encodedVideoPath') ? Optional.present(StringFilter.fromJson(json[r'encodedVideoPath'])) : const Optional.absent(),
|
||||
fileSizeInBytes: json.containsKey(r'fileSizeInBytes') ? Optional.present(NumberFilter.fromJson(json[r'fileSizeInBytes'])) : const Optional.absent(),
|
||||
hasAlbums: json.containsKey(r'hasAlbums') ? Optional.present(BoolFilter.fromJson(json[r'hasAlbums'])) : const Optional.absent(),
|
||||
hasPeople: json.containsKey(r'hasPeople') ? Optional.present(BoolFilter.fromJson(json[r'hasPeople'])) : const Optional.absent(),
|
||||
hasTags: json.containsKey(r'hasTags') ? Optional.present(BoolFilter.fromJson(json[r'hasTags'])) : const Optional.absent(),
|
||||
id: json.containsKey(r'id') ? Optional.present(IdFilter.fromJson(json[r'id'])) : const Optional.absent(),
|
||||
isEncoded: json.containsKey(r'isEncoded') ? Optional.present(BoolFilter.fromJson(json[r'isEncoded'])) : const Optional.absent(),
|
||||
isFavorite: json.containsKey(r'isFavorite') ? Optional.present(BoolFilter.fromJson(json[r'isFavorite'])) : const Optional.absent(),
|
||||
isMotion: json.containsKey(r'isMotion') ? Optional.present(BoolFilter.fromJson(json[r'isMotion'])) : const Optional.absent(),
|
||||
isOffline: json.containsKey(r'isOffline') ? Optional.present(BoolFilter.fromJson(json[r'isOffline'])) : const Optional.absent(),
|
||||
lensModel: json.containsKey(r'lensModel') ? Optional.present(StringFilterNullable.fromJson(json[r'lensModel'])) : const Optional.absent(),
|
||||
libraryId: json.containsKey(r'libraryId') ? Optional.present(IdFilterNullable.fromJson(json[r'libraryId'])) : const Optional.absent(),
|
||||
make: json.containsKey(r'make') ? Optional.present(StringFilterNullable.fromJson(json[r'make'])) : const Optional.absent(),
|
||||
model: json.containsKey(r'model') ? Optional.present(StringFilterNullable.fromJson(json[r'model'])) : const Optional.absent(),
|
||||
ocr: json.containsKey(r'ocr') ? Optional.present(StringSimilarityFilter.fromJson(json[r'ocr'])) : const Optional.absent(),
|
||||
or: json.containsKey(r'or') ? Optional.present(SearchFilterBranch.listFromJson(json[r'or'])) : const Optional.absent(),
|
||||
originalFileName: json.containsKey(r'originalFileName') ? Optional.present(StringPatternFilter.fromJson(json[r'originalFileName'])) : const Optional.absent(),
|
||||
originalPath: json.containsKey(r'originalPath') ? Optional.present(StringPatternFilter.fromJson(json[r'originalPath'])) : const Optional.absent(),
|
||||
personIds: json.containsKey(r'personIds') ? Optional.present(IdsFilter.fromJson(json[r'personIds'])) : const Optional.absent(),
|
||||
rating: json.containsKey(r'rating') ? Optional.present(NumberFilterNullable.fromJson(json[r'rating'])) : const Optional.absent(),
|
||||
state: json.containsKey(r'state') ? Optional.present(StringFilterNullable.fromJson(json[r'state'])) : const Optional.absent(),
|
||||
tagIds: json.containsKey(r'tagIds') ? Optional.present(IdsFilter.fromJson(json[r'tagIds'])) : const Optional.absent(),
|
||||
takenAt: json.containsKey(r'takenAt') ? Optional.present(DateFilter.fromJson(json[r'takenAt'])) : const Optional.absent(),
|
||||
trashedAt: json.containsKey(r'trashedAt') ? Optional.present(DateFilterNullable.fromJson(json[r'trashedAt'])) : const Optional.absent(),
|
||||
type: json.containsKey(r'type') ? Optional.present(EnumFilterAssetType.fromJson(json[r'type'])) : const Optional.absent(),
|
||||
updatedAt: json.containsKey(r'updatedAt') ? Optional.present(DateFilter.fromJson(json[r'updatedAt'])) : const Optional.absent(),
|
||||
visibility: json.containsKey(r'visibility') ? Optional.present(EnumFilterAssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SearchFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SearchFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SearchFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SearchFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, SearchFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SearchFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SearchFilter-objects as value to a dart map
|
||||
static Map<String, List<SearchFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SearchFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SearchFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
603
mobile/openapi/lib/model/search_filter_branch.dart
generated
Normal file
603
mobile/openapi/lib/model/search_filter_branch.dart
generated
Normal file
@@ -0,0 +1,603 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class SearchFilterBranch {
|
||||
/// Returns a new [SearchFilterBranch] instance.
|
||||
SearchFilterBranch({
|
||||
this.albumIds = const Optional.absent(),
|
||||
this.checksum = const Optional.absent(),
|
||||
this.city = const Optional.absent(),
|
||||
this.country = const Optional.absent(),
|
||||
this.createdAt = const Optional.absent(),
|
||||
this.description = const Optional.absent(),
|
||||
this.encodedVideoPath = const Optional.absent(),
|
||||
this.fileSizeInBytes = const Optional.absent(),
|
||||
this.hasAlbums = const Optional.absent(),
|
||||
this.hasPeople = const Optional.absent(),
|
||||
this.hasTags = const Optional.absent(),
|
||||
this.id = const Optional.absent(),
|
||||
this.isEncoded = const Optional.absent(),
|
||||
this.isFavorite = const Optional.absent(),
|
||||
this.isMotion = const Optional.absent(),
|
||||
this.isOffline = const Optional.absent(),
|
||||
this.lensModel = const Optional.absent(),
|
||||
this.libraryId = const Optional.absent(),
|
||||
this.make = const Optional.absent(),
|
||||
this.model = const Optional.absent(),
|
||||
this.ocr = const Optional.absent(),
|
||||
this.originalFileName = const Optional.absent(),
|
||||
this.originalPath = const Optional.absent(),
|
||||
this.personIds = const Optional.absent(),
|
||||
this.rating = const Optional.absent(),
|
||||
this.state = const Optional.absent(),
|
||||
this.tagIds = const Optional.absent(),
|
||||
this.takenAt = const Optional.absent(),
|
||||
this.trashedAt = const Optional.absent(),
|
||||
this.type = const Optional.absent(),
|
||||
this.updatedAt = const Optional.absent(),
|
||||
this.visibility = const Optional.absent(),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdsFilter?> albumIds;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilter?> checksum;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> city;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> country;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateFilter?> createdAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringPatternFilter?> description;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilter?> encodedVideoPath;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<NumberFilter?> fileSizeInBytes;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> hasAlbums;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> hasPeople;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> hasTags;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdFilter?> id;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> isEncoded;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> isFavorite;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> isMotion;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<BoolFilter?> isOffline;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> lensModel;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdFilterNullable?> libraryId;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> make;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> model;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringSimilarityFilter?> ocr;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringPatternFilter?> originalFileName;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringPatternFilter?> originalPath;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdsFilter?> personIds;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<NumberFilterNullable?> rating;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<StringFilterNullable?> state;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<IdsFilter?> tagIds;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateFilter?> takenAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateFilterNullable?> trashedAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<EnumFilterAssetType?> type;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<DateFilter?> updatedAt;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<EnumFilterAssetVisibility?> visibility;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SearchFilterBranch &&
|
||||
other.albumIds == albumIds &&
|
||||
other.checksum == checksum &&
|
||||
other.city == city &&
|
||||
other.country == country &&
|
||||
other.createdAt == createdAt &&
|
||||
other.description == description &&
|
||||
other.encodedVideoPath == encodedVideoPath &&
|
||||
other.fileSizeInBytes == fileSizeInBytes &&
|
||||
other.hasAlbums == hasAlbums &&
|
||||
other.hasPeople == hasPeople &&
|
||||
other.hasTags == hasTags &&
|
||||
other.id == id &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
other.isOffline == isOffline &&
|
||||
other.lensModel == lensModel &&
|
||||
other.libraryId == libraryId &&
|
||||
other.make == make &&
|
||||
other.model == model &&
|
||||
other.ocr == ocr &&
|
||||
other.originalFileName == originalFileName &&
|
||||
other.originalPath == originalPath &&
|
||||
other.personIds == personIds &&
|
||||
other.rating == rating &&
|
||||
other.state == state &&
|
||||
other.tagIds == tagIds &&
|
||||
other.takenAt == takenAt &&
|
||||
other.trashedAt == trashedAt &&
|
||||
other.type == type &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.visibility == visibility;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(albumIds == null ? 0 : albumIds!.hashCode) +
|
||||
(checksum == null ? 0 : checksum!.hashCode) +
|
||||
(city == null ? 0 : city!.hashCode) +
|
||||
(country == null ? 0 : country!.hashCode) +
|
||||
(createdAt == null ? 0 : createdAt!.hashCode) +
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(encodedVideoPath == null ? 0 : encodedVideoPath!.hashCode) +
|
||||
(fileSizeInBytes == null ? 0 : fileSizeInBytes!.hashCode) +
|
||||
(hasAlbums == null ? 0 : hasAlbums!.hashCode) +
|
||||
(hasPeople == null ? 0 : hasPeople!.hashCode) +
|
||||
(hasTags == null ? 0 : hasTags!.hashCode) +
|
||||
(id == null ? 0 : id!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
(isOffline == null ? 0 : isOffline!.hashCode) +
|
||||
(lensModel == null ? 0 : lensModel!.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
(make == null ? 0 : make!.hashCode) +
|
||||
(model == null ? 0 : model!.hashCode) +
|
||||
(ocr == null ? 0 : ocr!.hashCode) +
|
||||
(originalFileName == null ? 0 : originalFileName!.hashCode) +
|
||||
(originalPath == null ? 0 : originalPath!.hashCode) +
|
||||
(personIds == null ? 0 : personIds!.hashCode) +
|
||||
(rating == null ? 0 : rating!.hashCode) +
|
||||
(state == null ? 0 : state!.hashCode) +
|
||||
(tagIds == null ? 0 : tagIds!.hashCode) +
|
||||
(takenAt == null ? 0 : takenAt!.hashCode) +
|
||||
(trashedAt == null ? 0 : trashedAt!.hashCode) +
|
||||
(type == null ? 0 : type!.hashCode) +
|
||||
(updatedAt == null ? 0 : updatedAt!.hashCode) +
|
||||
(visibility == null ? 0 : visibility!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SearchFilterBranch[albumIds=$albumIds, checksum=$checksum, city=$city, country=$country, createdAt=$createdAt, description=$description, encodedVideoPath=$encodedVideoPath, fileSizeInBytes=$fileSizeInBytes, hasAlbums=$hasAlbums, hasPeople=$hasPeople, hasTags=$hasTags, id=$id, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, originalFileName=$originalFileName, originalPath=$originalPath, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAt=$takenAt, trashedAt=$trashedAt, type=$type, updatedAt=$updatedAt, visibility=$visibility]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.albumIds.isPresent) {
|
||||
final value = this.albumIds.value;
|
||||
json[r'albumIds'] = value;
|
||||
}
|
||||
if (this.checksum.isPresent) {
|
||||
final value = this.checksum.value;
|
||||
json[r'checksum'] = value;
|
||||
}
|
||||
if (this.city.isPresent) {
|
||||
final value = this.city.value;
|
||||
json[r'city'] = value;
|
||||
}
|
||||
if (this.country.isPresent) {
|
||||
final value = this.country.value;
|
||||
json[r'country'] = value;
|
||||
}
|
||||
if (this.createdAt.isPresent) {
|
||||
final value = this.createdAt.value;
|
||||
json[r'createdAt'] = value;
|
||||
}
|
||||
if (this.description.isPresent) {
|
||||
final value = this.description.value;
|
||||
json[r'description'] = value;
|
||||
}
|
||||
if (this.encodedVideoPath.isPresent) {
|
||||
final value = this.encodedVideoPath.value;
|
||||
json[r'encodedVideoPath'] = value;
|
||||
}
|
||||
if (this.fileSizeInBytes.isPresent) {
|
||||
final value = this.fileSizeInBytes.value;
|
||||
json[r'fileSizeInBytes'] = value;
|
||||
}
|
||||
if (this.hasAlbums.isPresent) {
|
||||
final value = this.hasAlbums.value;
|
||||
json[r'hasAlbums'] = value;
|
||||
}
|
||||
if (this.hasPeople.isPresent) {
|
||||
final value = this.hasPeople.value;
|
||||
json[r'hasPeople'] = value;
|
||||
}
|
||||
if (this.hasTags.isPresent) {
|
||||
final value = this.hasTags.value;
|
||||
json[r'hasTags'] = value;
|
||||
}
|
||||
if (this.id.isPresent) {
|
||||
final value = this.id.value;
|
||||
json[r'id'] = value;
|
||||
}
|
||||
if (this.isEncoded.isPresent) {
|
||||
final value = this.isEncoded.value;
|
||||
json[r'isEncoded'] = value;
|
||||
}
|
||||
if (this.isFavorite.isPresent) {
|
||||
final value = this.isFavorite.value;
|
||||
json[r'isFavorite'] = value;
|
||||
}
|
||||
if (this.isMotion.isPresent) {
|
||||
final value = this.isMotion.value;
|
||||
json[r'isMotion'] = value;
|
||||
}
|
||||
if (this.isOffline.isPresent) {
|
||||
final value = this.isOffline.value;
|
||||
json[r'isOffline'] = value;
|
||||
}
|
||||
if (this.lensModel.isPresent) {
|
||||
final value = this.lensModel.value;
|
||||
json[r'lensModel'] = value;
|
||||
}
|
||||
if (this.libraryId.isPresent) {
|
||||
final value = this.libraryId.value;
|
||||
json[r'libraryId'] = value;
|
||||
}
|
||||
if (this.make.isPresent) {
|
||||
final value = this.make.value;
|
||||
json[r'make'] = value;
|
||||
}
|
||||
if (this.model.isPresent) {
|
||||
final value = this.model.value;
|
||||
json[r'model'] = value;
|
||||
}
|
||||
if (this.ocr.isPresent) {
|
||||
final value = this.ocr.value;
|
||||
json[r'ocr'] = value;
|
||||
}
|
||||
if (this.originalFileName.isPresent) {
|
||||
final value = this.originalFileName.value;
|
||||
json[r'originalFileName'] = value;
|
||||
}
|
||||
if (this.originalPath.isPresent) {
|
||||
final value = this.originalPath.value;
|
||||
json[r'originalPath'] = value;
|
||||
}
|
||||
if (this.personIds.isPresent) {
|
||||
final value = this.personIds.value;
|
||||
json[r'personIds'] = value;
|
||||
}
|
||||
if (this.rating.isPresent) {
|
||||
final value = this.rating.value;
|
||||
json[r'rating'] = value;
|
||||
}
|
||||
if (this.state.isPresent) {
|
||||
final value = this.state.value;
|
||||
json[r'state'] = value;
|
||||
}
|
||||
if (this.tagIds.isPresent) {
|
||||
final value = this.tagIds.value;
|
||||
json[r'tagIds'] = value;
|
||||
}
|
||||
if (this.takenAt.isPresent) {
|
||||
final value = this.takenAt.value;
|
||||
json[r'takenAt'] = value;
|
||||
}
|
||||
if (this.trashedAt.isPresent) {
|
||||
final value = this.trashedAt.value;
|
||||
json[r'trashedAt'] = value;
|
||||
}
|
||||
if (this.type.isPresent) {
|
||||
final value = this.type.value;
|
||||
json[r'type'] = value;
|
||||
}
|
||||
if (this.updatedAt.isPresent) {
|
||||
final value = this.updatedAt.value;
|
||||
json[r'updatedAt'] = value;
|
||||
}
|
||||
if (this.visibility.isPresent) {
|
||||
final value = this.visibility.value;
|
||||
json[r'visibility'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SearchFilterBranch] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SearchFilterBranch? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SearchFilterBranch");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SearchFilterBranch(
|
||||
albumIds: json.containsKey(r'albumIds') ? Optional.present(IdsFilter.fromJson(json[r'albumIds'])) : const Optional.absent(),
|
||||
checksum: json.containsKey(r'checksum') ? Optional.present(StringFilter.fromJson(json[r'checksum'])) : const Optional.absent(),
|
||||
city: json.containsKey(r'city') ? Optional.present(StringFilterNullable.fromJson(json[r'city'])) : const Optional.absent(),
|
||||
country: json.containsKey(r'country') ? Optional.present(StringFilterNullable.fromJson(json[r'country'])) : const Optional.absent(),
|
||||
createdAt: json.containsKey(r'createdAt') ? Optional.present(DateFilter.fromJson(json[r'createdAt'])) : const Optional.absent(),
|
||||
description: json.containsKey(r'description') ? Optional.present(StringPatternFilter.fromJson(json[r'description'])) : const Optional.absent(),
|
||||
encodedVideoPath: json.containsKey(r'encodedVideoPath') ? Optional.present(StringFilter.fromJson(json[r'encodedVideoPath'])) : const Optional.absent(),
|
||||
fileSizeInBytes: json.containsKey(r'fileSizeInBytes') ? Optional.present(NumberFilter.fromJson(json[r'fileSizeInBytes'])) : const Optional.absent(),
|
||||
hasAlbums: json.containsKey(r'hasAlbums') ? Optional.present(BoolFilter.fromJson(json[r'hasAlbums'])) : const Optional.absent(),
|
||||
hasPeople: json.containsKey(r'hasPeople') ? Optional.present(BoolFilter.fromJson(json[r'hasPeople'])) : const Optional.absent(),
|
||||
hasTags: json.containsKey(r'hasTags') ? Optional.present(BoolFilter.fromJson(json[r'hasTags'])) : const Optional.absent(),
|
||||
id: json.containsKey(r'id') ? Optional.present(IdFilter.fromJson(json[r'id'])) : const Optional.absent(),
|
||||
isEncoded: json.containsKey(r'isEncoded') ? Optional.present(BoolFilter.fromJson(json[r'isEncoded'])) : const Optional.absent(),
|
||||
isFavorite: json.containsKey(r'isFavorite') ? Optional.present(BoolFilter.fromJson(json[r'isFavorite'])) : const Optional.absent(),
|
||||
isMotion: json.containsKey(r'isMotion') ? Optional.present(BoolFilter.fromJson(json[r'isMotion'])) : const Optional.absent(),
|
||||
isOffline: json.containsKey(r'isOffline') ? Optional.present(BoolFilter.fromJson(json[r'isOffline'])) : const Optional.absent(),
|
||||
lensModel: json.containsKey(r'lensModel') ? Optional.present(StringFilterNullable.fromJson(json[r'lensModel'])) : const Optional.absent(),
|
||||
libraryId: json.containsKey(r'libraryId') ? Optional.present(IdFilterNullable.fromJson(json[r'libraryId'])) : const Optional.absent(),
|
||||
make: json.containsKey(r'make') ? Optional.present(StringFilterNullable.fromJson(json[r'make'])) : const Optional.absent(),
|
||||
model: json.containsKey(r'model') ? Optional.present(StringFilterNullable.fromJson(json[r'model'])) : const Optional.absent(),
|
||||
ocr: json.containsKey(r'ocr') ? Optional.present(StringSimilarityFilter.fromJson(json[r'ocr'])) : const Optional.absent(),
|
||||
originalFileName: json.containsKey(r'originalFileName') ? Optional.present(StringPatternFilter.fromJson(json[r'originalFileName'])) : const Optional.absent(),
|
||||
originalPath: json.containsKey(r'originalPath') ? Optional.present(StringPatternFilter.fromJson(json[r'originalPath'])) : const Optional.absent(),
|
||||
personIds: json.containsKey(r'personIds') ? Optional.present(IdsFilter.fromJson(json[r'personIds'])) : const Optional.absent(),
|
||||
rating: json.containsKey(r'rating') ? Optional.present(NumberFilterNullable.fromJson(json[r'rating'])) : const Optional.absent(),
|
||||
state: json.containsKey(r'state') ? Optional.present(StringFilterNullable.fromJson(json[r'state'])) : const Optional.absent(),
|
||||
tagIds: json.containsKey(r'tagIds') ? Optional.present(IdsFilter.fromJson(json[r'tagIds'])) : const Optional.absent(),
|
||||
takenAt: json.containsKey(r'takenAt') ? Optional.present(DateFilter.fromJson(json[r'takenAt'])) : const Optional.absent(),
|
||||
trashedAt: json.containsKey(r'trashedAt') ? Optional.present(DateFilterNullable.fromJson(json[r'trashedAt'])) : const Optional.absent(),
|
||||
type: json.containsKey(r'type') ? Optional.present(EnumFilterAssetType.fromJson(json[r'type'])) : const Optional.absent(),
|
||||
updatedAt: json.containsKey(r'updatedAt') ? Optional.present(DateFilter.fromJson(json[r'updatedAt'])) : const Optional.absent(),
|
||||
visibility: json.containsKey(r'visibility') ? Optional.present(EnumFilterAssetVisibility.fromJson(json[r'visibility'])) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SearchFilterBranch> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SearchFilterBranch>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SearchFilterBranch.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SearchFilterBranch> mapFromJson(dynamic json) {
|
||||
final map = <String, SearchFilterBranch>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SearchFilterBranch.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SearchFilterBranch-objects as value to a dart map
|
||||
static Map<String, List<SearchFilterBranch>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SearchFilterBranch>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SearchFilterBranch.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
123
mobile/openapi/lib/model/search_order.dart
generated
Normal file
123
mobile/openapi/lib/model/search_order.dart
generated
Normal file
@@ -0,0 +1,123 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class SearchOrder {
|
||||
/// Returns a new [SearchOrder] instance.
|
||||
SearchOrder({
|
||||
this.direction = const Optional.absent(),
|
||||
this.field = const Optional.absent(),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<AssetOrder?> direction;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<SearchOrderField?> field;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SearchOrder &&
|
||||
other.direction == direction &&
|
||||
other.field == field;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(direction == null ? 0 : direction!.hashCode) +
|
||||
(field == null ? 0 : field!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SearchOrder[direction=$direction, field=$field]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.direction.isPresent) {
|
||||
final value = this.direction.value;
|
||||
json[r'direction'] = value;
|
||||
}
|
||||
if (this.field.isPresent) {
|
||||
final value = this.field.value;
|
||||
json[r'field'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SearchOrder] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SearchOrder? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SearchOrder");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SearchOrder(
|
||||
direction: json.containsKey(r'direction') ? Optional.present(AssetOrder.fromJson(json[r'direction'])) : const Optional.absent(),
|
||||
field: json.containsKey(r'field') ? Optional.present(SearchOrderField.fromJson(json[r'field'])) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SearchOrder> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SearchOrder>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SearchOrder.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SearchOrder> mapFromJson(dynamic json) {
|
||||
final map = <String, SearchOrder>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SearchOrder.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SearchOrder-objects as value to a dart map
|
||||
static Map<String, List<SearchOrder>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SearchOrder>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SearchOrder.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
94
mobile/openapi/lib/model/search_order_field.dart
generated
Normal file
94
mobile/openapi/lib/model/search_order_field.dart
generated
Normal file
@@ -0,0 +1,94 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
|
||||
enum SearchOrderField {
|
||||
fileCreatedAt._(r'fileCreatedAt'),
|
||||
localDateTime._(r'localDateTime'),
|
||||
fileSizeInBytes._(r'fileSizeInBytes'),
|
||||
rating._(r'rating'),
|
||||
;
|
||||
|
||||
/// Instantiate a new enum with the provided value.
|
||||
const SearchOrderField._(this._value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String _value;
|
||||
|
||||
@override
|
||||
String toString() => _value;
|
||||
|
||||
/// Encodes this enum as a value suitable for JSON.
|
||||
String toJson() => _value;
|
||||
|
||||
/// Returns the instance of [SearchOrderField] that was successfully decoded
|
||||
/// from the passed [value] on success, null otherwise.
|
||||
static SearchOrderField? fromJson(dynamic value) => SearchOrderFieldTypeTransformer().decode(value);
|
||||
|
||||
/// Returns a [List] containing instances of [SearchOrderField]
|
||||
/// that were successfully decoded from the passed [JSON][json].
|
||||
static List<SearchOrderField> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SearchOrderField>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SearchOrderField.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [SearchOrderField] to String,
|
||||
/// and [decode] dynamic data back to [SearchOrderField].
|
||||
class SearchOrderFieldTypeTransformer {
|
||||
factory SearchOrderFieldTypeTransformer() => _instance ??= const SearchOrderFieldTypeTransformer._();
|
||||
|
||||
const SearchOrderFieldTypeTransformer._();
|
||||
|
||||
/// Encodes this enum as a value suitable for JSON.
|
||||
String encode(SearchOrderField data) => data._value;
|
||||
|
||||
/// Returns the instance of [SearchOrderField] that was successfully decoded
|
||||
/// from the passed [data] value on success, null otherwise.
|
||||
///
|
||||
/// If [allowNull] is true and the [dynamic value][data] cannot be decoded successfully,
|
||||
/// then null is returned. However, if [allowNull] is false and the [dynamic value][data]
|
||||
/// cannot be decoded successfully, then an [UnimplementedError] is thrown.
|
||||
///
|
||||
/// The [allowNull] is very handy when an API changes and a new enum value is added or removed,
|
||||
/// and users are still using an old app with the old code.
|
||||
SearchOrderField? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data is SearchOrderField) {
|
||||
return data;
|
||||
}
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'fileCreatedAt': return SearchOrderField.fileCreatedAt;
|
||||
case r'localDateTime': return SearchOrderField.localDateTime;
|
||||
case r'fileSizeInBytes': return SearchOrderField.fileSizeInBytes;
|
||||
case r'rating': return SearchOrderField.rating;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// The singleton instance of this transformer.
|
||||
static SearchOrderFieldTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
35
mobile/openapi/lib/model/smart_search_dto.dart
generated
35
mobile/openapi/lib/model/smart_search_dto.dart
generated
@@ -18,6 +18,8 @@ class SmartSearchDto {
|
||||
this.country = const Optional.absent(),
|
||||
this.createdAfter = const Optional.absent(),
|
||||
this.createdBefore = const Optional.absent(),
|
||||
this.cursor = const Optional.absent(),
|
||||
this.filter = const Optional.absent(),
|
||||
this.isEncoded = const Optional.absent(),
|
||||
this.isFavorite = const Optional.absent(),
|
||||
this.isMotion = const Optional.absent(),
|
||||
@@ -76,6 +78,23 @@ class SmartSearchDto {
|
||||
///
|
||||
Optional<DateTime?> createdBefore;
|
||||
|
||||
/// Cursor for the next page of results
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> cursor;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<SearchFilter?> filter;
|
||||
|
||||
/// Filter by encoded status
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -303,6 +322,8 @@ class SmartSearchDto {
|
||||
other.country == country &&
|
||||
other.createdAfter == createdAfter &&
|
||||
other.createdBefore == createdBefore &&
|
||||
other.cursor == cursor &&
|
||||
other.filter == filter &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
@@ -341,6 +362,8 @@ class SmartSearchDto {
|
||||
(country == null ? 0 : country!.hashCode) +
|
||||
(createdAfter == null ? 0 : createdAfter!.hashCode) +
|
||||
(createdBefore == null ? 0 : createdBefore!.hashCode) +
|
||||
(cursor == null ? 0 : cursor!.hashCode) +
|
||||
(filter == null ? 0 : filter!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
@@ -372,7 +395,7 @@ class SmartSearchDto {
|
||||
(withExif == null ? 0 : withExif!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]';
|
||||
String toString() => 'SmartSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, cursor=$cursor, filter=$filter, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, language=$language, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, page=$page, personIds=$personIds, query=$query, queryAssetId=$queryAssetId, rating=$rating, size=$size, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility, withDeleted=$withDeleted, withExif=$withExif]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -400,6 +423,14 @@ class SmartSearchDto {
|
||||
? value.millisecondsSinceEpoch
|
||||
: value.toUtc().toIso8601String());
|
||||
}
|
||||
if (this.cursor.isPresent) {
|
||||
final value = this.cursor.value;
|
||||
json[r'cursor'] = value;
|
||||
}
|
||||
if (this.filter.isPresent) {
|
||||
final value = this.filter.value;
|
||||
json[r'filter'] = value;
|
||||
}
|
||||
if (this.isEncoded.isPresent) {
|
||||
final value = this.isEncoded.value;
|
||||
json[r'isEncoded'] = value;
|
||||
@@ -547,6 +578,8 @@ class SmartSearchDto {
|
||||
country: json.containsKey(r'country') ? Optional.present(mapValueOfType<String>(json, r'country')) : const Optional.absent(),
|
||||
createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
cursor: json.containsKey(r'cursor') ? Optional.present(mapValueOfType<String>(json, r'cursor')) : const Optional.absent(),
|
||||
filter: json.containsKey(r'filter') ? Optional.present(SearchFilter.fromJson(json[r'filter'])) : const Optional.absent(),
|
||||
isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType<bool>(json, r'isEncoded')) : const Optional.absent(),
|
||||
isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType<bool>(json, r'isFavorite')) : const Optional.absent(),
|
||||
isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType<bool>(json, r'isMotion')) : const Optional.absent(),
|
||||
|
||||
18
mobile/openapi/lib/model/statistics_search_dto.dart
generated
18
mobile/openapi/lib/model/statistics_search_dto.dart
generated
@@ -19,6 +19,7 @@ class StatisticsSearchDto {
|
||||
this.createdAfter = const Optional.absent(),
|
||||
this.createdBefore = const Optional.absent(),
|
||||
this.description = const Optional.absent(),
|
||||
this.filter = const Optional.absent(),
|
||||
this.isEncoded = const Optional.absent(),
|
||||
this.isFavorite = const Optional.absent(),
|
||||
this.isMotion = const Optional.absent(),
|
||||
@@ -79,6 +80,14 @@ class StatisticsSearchDto {
|
||||
///
|
||||
Optional<String?> description;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<SearchFilter?> filter;
|
||||
|
||||
/// Filter by encoded status
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
@@ -238,6 +247,7 @@ class StatisticsSearchDto {
|
||||
other.createdAfter == createdAfter &&
|
||||
other.createdBefore == createdBefore &&
|
||||
other.description == description &&
|
||||
other.filter == filter &&
|
||||
other.isEncoded == isEncoded &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isMotion == isMotion &&
|
||||
@@ -270,6 +280,7 @@ class StatisticsSearchDto {
|
||||
(createdAfter == null ? 0 : createdAfter!.hashCode) +
|
||||
(createdBefore == null ? 0 : createdBefore!.hashCode) +
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(filter == null ? 0 : filter!.hashCode) +
|
||||
(isEncoded == null ? 0 : isEncoded!.hashCode) +
|
||||
(isFavorite == null ? 0 : isFavorite!.hashCode) +
|
||||
(isMotion == null ? 0 : isMotion!.hashCode) +
|
||||
@@ -294,7 +305,7 @@ class StatisticsSearchDto {
|
||||
(visibility == null ? 0 : visibility!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility]';
|
||||
String toString() => 'StatisticsSearchDto[albumIds=$albumIds, city=$city, country=$country, createdAfter=$createdAfter, createdBefore=$createdBefore, description=$description, filter=$filter, isEncoded=$isEncoded, isFavorite=$isFavorite, isMotion=$isMotion, isNotInAlbum=$isNotInAlbum, isOffline=$isOffline, lensModel=$lensModel, libraryId=$libraryId, make=$make, model=$model, ocr=$ocr, personIds=$personIds, rating=$rating, state=$state, tagIds=$tagIds, takenAfter=$takenAfter, takenBefore=$takenBefore, trashedAfter=$trashedAfter, trashedBefore=$trashedBefore, type=$type, updatedAfter=$updatedAfter, updatedBefore=$updatedBefore, visibility=$visibility]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
@@ -326,6 +337,10 @@ class StatisticsSearchDto {
|
||||
final value = this.description.value;
|
||||
json[r'description'] = value;
|
||||
}
|
||||
if (this.filter.isPresent) {
|
||||
final value = this.filter.value;
|
||||
json[r'filter'] = value;
|
||||
}
|
||||
if (this.isEncoded.isPresent) {
|
||||
final value = this.isEncoded.value;
|
||||
json[r'isEncoded'] = value;
|
||||
@@ -446,6 +461,7 @@ class StatisticsSearchDto {
|
||||
createdAfter: json.containsKey(r'createdAfter') ? Optional.present(mapDateTime(json, r'createdAfter', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
createdBefore: json.containsKey(r'createdBefore') ? Optional.present(mapDateTime(json, r'createdBefore', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$/')) : const Optional.absent(),
|
||||
description: json.containsKey(r'description') ? Optional.present(mapValueOfType<String>(json, r'description')) : const Optional.absent(),
|
||||
filter: json.containsKey(r'filter') ? Optional.present(SearchFilter.fromJson(json[r'filter'])) : const Optional.absent(),
|
||||
isEncoded: json.containsKey(r'isEncoded') ? Optional.present(mapValueOfType<bool>(json, r'isEncoded')) : const Optional.absent(),
|
||||
isFavorite: json.containsKey(r'isFavorite') ? Optional.present(mapValueOfType<bool>(json, r'isFavorite')) : const Optional.absent(),
|
||||
isMotion: json.containsKey(r'isMotion') ? Optional.present(mapValueOfType<bool>(json, r'isMotion')) : const Optional.absent(),
|
||||
|
||||
147
mobile/openapi/lib/model/string_filter.dart
generated
Normal file
147
mobile/openapi/lib/model/string_filter.dart
generated
Normal file
@@ -0,0 +1,147 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class StringFilter {
|
||||
/// Returns a new [StringFilter] instance.
|
||||
StringFilter({
|
||||
this.eq = const Optional.absent(),
|
||||
this.in_ = const Optional.present(const []),
|
||||
this.ne = const Optional.absent(),
|
||||
this.notIn = const Optional.present(const []),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> eq;
|
||||
|
||||
Optional<List<String>?> in_;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> ne;
|
||||
|
||||
Optional<List<String>?> notIn;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is StringFilter &&
|
||||
other.eq == eq &&
|
||||
_deepEquality.equals(other.in_, in_) &&
|
||||
other.ne == ne &&
|
||||
_deepEquality.equals(other.notIn, notIn);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(in_.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode) +
|
||||
(notIn.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'StringFilter[eq=$eq, in_=$in_, ne=$ne, notIn=$notIn]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.in_.isPresent) {
|
||||
final value = this.in_.value;
|
||||
json[r'in'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
if (this.notIn.isPresent) {
|
||||
final value = this.notIn.value;
|
||||
json[r'notIn'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [StringFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static StringFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "StringFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return StringFilter(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(mapValueOfType<String>(json, r'eq')) : const Optional.absent(),
|
||||
in_: json.containsKey(r'in') ? Optional.present(json[r'in'] is Iterable
|
||||
? (json[r'in'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(mapValueOfType<String>(json, r'ne')) : const Optional.absent(),
|
||||
notIn: json.containsKey(r'notIn') ? Optional.present(json[r'notIn'] is Iterable
|
||||
? (json[r'notIn'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<StringFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <StringFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = StringFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, StringFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, StringFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = StringFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of StringFilter-objects as value to a dart map
|
||||
static Map<String, List<StringFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<StringFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = StringFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
135
mobile/openapi/lib/model/string_filter_nullable.dart
generated
Normal file
135
mobile/openapi/lib/model/string_filter_nullable.dart
generated
Normal file
@@ -0,0 +1,135 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class StringFilterNullable {
|
||||
/// Returns a new [StringFilterNullable] instance.
|
||||
StringFilterNullable({
|
||||
this.eq = const Optional.absent(),
|
||||
this.in_ = const Optional.present(const []),
|
||||
this.ne = const Optional.absent(),
|
||||
this.notIn = const Optional.present(const []),
|
||||
});
|
||||
|
||||
Optional<String?> eq;
|
||||
|
||||
Optional<List<String>?> in_;
|
||||
|
||||
Optional<String?> ne;
|
||||
|
||||
Optional<List<String>?> notIn;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is StringFilterNullable &&
|
||||
other.eq == eq &&
|
||||
_deepEquality.equals(other.in_, in_) &&
|
||||
other.ne == ne &&
|
||||
_deepEquality.equals(other.notIn, notIn);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(in_.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode) +
|
||||
(notIn.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'StringFilterNullable[eq=$eq, in_=$in_, ne=$ne, notIn=$notIn]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.in_.isPresent) {
|
||||
final value = this.in_.value;
|
||||
json[r'in'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
if (this.notIn.isPresent) {
|
||||
final value = this.notIn.value;
|
||||
json[r'notIn'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [StringFilterNullable] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static StringFilterNullable? fromJson(dynamic value) {
|
||||
upgradeDto(value, "StringFilterNullable");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return StringFilterNullable(
|
||||
eq: json.containsKey(r'eq') ? Optional.present(mapValueOfType<String>(json, r'eq')) : const Optional.absent(),
|
||||
in_: json.containsKey(r'in') ? Optional.present(json[r'in'] is Iterable
|
||||
? (json[r'in'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(mapValueOfType<String>(json, r'ne')) : const Optional.absent(),
|
||||
notIn: json.containsKey(r'notIn') ? Optional.present(json[r'notIn'] is Iterable
|
||||
? (json[r'notIn'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<StringFilterNullable> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <StringFilterNullable>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = StringFilterNullable.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, StringFilterNullable> mapFromJson(dynamic json) {
|
||||
final map = <String, StringFilterNullable>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = StringFilterNullable.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of StringFilterNullable-objects as value to a dart map
|
||||
static Map<String, List<StringFilterNullable>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<StringFilterNullable>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = StringFilterNullable.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
199
mobile/openapi/lib/model/string_pattern_filter.dart
generated
Normal file
199
mobile/openapi/lib/model/string_pattern_filter.dart
generated
Normal file
@@ -0,0 +1,199 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class StringPatternFilter {
|
||||
/// Returns a new [StringPatternFilter] instance.
|
||||
StringPatternFilter({
|
||||
this.endsWith = const Optional.absent(),
|
||||
this.eq = const Optional.absent(),
|
||||
this.in_ = const Optional.present(const []),
|
||||
this.like = const Optional.absent(),
|
||||
this.ne = const Optional.absent(),
|
||||
this.notIn = const Optional.present(const []),
|
||||
this.notLike = const Optional.absent(),
|
||||
this.startsWith = const Optional.absent(),
|
||||
});
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> endsWith;
|
||||
|
||||
Optional<String?> eq;
|
||||
|
||||
Optional<List<String>?> in_;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> like;
|
||||
|
||||
Optional<String?> ne;
|
||||
|
||||
Optional<List<String>?> notIn;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> notLike;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
Optional<String?> startsWith;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is StringPatternFilter &&
|
||||
other.endsWith == endsWith &&
|
||||
other.eq == eq &&
|
||||
_deepEquality.equals(other.in_, in_) &&
|
||||
other.like == like &&
|
||||
other.ne == ne &&
|
||||
_deepEquality.equals(other.notIn, notIn) &&
|
||||
other.notLike == notLike &&
|
||||
other.startsWith == startsWith;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(endsWith == null ? 0 : endsWith!.hashCode) +
|
||||
(eq == null ? 0 : eq!.hashCode) +
|
||||
(in_.hashCode) +
|
||||
(like == null ? 0 : like!.hashCode) +
|
||||
(ne == null ? 0 : ne!.hashCode) +
|
||||
(notIn.hashCode) +
|
||||
(notLike == null ? 0 : notLike!.hashCode) +
|
||||
(startsWith == null ? 0 : startsWith!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'StringPatternFilter[endsWith=$endsWith, eq=$eq, in_=$in_, like=$like, ne=$ne, notIn=$notIn, notLike=$notLike, startsWith=$startsWith]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.endsWith.isPresent) {
|
||||
final value = this.endsWith.value;
|
||||
json[r'endsWith'] = value;
|
||||
}
|
||||
if (this.eq.isPresent) {
|
||||
final value = this.eq.value;
|
||||
json[r'eq'] = value;
|
||||
}
|
||||
if (this.in_.isPresent) {
|
||||
final value = this.in_.value;
|
||||
json[r'in'] = value;
|
||||
}
|
||||
if (this.like.isPresent) {
|
||||
final value = this.like.value;
|
||||
json[r'like'] = value;
|
||||
}
|
||||
if (this.ne.isPresent) {
|
||||
final value = this.ne.value;
|
||||
json[r'ne'] = value;
|
||||
}
|
||||
if (this.notIn.isPresent) {
|
||||
final value = this.notIn.value;
|
||||
json[r'notIn'] = value;
|
||||
}
|
||||
if (this.notLike.isPresent) {
|
||||
final value = this.notLike.value;
|
||||
json[r'notLike'] = value;
|
||||
}
|
||||
if (this.startsWith.isPresent) {
|
||||
final value = this.startsWith.value;
|
||||
json[r'startsWith'] = value;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [StringPatternFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static StringPatternFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "StringPatternFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return StringPatternFilter(
|
||||
endsWith: json.containsKey(r'endsWith') ? Optional.present(mapValueOfType<String>(json, r'endsWith')) : const Optional.absent(),
|
||||
eq: json.containsKey(r'eq') ? Optional.present(mapValueOfType<String>(json, r'eq')) : const Optional.absent(),
|
||||
in_: json.containsKey(r'in') ? Optional.present(json[r'in'] is Iterable
|
||||
? (json[r'in'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
like: json.containsKey(r'like') ? Optional.present(mapValueOfType<String>(json, r'like')) : const Optional.absent(),
|
||||
ne: json.containsKey(r'ne') ? Optional.present(mapValueOfType<String>(json, r'ne')) : const Optional.absent(),
|
||||
notIn: json.containsKey(r'notIn') ? Optional.present(json[r'notIn'] is Iterable
|
||||
? (json[r'notIn'] as Iterable).cast<String>().toList(growable: false)
|
||||
: const []) : const Optional.absent(),
|
||||
notLike: json.containsKey(r'notLike') ? Optional.present(mapValueOfType<String>(json, r'notLike')) : const Optional.absent(),
|
||||
startsWith: json.containsKey(r'startsWith') ? Optional.present(mapValueOfType<String>(json, r'startsWith')) : const Optional.absent(),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<StringPatternFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <StringPatternFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = StringPatternFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, StringPatternFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, StringPatternFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = StringPatternFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of StringPatternFilter-objects as value to a dart map
|
||||
static Map<String, List<StringPatternFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<StringPatternFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = StringPatternFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
||||
99
mobile/openapi/lib/model/string_similarity_filter.dart
generated
Normal file
99
mobile/openapi/lib/model/string_similarity_filter.dart
generated
Normal file
@@ -0,0 +1,99 @@
|
||||
//
|
||||
// AUTO-GENERATED FILE, DO NOT MODIFY!
|
||||
//
|
||||
// @dart=2.18
|
||||
|
||||
// ignore_for_file: unused_element, unused_import
|
||||
// ignore_for_file: always_put_required_named_parameters_first
|
||||
// ignore_for_file: constant_identifier_names
|
||||
// ignore_for_file: lines_longer_than_80_chars
|
||||
|
||||
part of openapi.api;
|
||||
|
||||
class StringSimilarityFilter {
|
||||
/// Returns a new [StringSimilarityFilter] instance.
|
||||
StringSimilarityFilter({
|
||||
required this.matches,
|
||||
});
|
||||
|
||||
String matches;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is StringSimilarityFilter &&
|
||||
other.matches == matches;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(matches.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'StringSimilarityFilter[matches=$matches]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'matches'] = this.matches;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [StringSimilarityFilter] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static StringSimilarityFilter? fromJson(dynamic value) {
|
||||
upgradeDto(value, "StringSimilarityFilter");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return StringSimilarityFilter(
|
||||
matches: mapValueOfType<String>(json, r'matches')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<StringSimilarityFilter> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <StringSimilarityFilter>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = StringSimilarityFilter.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, StringSimilarityFilter> mapFromJson(dynamic json) {
|
||||
final map = <String, StringSimilarityFilter>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = StringSimilarityFilter.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of StringSimilarityFilter-objects as value to a dart map
|
||||
static Map<String, List<StringSimilarityFilter>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<StringSimilarityFilter>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = StringSimilarityFilter.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'matches',
|
||||
};
|
||||
}
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:immich_mobile/presentation/widgets/people/person_edit_birthday_modal.widget.dart';
|
||||
import 'package:intl/date_symbol_data_local.dart';
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:scroll_date_picker/scroll_date_picker.dart';
|
||||
|
||||
void main() {
|
||||
group('datePickerColumnOrder', () {
|
||||
test('month first (en_US)', () {
|
||||
expect(
|
||||
datePickerColumnOrder('M/d/y'),
|
||||
orderedEquals([DatePickerViewType.month, DatePickerViewType.day, DatePickerViewType.year]),
|
||||
);
|
||||
});
|
||||
|
||||
test('day first (pl)', () {
|
||||
expect(
|
||||
datePickerColumnOrder('dd.MM.y'),
|
||||
orderedEquals([DatePickerViewType.day, DatePickerViewType.month, DatePickerViewType.year]),
|
||||
);
|
||||
});
|
||||
|
||||
test('year first (ko)', () {
|
||||
expect(
|
||||
datePickerColumnOrder('y. M. d.'),
|
||||
orderedEquals([DatePickerViewType.year, DatePickerViewType.month, DatePickerViewType.day]),
|
||||
);
|
||||
});
|
||||
|
||||
test('null pattern falls back to package default', () {
|
||||
expect(datePickerColumnOrder(null), isNull);
|
||||
});
|
||||
|
||||
test('missing field falls back to package default', () {
|
||||
expect(datePickerColumnOrder('M/y'), isNull);
|
||||
});
|
||||
});
|
||||
|
||||
group('datePickerColumnOrder with real locale patterns', () {
|
||||
setUpAll(() async {
|
||||
await initializeDateFormatting();
|
||||
});
|
||||
|
||||
for (final (locales, order, name) in const [
|
||||
(
|
||||
['en', 'en-US', 'en-PH'],
|
||||
[DatePickerViewType.month, DatePickerViewType.day, DatePickerViewType.year],
|
||||
'month/day/year',
|
||||
),
|
||||
(
|
||||
['en-GB', 'fr', 'fr-FR', 'de', 'de-DE', 'pl'],
|
||||
[DatePickerViewType.day, DatePickerViewType.month, DatePickerViewType.year],
|
||||
'day/month/year',
|
||||
),
|
||||
(
|
||||
['ja', 'ja-JP', 'zh', 'zh-CN', 'ko', 'ko-KR'],
|
||||
[DatePickerViewType.year, DatePickerViewType.month, DatePickerViewType.day],
|
||||
'year/month/day',
|
||||
),
|
||||
(['ky'], [DatePickerViewType.year, DatePickerViewType.day, DatePickerViewType.month], 'year/day/month'),
|
||||
]) {
|
||||
for (final locale in locales) {
|
||||
test('$locale uses $name', () {
|
||||
expect(datePickerColumnOrder(DateFormat.yMd(locale).pattern), orderedEquals(order));
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1617,6 +1617,168 @@ export type SearchExploreResponseDto = {
|
||||
fieldName: string;
|
||||
items: SearchExploreItem[];
|
||||
};
|
||||
export type IdsFilter = {
|
||||
all?: string[];
|
||||
"any"?: string[];
|
||||
none?: string[];
|
||||
};
|
||||
export type StringFilter = {
|
||||
eq?: string;
|
||||
"in"?: string[];
|
||||
ne?: string;
|
||||
notIn?: string[];
|
||||
};
|
||||
export type StringFilterNullable = {
|
||||
eq?: string | null;
|
||||
"in"?: string[];
|
||||
ne?: string | null;
|
||||
notIn?: string[];
|
||||
};
|
||||
export type DateFilter = {
|
||||
eq?: string;
|
||||
gt?: string;
|
||||
gte?: string;
|
||||
lt?: string;
|
||||
lte?: string;
|
||||
ne?: string;
|
||||
};
|
||||
export type StringPatternFilter = {
|
||||
endsWith?: string;
|
||||
eq?: string | null;
|
||||
"in"?: string[];
|
||||
like?: string;
|
||||
ne?: string | null;
|
||||
notIn?: string[];
|
||||
notLike?: string;
|
||||
startsWith?: string;
|
||||
};
|
||||
export type NumberFilter = {
|
||||
eq?: number;
|
||||
gt?: number;
|
||||
gte?: number;
|
||||
"in"?: number[];
|
||||
lt?: number;
|
||||
lte?: number;
|
||||
ne?: number;
|
||||
notIn?: number[];
|
||||
};
|
||||
export type BoolFilter = {
|
||||
eq: boolean;
|
||||
};
|
||||
export type IdFilter = {
|
||||
eq?: string;
|
||||
ne?: string;
|
||||
};
|
||||
export type IdFilterNullable = {
|
||||
eq?: string | null;
|
||||
ne?: string | null;
|
||||
};
|
||||
export type StringSimilarityFilter = {
|
||||
matches: string;
|
||||
};
|
||||
export type NumberFilterNullable = {
|
||||
eq?: number | null;
|
||||
gt?: number;
|
||||
gte?: number;
|
||||
"in"?: number[];
|
||||
lt?: number;
|
||||
lte?: number;
|
||||
ne?: number | null;
|
||||
notIn?: number[];
|
||||
};
|
||||
export type DateFilterNullable = {
|
||||
eq?: string | null;
|
||||
gt?: string;
|
||||
gte?: string;
|
||||
lt?: string;
|
||||
lte?: string;
|
||||
ne?: string | null;
|
||||
};
|
||||
export type EnumFilterAssetType = {
|
||||
eq?: AssetTypeEnum;
|
||||
"in"?: AssetTypeEnum[];
|
||||
ne?: AssetTypeEnum;
|
||||
notIn?: AssetTypeEnum[];
|
||||
};
|
||||
export type EnumFilterAssetVisibility = {
|
||||
eq?: AssetVisibility;
|
||||
"in"?: AssetVisibility[];
|
||||
ne?: AssetVisibility;
|
||||
notIn?: AssetVisibility[];
|
||||
};
|
||||
export type SearchFilterBranch = {
|
||||
albumIds?: IdsFilter;
|
||||
checksum?: StringFilter;
|
||||
city?: StringFilterNullable;
|
||||
country?: StringFilterNullable;
|
||||
createdAt?: DateFilter;
|
||||
description?: StringPatternFilter;
|
||||
encodedVideoPath?: StringFilter;
|
||||
fileSizeInBytes?: NumberFilter;
|
||||
hasAlbums?: BoolFilter;
|
||||
hasPeople?: BoolFilter;
|
||||
hasTags?: BoolFilter;
|
||||
id?: IdFilter;
|
||||
isEncoded?: BoolFilter;
|
||||
isFavorite?: BoolFilter;
|
||||
isMotion?: BoolFilter;
|
||||
isOffline?: BoolFilter;
|
||||
lensModel?: StringFilterNullable;
|
||||
libraryId?: IdFilterNullable;
|
||||
make?: StringFilterNullable;
|
||||
model?: StringFilterNullable;
|
||||
ocr?: StringSimilarityFilter;
|
||||
originalFileName?: StringPatternFilter;
|
||||
originalPath?: StringPatternFilter;
|
||||
personIds?: IdsFilter;
|
||||
rating?: NumberFilterNullable;
|
||||
state?: StringFilterNullable;
|
||||
tagIds?: IdsFilter;
|
||||
takenAt?: DateFilter;
|
||||
trashedAt?: DateFilterNullable;
|
||||
"type"?: EnumFilterAssetType;
|
||||
updatedAt?: DateFilter;
|
||||
visibility?: EnumFilterAssetVisibility;
|
||||
};
|
||||
export type SearchFilter = {
|
||||
albumIds?: IdsFilter;
|
||||
checksum?: StringFilter;
|
||||
city?: StringFilterNullable;
|
||||
country?: StringFilterNullable;
|
||||
createdAt?: DateFilter;
|
||||
description?: StringPatternFilter;
|
||||
encodedVideoPath?: StringFilter;
|
||||
fileSizeInBytes?: NumberFilter;
|
||||
hasAlbums?: BoolFilter;
|
||||
hasPeople?: BoolFilter;
|
||||
hasTags?: BoolFilter;
|
||||
id?: IdFilter;
|
||||
isEncoded?: BoolFilter;
|
||||
isFavorite?: BoolFilter;
|
||||
isMotion?: BoolFilter;
|
||||
isOffline?: BoolFilter;
|
||||
lensModel?: StringFilterNullable;
|
||||
libraryId?: IdFilterNullable;
|
||||
make?: StringFilterNullable;
|
||||
model?: StringFilterNullable;
|
||||
ocr?: StringSimilarityFilter;
|
||||
or?: SearchFilterBranch[];
|
||||
originalFileName?: StringPatternFilter;
|
||||
originalPath?: StringPatternFilter;
|
||||
personIds?: IdsFilter;
|
||||
rating?: NumberFilterNullable;
|
||||
state?: StringFilterNullable;
|
||||
tagIds?: IdsFilter;
|
||||
takenAt?: DateFilter;
|
||||
trashedAt?: DateFilterNullable;
|
||||
"type"?: EnumFilterAssetType;
|
||||
updatedAt?: DateFilter;
|
||||
visibility?: EnumFilterAssetVisibility;
|
||||
};
|
||||
export type SearchOrder = {
|
||||
direction?: AssetOrder;
|
||||
field?: SearchOrderField;
|
||||
};
|
||||
export type MetadataSearchDto = {
|
||||
/** Filter by album IDs */
|
||||
albumIds?: string[];
|
||||
@@ -1630,10 +1792,13 @@ export type MetadataSearchDto = {
|
||||
createdAfter?: string;
|
||||
/** Filter by creation date (before) */
|
||||
createdBefore?: string;
|
||||
/** Cursor for the next page of results */
|
||||
cursor?: string;
|
||||
/** Filter by description text */
|
||||
description?: string;
|
||||
/** Filter by encoded video file path */
|
||||
encodedVideoPath?: string;
|
||||
filter?: SearchFilter;
|
||||
/** Filter by asset ID */
|
||||
id?: string;
|
||||
/** Filter by encoded status */
|
||||
@@ -1658,6 +1823,7 @@ export type MetadataSearchDto = {
|
||||
ocr?: string;
|
||||
/** Sort order */
|
||||
order?: AssetOrder;
|
||||
orderBy?: SearchOrder;
|
||||
/** Filter by original file name */
|
||||
originalFileName?: string;
|
||||
/** Filter by original file path */
|
||||
@@ -1725,6 +1891,8 @@ export type SearchAssetResponseDto = {
|
||||
count: number;
|
||||
facets: SearchFacetResponseDto[];
|
||||
items: AssetResponseDto[];
|
||||
/** Cursor for the next page of results */
|
||||
nextCursor: string | null;
|
||||
/** Next page token */
|
||||
nextPage: string | null;
|
||||
/** Total number of matching assets */
|
||||
@@ -1757,6 +1925,7 @@ export type RandomSearchDto = {
|
||||
createdAfter?: string;
|
||||
/** Filter by creation date (before) */
|
||||
createdBefore?: string;
|
||||
filter?: SearchFilter;
|
||||
/** Filter by encoded status */
|
||||
isEncoded?: boolean;
|
||||
/** Filter by favorite status */
|
||||
@@ -1821,6 +1990,9 @@ export type SmartSearchDto = {
|
||||
createdAfter?: string;
|
||||
/** Filter by creation date (before) */
|
||||
createdBefore?: string;
|
||||
/** Cursor for the next page of results */
|
||||
cursor?: string;
|
||||
filter?: SearchFilter;
|
||||
/** Filter by encoded status */
|
||||
isEncoded?: boolean;
|
||||
/** Filter by favorite status */
|
||||
@@ -1891,6 +2063,7 @@ export type StatisticsSearchDto = {
|
||||
createdBefore?: string;
|
||||
/** Filter by description text */
|
||||
description?: string;
|
||||
filter?: SearchFilter;
|
||||
/** Filter by encoded status */
|
||||
isEncoded?: boolean;
|
||||
/** Filter by favorite status */
|
||||
@@ -7501,6 +7674,12 @@ export enum JobName {
|
||||
IntegrityDeleteReportType = "IntegrityDeleteReportType",
|
||||
IntegrityDeleteReports = "IntegrityDeleteReports"
|
||||
}
|
||||
export enum SearchOrderField {
|
||||
FileCreatedAt = "fileCreatedAt",
|
||||
LocalDateTime = "localDateTime",
|
||||
FileSizeInBytes = "fileSizeInBytes",
|
||||
Rating = "rating"
|
||||
}
|
||||
export enum SearchSuggestionType {
|
||||
Country = "country",
|
||||
State = "state",
|
||||
|
||||
@@ -106,7 +106,11 @@ describe(MemoryController.name, () => {
|
||||
it('should require at least one field', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer()).put(`/memories/${factory.uuid()}`).send({});
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.validationError([{ path: [], message: 'At least one field must be provided' }]));
|
||||
expect(body).toEqual(
|
||||
errorDto.validationError([
|
||||
{ path: [], message: 'At least one of the following fields is required: isSaved, seenAt, memoryAt' },
|
||||
]),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -120,6 +120,16 @@ describe(SearchController.name, () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject a deprecated field combined with a new structure field', async () => {
|
||||
const { status, body } = await request(ctx.getHttpServer())
|
||||
.post('/search/metadata')
|
||||
.send({ filter: {}, city: 'Oslo' });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(
|
||||
errorDto.validationError([{ path: ['city'], message: 'Deprecated field city cannot be combined with filter' }]),
|
||||
);
|
||||
});
|
||||
|
||||
describe('POST /search/random', () => {
|
||||
it('should be an authenticated route', async () => {
|
||||
await request(ctx.getHttpServer()).post('/search/random');
|
||||
|
||||
@@ -69,7 +69,11 @@ export class SearchController {
|
||||
@Endpoint({
|
||||
summary: 'Search large assets',
|
||||
description: 'Search for assets that are considered large based on specified criteria.',
|
||||
history: new HistoryBuilder().added('v1').beta('v1').stable('v2'),
|
||||
history: new HistoryBuilder()
|
||||
.added('v1')
|
||||
.beta('v1')
|
||||
.stable('v2')
|
||||
.deprecated('v3.1.0', { replacementId: 'searchAssets' }),
|
||||
})
|
||||
searchLargeAssets(@Auth() auth: AuthDto, @Query() dto: LargeAssetSearchDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.searchLargeAssets(auth, dto);
|
||||
|
||||
@@ -304,6 +304,36 @@ export const columns = {
|
||||
'asset.height',
|
||||
'asset.isEdited',
|
||||
],
|
||||
searchAsset: [
|
||||
'asset.id',
|
||||
'asset.updateId',
|
||||
'asset.createdAt',
|
||||
'asset.updatedAt',
|
||||
'asset.deletedAt',
|
||||
'asset.status',
|
||||
'asset.checksum',
|
||||
'asset.checksumAlgorithm',
|
||||
'asset.duplicateId',
|
||||
'asset.duration',
|
||||
'asset.fileCreatedAt',
|
||||
'asset.fileModifiedAt',
|
||||
'asset.isExternal',
|
||||
'asset.isFavorite',
|
||||
'asset.isOffline',
|
||||
'asset.isEdited',
|
||||
'asset.visibility',
|
||||
'asset.libraryId',
|
||||
'asset.livePhotoVideoId',
|
||||
'asset.localDateTime',
|
||||
'asset.originalFileName',
|
||||
'asset.originalPath',
|
||||
'asset.ownerId',
|
||||
'asset.stackId',
|
||||
'asset.thumbhash',
|
||||
'asset.type',
|
||||
'asset.width',
|
||||
'asset.height',
|
||||
],
|
||||
workflowAssetV1: [
|
||||
'asset.id',
|
||||
'asset.ownerId',
|
||||
|
||||
@@ -108,9 +108,11 @@ export function ChunkedSet(options?: { paramIndex?: number; chunkSize?: number }
|
||||
}
|
||||
|
||||
const UUID = '00000000-0000-4000-a000-000000000000';
|
||||
const UUID_1 = '00000000-0000-4000-a000-000000000001';
|
||||
|
||||
export const DummyValue = {
|
||||
UUID,
|
||||
UUID_1,
|
||||
UUID_SET: new Set([UUID]),
|
||||
PAGINATION: { take: 10, skip: 0 },
|
||||
EMAIL: 'user@immich.app',
|
||||
|
||||
@@ -3,36 +3,51 @@ import { Place } from 'src/database';
|
||||
import { HistoryBuilder } from 'src/decorators';
|
||||
import { AlbumResponseSchema } from 'src/dtos/album.dto';
|
||||
import { AssetResponseSchema } from 'src/dtos/asset-response.dto';
|
||||
import { AssetOrder, AssetOrderSchema, AssetTypeSchema, AssetVisibilitySchema } from 'src/enum';
|
||||
import { isoDatetimeToDate, stringToBool } from 'src/validation';
|
||||
import {
|
||||
AssetOrder,
|
||||
AssetOrderSchema,
|
||||
AssetTypeSchema,
|
||||
AssetVisibilitySchema,
|
||||
SearchOrderField,
|
||||
SearchOrderFieldSchema,
|
||||
} from 'src/enum';
|
||||
import { isoDatetimeToDate, nonEmptyPartial, stringToBool } from 'src/validation';
|
||||
import z from 'zod';
|
||||
|
||||
const ADDED_V3_1 = new HistoryBuilder().added('v3.1.0').getExtensions();
|
||||
|
||||
// fields deprecated in favor of the structured filter tree
|
||||
const DEPRECATED_FLAT_FIELD = {
|
||||
...new HistoryBuilder().added('v1').stable('v2').deprecated('v3.1.0').getExtensions(),
|
||||
deprecated: true,
|
||||
};
|
||||
|
||||
const BaseSearchSchema = z.object({
|
||||
libraryId: z.uuidv4().nullish().describe('Library ID to filter by'),
|
||||
type: AssetTypeSchema.optional(),
|
||||
isEncoded: z.boolean().optional().describe('Filter by encoded status'),
|
||||
isFavorite: z.boolean().optional().describe('Filter by favorite status'),
|
||||
isMotion: z.boolean().optional().describe('Filter by motion photo status'),
|
||||
isOffline: z.boolean().optional().describe('Filter by offline status'),
|
||||
visibility: AssetVisibilitySchema.optional(),
|
||||
createdBefore: isoDatetimeToDate.optional().describe('Filter by creation date (before)'),
|
||||
createdAfter: isoDatetimeToDate.optional().describe('Filter by creation date (after)'),
|
||||
updatedBefore: isoDatetimeToDate.optional().describe('Filter by update date (before)'),
|
||||
updatedAfter: isoDatetimeToDate.optional().describe('Filter by update date (after)'),
|
||||
trashedBefore: isoDatetimeToDate.optional().describe('Filter by trash date (before)'),
|
||||
trashedAfter: isoDatetimeToDate.optional().describe('Filter by trash date (after)'),
|
||||
takenBefore: isoDatetimeToDate.optional().describe('Filter by taken date (before)'),
|
||||
takenAfter: isoDatetimeToDate.optional().describe('Filter by taken date (after)'),
|
||||
city: z.string().nullable().optional().describe('Filter by city name'),
|
||||
state: z.string().nullable().optional().describe('Filter by state/province name'),
|
||||
country: z.string().nullable().optional().describe('Filter by country name'),
|
||||
make: z.string().nullable().optional().describe('Filter by camera make'),
|
||||
model: z.string().nullable().optional().describe('Filter by camera model'),
|
||||
lensModel: z.string().nullable().optional().describe('Filter by lens model'),
|
||||
isNotInAlbum: z.boolean().optional().describe('Filter assets not in any album'),
|
||||
personIds: z.array(z.uuidv4()).optional().describe('Filter by person IDs'),
|
||||
tagIds: z.array(z.uuidv4()).nullish().describe('Filter by tag IDs'),
|
||||
albumIds: z.array(z.uuidv4()).optional().describe('Filter by album IDs'),
|
||||
libraryId: z.uuidv4().nullish().describe('Library ID to filter by').meta(DEPRECATED_FLAT_FIELD),
|
||||
type: AssetTypeSchema.optional().meta(DEPRECATED_FLAT_FIELD),
|
||||
isEncoded: z.boolean().optional().describe('Filter by encoded status').meta(DEPRECATED_FLAT_FIELD),
|
||||
isFavorite: z.boolean().optional().describe('Filter by favorite status').meta(DEPRECATED_FLAT_FIELD),
|
||||
isMotion: z.boolean().optional().describe('Filter by motion photo status').meta(DEPRECATED_FLAT_FIELD),
|
||||
isOffline: z.boolean().optional().describe('Filter by offline status').meta(DEPRECATED_FLAT_FIELD),
|
||||
visibility: AssetVisibilitySchema.optional().meta(DEPRECATED_FLAT_FIELD),
|
||||
createdBefore: isoDatetimeToDate.optional().describe('Filter by creation date (before)').meta(DEPRECATED_FLAT_FIELD),
|
||||
createdAfter: isoDatetimeToDate.optional().describe('Filter by creation date (after)').meta(DEPRECATED_FLAT_FIELD),
|
||||
updatedBefore: isoDatetimeToDate.optional().describe('Filter by update date (before)').meta(DEPRECATED_FLAT_FIELD),
|
||||
updatedAfter: isoDatetimeToDate.optional().describe('Filter by update date (after)').meta(DEPRECATED_FLAT_FIELD),
|
||||
trashedBefore: isoDatetimeToDate.optional().describe('Filter by trash date (before)').meta(DEPRECATED_FLAT_FIELD),
|
||||
trashedAfter: isoDatetimeToDate.optional().describe('Filter by trash date (after)').meta(DEPRECATED_FLAT_FIELD),
|
||||
takenBefore: isoDatetimeToDate.optional().describe('Filter by taken date (before)').meta(DEPRECATED_FLAT_FIELD),
|
||||
takenAfter: isoDatetimeToDate.optional().describe('Filter by taken date (after)').meta(DEPRECATED_FLAT_FIELD),
|
||||
city: z.string().nullable().optional().describe('Filter by city name').meta(DEPRECATED_FLAT_FIELD),
|
||||
state: z.string().nullable().optional().describe('Filter by state/province name').meta(DEPRECATED_FLAT_FIELD),
|
||||
country: z.string().nullable().optional().describe('Filter by country name').meta(DEPRECATED_FLAT_FIELD),
|
||||
make: z.string().nullable().optional().describe('Filter by camera make').meta(DEPRECATED_FLAT_FIELD),
|
||||
model: z.string().nullable().optional().describe('Filter by camera model').meta(DEPRECATED_FLAT_FIELD),
|
||||
lensModel: z.string().nullable().optional().describe('Filter by lens model').meta(DEPRECATED_FLAT_FIELD),
|
||||
isNotInAlbum: z.boolean().optional().describe('Filter assets not in any album').meta(DEPRECATED_FLAT_FIELD),
|
||||
personIds: z.array(z.uuidv4()).optional().describe('Filter by person IDs').meta(DEPRECATED_FLAT_FIELD),
|
||||
tagIds: z.array(z.uuidv4()).nullish().describe('Filter by tag IDs').meta(DEPRECATED_FLAT_FIELD),
|
||||
albumIds: z.array(z.uuidv4()).optional().describe('Filter by album IDs').meta(DEPRECATED_FLAT_FIELD),
|
||||
rating: z
|
||||
.int()
|
||||
.min(1)
|
||||
@@ -45,51 +60,24 @@ const BaseSearchSchema = z.object({
|
||||
.stable('v2')
|
||||
.updated('v2.6.0', 'Using -1 as a rating is deprecated and will be removed in the next major version.')
|
||||
.updated('v3', 'Using -1 as a rating is no longer valid.')
|
||||
.deprecated('v3.1.0')
|
||||
.getExtensions(),
|
||||
deprecated: true,
|
||||
}),
|
||||
ocr: z.string().optional().describe('Filter by OCR text content'),
|
||||
ocr: z.string().optional().describe('Filter by OCR text content').meta(DEPRECATED_FLAT_FIELD),
|
||||
});
|
||||
|
||||
const BaseSearchWithResultsSchema = BaseSearchSchema.extend({
|
||||
withDeleted: z.boolean().optional().describe('Include deleted assets'),
|
||||
withDeleted: z.boolean().optional().describe('Include deleted assets').meta(DEPRECATED_FLAT_FIELD),
|
||||
withExif: z.boolean().optional().describe('Include EXIF data in response'),
|
||||
size: z.int().min(1).max(1000).optional().describe('Number of results to return'),
|
||||
size: z.int().min(1).max(1000).default(250).describe('Number of results to return'),
|
||||
});
|
||||
|
||||
const RandomSearchSchema = BaseSearchWithResultsSchema.extend({
|
||||
withStacked: z.boolean().optional().describe('Include stacked assets'),
|
||||
withPeople: z.boolean().optional().describe('Include people data in response'),
|
||||
}).meta({ id: 'RandomSearchDto' });
|
||||
|
||||
const LargeAssetSearchSchema = BaseSearchWithResultsSchema.extend({
|
||||
minFileSize: z.coerce.number().int().min(0).optional().describe('Minimum file size in bytes'),
|
||||
size: z.coerce.number().int().min(1).max(1000).optional().describe('Number of results to return'),
|
||||
size: z.coerce.number().int().min(1).max(1000).default(250).describe('Number of results to return'),
|
||||
}).meta({ id: 'LargeAssetSearchDto' });
|
||||
|
||||
const MetadataSearchSchema = RandomSearchSchema.extend({
|
||||
id: z.uuidv4().optional().describe('Filter by asset ID'),
|
||||
description: z.string().trim().optional().describe('Filter by description text'),
|
||||
checksum: z.string().optional().describe('Filter by file checksum'),
|
||||
originalFileName: z.string().trim().optional().describe('Filter by original file name'),
|
||||
originalPath: z.string().optional().describe('Filter by original file path'),
|
||||
previewPath: z.string().optional().describe('Filter by preview file path'),
|
||||
thumbnailPath: z.string().optional().describe('Filter by thumbnail file path'),
|
||||
encodedVideoPath: z.string().optional().describe('Filter by encoded video file path'),
|
||||
order: AssetOrderSchema.default(AssetOrder.Desc).optional().describe('Sort order'),
|
||||
page: z.int().min(1).optional().describe('Page number'),
|
||||
}).meta({ id: 'MetadataSearchDto' });
|
||||
|
||||
const StatisticsSearchSchema = BaseSearchSchema.extend({
|
||||
description: z.string().trim().optional().describe('Filter by description text'),
|
||||
}).meta({ id: 'StatisticsSearchDto' });
|
||||
|
||||
const SmartSearchSchema = BaseSearchWithResultsSchema.extend({
|
||||
query: z.string().trim().optional().describe('Natural language search query'),
|
||||
queryAssetId: z.uuidv4().optional().describe('Asset ID to use as search reference'),
|
||||
language: z.string().optional().describe('Search language code'),
|
||||
page: z.int().min(1).optional().describe('Page number'),
|
||||
}).meta({ id: 'SmartSearchDto' });
|
||||
|
||||
const SearchPlacesSchema = z
|
||||
.object({
|
||||
name: z.string().describe('Place name to search for'),
|
||||
@@ -142,6 +130,258 @@ const SearchSuggestionRequestSchema = z
|
||||
})
|
||||
.meta({ id: 'SearchSuggestionRequestDto' });
|
||||
|
||||
const IdFilterSchema = nonEmptyPartial({
|
||||
eq: z.uuidv4(),
|
||||
ne: z.uuidv4(),
|
||||
}).meta({ id: 'IdFilter' });
|
||||
|
||||
const IdFilterNullableSchema = nonEmptyPartial({
|
||||
eq: z.uuidv4().nullable(),
|
||||
ne: z.uuidv4().nullable(),
|
||||
}).meta({ id: 'IdFilterNullable' });
|
||||
|
||||
const IdsFilterSchema = nonEmptyPartial({
|
||||
any: z.array(z.uuidv4()).min(1),
|
||||
all: z.array(z.uuidv4()).min(1),
|
||||
none: z.array(z.uuidv4()).min(1),
|
||||
}).meta({ id: 'IdsFilter' });
|
||||
|
||||
const stringListShape = {
|
||||
in: z.array(z.string()).min(1),
|
||||
notIn: z.array(z.string()).min(1),
|
||||
};
|
||||
|
||||
const StringFilterSchema = nonEmptyPartial({
|
||||
eq: z.string(),
|
||||
ne: z.string(),
|
||||
...stringListShape,
|
||||
}).meta({ id: 'StringFilter' });
|
||||
|
||||
const stringNullableShape = {
|
||||
eq: z.string().nullable(),
|
||||
ne: z.string().nullable(),
|
||||
...stringListShape,
|
||||
};
|
||||
|
||||
const StringFilterNullableSchema = nonEmptyPartial(stringNullableShape).meta({ id: 'StringFilterNullable' });
|
||||
|
||||
const StringPatternFilterSchema = nonEmptyPartial({
|
||||
...stringNullableShape,
|
||||
like: z.string().min(1),
|
||||
notLike: z.string().min(1),
|
||||
startsWith: z.string().min(1),
|
||||
endsWith: z.string().min(1),
|
||||
}).meta({ id: 'StringPatternFilter' });
|
||||
|
||||
const numberRangeShape = {
|
||||
lt: z.number(),
|
||||
lte: z.number(),
|
||||
gt: z.number(),
|
||||
gte: z.number(),
|
||||
in: z.array(z.number()).min(1),
|
||||
notIn: z.array(z.number()).min(1),
|
||||
};
|
||||
|
||||
const NumberFilterSchema = nonEmptyPartial({
|
||||
eq: z.number(),
|
||||
ne: z.number(),
|
||||
...numberRangeShape,
|
||||
}).meta({ id: 'NumberFilter' });
|
||||
|
||||
const NumberFilterNullableSchema = nonEmptyPartial({
|
||||
eq: z.number().nullable(),
|
||||
ne: z.number().nullable(),
|
||||
...numberRangeShape,
|
||||
}).meta({ id: 'NumberFilterNullable' });
|
||||
|
||||
const dateRangeShape = {
|
||||
gt: isoDatetimeToDate,
|
||||
gte: isoDatetimeToDate,
|
||||
lt: isoDatetimeToDate,
|
||||
lte: isoDatetimeToDate,
|
||||
};
|
||||
|
||||
const DateFilterSchema = nonEmptyPartial({
|
||||
eq: isoDatetimeToDate,
|
||||
ne: isoDatetimeToDate,
|
||||
...dateRangeShape,
|
||||
}).meta({ id: 'DateFilter' });
|
||||
|
||||
const DateFilterNullableSchema = nonEmptyPartial({
|
||||
eq: isoDatetimeToDate.nullable(),
|
||||
ne: isoDatetimeToDate.nullable(),
|
||||
...dateRangeShape,
|
||||
}).meta({ id: 'DateFilterNullable' });
|
||||
|
||||
const BoolFilterSchema = z.object({ eq: z.boolean() }).meta({ id: 'BoolFilter' });
|
||||
|
||||
const enumFilterSchema = <T extends z.core.util.EnumLike>(values: z.ZodEnum<T>, id: string) =>
|
||||
nonEmptyPartial({
|
||||
eq: values,
|
||||
ne: values,
|
||||
in: z.array(values).min(1),
|
||||
notIn: z.array(values).min(1),
|
||||
}).meta({ id });
|
||||
|
||||
const EnumFilterAssetTypeSchema = enumFilterSchema(AssetTypeSchema, 'EnumFilterAssetType');
|
||||
const EnumFilterAssetVisibilitySchema = enumFilterSchema(AssetVisibilitySchema, 'EnumFilterAssetVisibility');
|
||||
|
||||
const StringSimilarityFilterSchema = z
|
||||
.object({
|
||||
matches: z.string().min(1),
|
||||
})
|
||||
.meta({ id: 'StringSimilarityFilter' });
|
||||
|
||||
export const DEFAULT_SEARCH_ORDER = {
|
||||
field: SearchOrderField.FileCreatedAt,
|
||||
direction: AssetOrder.Desc,
|
||||
};
|
||||
|
||||
export const SearchOrderSchema = z
|
||||
.object({
|
||||
field: SearchOrderFieldSchema.default(DEFAULT_SEARCH_ORDER.field),
|
||||
direction: AssetOrderSchema.default(DEFAULT_SEARCH_ORDER.direction),
|
||||
})
|
||||
.meta({ id: 'SearchOrder' });
|
||||
|
||||
const SearchFilterBranchSchema = z
|
||||
.object({
|
||||
id: IdFilterSchema,
|
||||
libraryId: IdFilterNullableSchema,
|
||||
type: EnumFilterAssetTypeSchema,
|
||||
visibility: EnumFilterAssetVisibilitySchema,
|
||||
isFavorite: BoolFilterSchema,
|
||||
isMotion: BoolFilterSchema,
|
||||
isOffline: BoolFilterSchema,
|
||||
isEncoded: BoolFilterSchema,
|
||||
hasAlbums: BoolFilterSchema,
|
||||
hasPeople: BoolFilterSchema,
|
||||
hasTags: BoolFilterSchema,
|
||||
city: StringFilterNullableSchema,
|
||||
state: StringFilterNullableSchema,
|
||||
country: StringFilterNullableSchema,
|
||||
make: StringFilterNullableSchema,
|
||||
model: StringFilterNullableSchema,
|
||||
lensModel: StringFilterNullableSchema,
|
||||
description: StringPatternFilterSchema,
|
||||
originalFileName: StringPatternFilterSchema,
|
||||
originalPath: StringPatternFilterSchema,
|
||||
ocr: StringSimilarityFilterSchema,
|
||||
rating: NumberFilterNullableSchema,
|
||||
fileSizeInBytes: NumberFilterSchema,
|
||||
takenAt: DateFilterSchema,
|
||||
createdAt: DateFilterSchema,
|
||||
updatedAt: DateFilterSchema,
|
||||
trashedAt: DateFilterNullableSchema,
|
||||
personIds: IdsFilterSchema,
|
||||
tagIds: IdsFilterSchema,
|
||||
albumIds: IdsFilterSchema,
|
||||
checksum: StringFilterSchema,
|
||||
encodedVideoPath: StringFilterSchema,
|
||||
})
|
||||
.partial()
|
||||
.meta({ id: 'SearchFilterBranch' });
|
||||
|
||||
export const SearchFilterSchema = SearchFilterBranchSchema.extend({
|
||||
or: z.array(SearchFilterBranchSchema).min(1).optional(),
|
||||
}).meta({ id: 'SearchFilter' });
|
||||
|
||||
export type IdFilter = z.infer<typeof IdFilterSchema>;
|
||||
export type IdFilterNullable = z.infer<typeof IdFilterNullableSchema>;
|
||||
export type IdsFilter = z.infer<typeof IdsFilterSchema>;
|
||||
export type StringFilter = z.infer<typeof StringFilterSchema>;
|
||||
export type StringFilterNullable = z.infer<typeof StringFilterNullableSchema>;
|
||||
export type StringPatternFilter = z.infer<typeof StringPatternFilterSchema>;
|
||||
export type NumberFilter = z.infer<typeof NumberFilterSchema>;
|
||||
export type NumberFilterNullable = z.infer<typeof NumberFilterNullableSchema>;
|
||||
export type DateFilter = z.infer<typeof DateFilterSchema>;
|
||||
export type DateFilterNullable = z.infer<typeof DateFilterNullableSchema>;
|
||||
export type SearchOrder = z.infer<typeof SearchOrderSchema>;
|
||||
export type SearchFilter = z.infer<typeof SearchFilterSchema>;
|
||||
export type SearchFilterBranch = z.infer<typeof SearchFilterBranchSchema>;
|
||||
|
||||
const NEW_SHAPE_FIELDS = ['filter', 'orderBy', 'cursor'] as const;
|
||||
|
||||
export const isNewShapeRequest = (dto: Partial<Record<(typeof NEW_SHAPE_FIELDS)[number], unknown>>): boolean =>
|
||||
NEW_SHAPE_FIELDS.some((field) => dto[field] !== undefined);
|
||||
|
||||
/**
|
||||
* The structured shape and the deprecated flat search fields are mutually exclusive
|
||||
* TODO(v4): remove together with the deprecated flat fields.
|
||||
*/
|
||||
const withShapeExclusivity = <T extends z.ZodObject<z.ZodRawShape>>(schema: T) => {
|
||||
const deprecatedFields = Object.keys(schema.shape).filter(
|
||||
(field) => (schema.shape[field] as z.ZodType).meta()?.deprecated,
|
||||
);
|
||||
|
||||
return schema.superRefine((dto, ctx) => {
|
||||
const values = dto as Record<string, unknown>;
|
||||
const newShapeFields = NEW_SHAPE_FIELDS.filter((field) => values[field] !== undefined);
|
||||
if (newShapeFields.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const field of deprecatedFields) {
|
||||
if (values[field] === undefined) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ctx.addIssue({
|
||||
code: 'custom',
|
||||
path: [field],
|
||||
message: `Deprecated field ${field} cannot be combined with ${newShapeFields.join('/')}`,
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
const filterField = SearchFilterSchema.optional().meta(ADDED_V3_1);
|
||||
const cursorField = z.string().min(1).optional().describe('Cursor for the next page of results').meta(ADDED_V3_1);
|
||||
|
||||
const RandomSearchBaseSchema = BaseSearchWithResultsSchema.extend({
|
||||
withStacked: z.boolean().optional().describe('Include stacked assets'),
|
||||
withPeople: z.boolean().optional().describe('Include people data in response'),
|
||||
filter: filterField,
|
||||
});
|
||||
|
||||
const RandomSearchSchema = withShapeExclusivity(RandomSearchBaseSchema).meta({ id: 'RandomSearchDto' });
|
||||
|
||||
const MetadataSearchSchema = withShapeExclusivity(
|
||||
RandomSearchBaseSchema.extend({
|
||||
id: z.uuidv4().optional().describe('Filter by asset ID').meta(DEPRECATED_FLAT_FIELD),
|
||||
description: z.string().trim().optional().describe('Filter by description text').meta(DEPRECATED_FLAT_FIELD),
|
||||
checksum: z.string().optional().describe('Filter by file checksum').meta(DEPRECATED_FLAT_FIELD),
|
||||
originalFileName: z.string().trim().optional().describe('Filter by original file name').meta(DEPRECATED_FLAT_FIELD),
|
||||
originalPath: z.string().optional().describe('Filter by original file path').meta(DEPRECATED_FLAT_FIELD),
|
||||
previewPath: z.string().optional().describe('Filter by preview file path').meta(DEPRECATED_FLAT_FIELD),
|
||||
thumbnailPath: z.string().optional().describe('Filter by thumbnail file path').meta(DEPRECATED_FLAT_FIELD),
|
||||
encodedVideoPath: z.string().optional().describe('Filter by encoded video file path').meta(DEPRECATED_FLAT_FIELD),
|
||||
order: AssetOrderSchema.optional().describe('Sort order').meta(DEPRECATED_FLAT_FIELD),
|
||||
page: z.int().min(1).optional().describe('Page number').meta(DEPRECATED_FLAT_FIELD),
|
||||
orderBy: SearchOrderSchema.optional().meta(ADDED_V3_1),
|
||||
cursor: cursorField,
|
||||
}),
|
||||
).meta({ id: 'MetadataSearchDto' });
|
||||
|
||||
const StatisticsSearchSchema = withShapeExclusivity(
|
||||
BaseSearchSchema.extend({
|
||||
description: z.string().trim().optional().describe('Filter by description text').meta(DEPRECATED_FLAT_FIELD),
|
||||
filter: filterField,
|
||||
}),
|
||||
).meta({ id: 'StatisticsSearchDto' });
|
||||
|
||||
const SmartSearchSchema = withShapeExclusivity(
|
||||
BaseSearchWithResultsSchema.extend({
|
||||
size: z.int().min(1).max(1000).default(100).describe('Number of results to return'),
|
||||
query: z.string().trim().optional().describe('Natural language search query'),
|
||||
queryAssetId: z.uuidv4().optional().describe('Asset ID to use as search reference'),
|
||||
language: z.string().optional().describe('Search language code'),
|
||||
page: z.int().min(1).optional().describe('Page number').meta(DEPRECATED_FLAT_FIELD),
|
||||
filter: filterField,
|
||||
cursor: cursorField,
|
||||
}),
|
||||
).meta({ id: 'SmartSearchDto' });
|
||||
|
||||
export class RandomSearchDto extends createZodDto(RandomSearchSchema) {}
|
||||
export class LargeAssetSearchDto extends createZodDto(LargeAssetSearchSchema) {}
|
||||
export class MetadataSearchDto extends createZodDto(MetadataSearchSchema) {}
|
||||
@@ -195,7 +435,8 @@ const SearchAssetResponseSchema = z
|
||||
count: z.int().min(0).describe('Number of assets in this page'),
|
||||
items: z.array(AssetResponseSchema),
|
||||
facets: z.array(SearchFacetResponseSchema),
|
||||
nextPage: z.string().nullable().describe('Next page token'),
|
||||
nextPage: z.string().nullable().describe('Next page token').meta(DEPRECATED_FLAT_FIELD),
|
||||
nextCursor: z.string().nullable().describe('Cursor for the next page of results').meta(ADDED_V3_1),
|
||||
})
|
||||
.meta({ id: 'SearchAssetResponseDto' });
|
||||
|
||||
|
||||
@@ -1234,3 +1234,12 @@ export enum CalendarHeatmapType {
|
||||
Upload = 'Upload',
|
||||
Taken = 'Taken',
|
||||
}
|
||||
|
||||
export enum SearchOrderField {
|
||||
FileCreatedAt = 'fileCreatedAt',
|
||||
LocalDateTime = 'localDateTime',
|
||||
FileSizeInBytes = 'fileSizeInBytes',
|
||||
Rating = 'rating',
|
||||
}
|
||||
|
||||
export const SearchOrderFieldSchema = z.enum(SearchOrderField).meta({ id: 'SearchOrderField' });
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,12 +1,25 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { Kysely, OrderByDirection, Selectable, ShallowDehydrateObject, sql } from 'kysely';
|
||||
import { InjectKysely } from 'nestjs-kysely';
|
||||
import { columns } from 'src/database';
|
||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||
import { MapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { SearchFilter, SearchOrder } from 'src/dtos/search.dto';
|
||||
import { AssetStatus, AssetType, AssetVisibility, VectorIndex } from 'src/enum';
|
||||
import { probes } from 'src/repositories/database.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
import { anyUuid, searchAssetBuilder, withExifInner } from 'src/utils/database';
|
||||
import {
|
||||
anyUuid,
|
||||
searchAssetBuilder,
|
||||
searchAssetBuilderLegacy,
|
||||
searchMetadataV3Examples,
|
||||
searchRandomV3Examples,
|
||||
searchSmartV3Examples,
|
||||
searchStatisticsV3Examples,
|
||||
withExifInner,
|
||||
withSearchOrder,
|
||||
} from 'src/utils/database';
|
||||
import { paginationHelper } from 'src/utils/pagination';
|
||||
import z from 'zod';
|
||||
|
||||
@@ -122,6 +135,25 @@ export type AssetSearchOptions = Omit<BaseAssetSearchOptions, 'visibility'> &
|
||||
|
||||
export type AssetSearchBuilderOptions = Omit<AssetSearchOptions, 'orderDirection'>;
|
||||
|
||||
export interface AssetSearchScope {
|
||||
userIds?: string[];
|
||||
lockedOwnerId: string;
|
||||
}
|
||||
|
||||
export interface AssetSearchBuilderV3Options {
|
||||
filter?: SearchFilter;
|
||||
withExif?: boolean;
|
||||
withFaces?: boolean;
|
||||
withPeople?: boolean;
|
||||
withStacked?: boolean;
|
||||
order?: SearchOrder;
|
||||
}
|
||||
|
||||
export interface AssetSearchPaginationV3Options {
|
||||
size: number;
|
||||
offset?: number;
|
||||
}
|
||||
|
||||
export type SmartSearchOptions = SearchDateOptions &
|
||||
SearchEmbeddingOptions &
|
||||
SearchExifOptions &
|
||||
@@ -182,6 +214,7 @@ export interface GetCameraLensModelsOptions {
|
||||
export class SearchRepository {
|
||||
constructor(@InjectKysely() private db: Kysely<DB>) {}
|
||||
|
||||
// TODO(v4): remove with the deprecated flat-field search API
|
||||
@GenerateSql({
|
||||
params: [
|
||||
{ page: 1, size: 100 },
|
||||
@@ -196,9 +229,10 @@ export class SearchRepository {
|
||||
})
|
||||
async searchMetadata(pagination: SearchPaginationOptions, options: AssetSearchOptions) {
|
||||
const orderDirection = (options.orderDirection?.toLowerCase() || 'desc') as OrderByDirection;
|
||||
const items = await searchAssetBuilder(this.db, options)
|
||||
.selectAll('asset')
|
||||
const items = await searchAssetBuilderLegacy(this.db, options)
|
||||
.select(columns.searchAsset)
|
||||
.orderBy('asset.fileCreatedAt', orderDirection)
|
||||
.orderBy('asset.id', orderDirection)
|
||||
.limit(pagination.size + 1)
|
||||
.offset((pagination.page - 1) * pagination.size)
|
||||
.execute();
|
||||
@@ -206,6 +240,7 @@ export class SearchRepository {
|
||||
return paginationHelper(items, pagination.size);
|
||||
}
|
||||
|
||||
// TODO(v4): remove with the deprecated flat-field search API
|
||||
@GenerateSql({
|
||||
params: [
|
||||
{
|
||||
@@ -217,11 +252,12 @@ export class SearchRepository {
|
||||
],
|
||||
})
|
||||
searchStatistics(options: AssetSearchOptions) {
|
||||
return searchAssetBuilder(this.db, options)
|
||||
return searchAssetBuilderLegacy(this.db, options)
|
||||
.select((qb) => qb.fn.countAll<number>().as('total'))
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
// TODO(v4): remove with the deprecated flat-field search API
|
||||
@GenerateSql({
|
||||
params: [
|
||||
100,
|
||||
@@ -235,13 +271,14 @@ export class SearchRepository {
|
||||
],
|
||||
})
|
||||
async searchRandom(size: number, options: AssetSearchOptions) {
|
||||
return searchAssetBuilder(this.db, options)
|
||||
.selectAll('asset')
|
||||
return searchAssetBuilderLegacy(this.db, options)
|
||||
.select(columns.searchAsset)
|
||||
.orderBy(sql`random()`)
|
||||
.limit(size)
|
||||
.execute();
|
||||
}
|
||||
|
||||
// TODO(v4): remove with the deprecated flat-field search API
|
||||
@GenerateSql({
|
||||
params: [
|
||||
100,
|
||||
@@ -256,8 +293,8 @@ export class SearchRepository {
|
||||
})
|
||||
searchLargeAssets(size: number, options: LargeAssetSearchOptions) {
|
||||
const orderDirection = (options.orderDirection?.toLowerCase() || 'desc') as OrderByDirection;
|
||||
return searchAssetBuilder(this.db, options)
|
||||
.selectAll('asset')
|
||||
return searchAssetBuilderLegacy(this.db, options)
|
||||
.select(columns.searchAsset)
|
||||
.$call(withExifInner)
|
||||
.where('asset_exif.fileSizeInByte', '>', options.minFileSize || 0)
|
||||
.orderBy('asset_exif.fileSizeInByte', orderDirection)
|
||||
@@ -265,6 +302,7 @@ export class SearchRepository {
|
||||
.execute();
|
||||
}
|
||||
|
||||
// TODO(v4): remove with the deprecated flat-field search API
|
||||
@GenerateSql({
|
||||
params: [
|
||||
{ page: 1, size: 200 },
|
||||
@@ -285,10 +323,11 @@ export class SearchRepository {
|
||||
|
||||
return this.db.transaction().execute(async (trx) => {
|
||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Clip])}`.execute(trx);
|
||||
const items = await searchAssetBuilder(trx, options)
|
||||
.selectAll('asset')
|
||||
const items = await searchAssetBuilderLegacy(trx, options)
|
||||
.select(columns.searchAsset)
|
||||
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
|
||||
.orderBy(sql`smart_search.embedding <=> ${options.embedding}`)
|
||||
.orderBy('asset.id', 'asc')
|
||||
.limit(pagination.size + 1)
|
||||
.offset((pagination.page - 1) * pagination.size)
|
||||
.execute();
|
||||
@@ -417,7 +456,7 @@ export class SearchRepository {
|
||||
.selectFrom('asset')
|
||||
.innerJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
|
||||
.innerJoin('cte', 'asset.id', 'cte.assetId')
|
||||
.selectAll('asset')
|
||||
.select(columns.searchAsset)
|
||||
.select((eb) =>
|
||||
eb
|
||||
.fn('to_jsonb', [eb.table('asset_exif')])
|
||||
@@ -490,6 +529,64 @@ export class SearchRepository {
|
||||
return res.map((row) => row.lensModel!);
|
||||
}
|
||||
|
||||
// TODO(v4): drop the V3 suffix once the legacy methods are removed
|
||||
@GenerateSql(...searchMetadataV3Examples)
|
||||
async searchMetadataV3(
|
||||
pagination: AssetSearchPaginationV3Options,
|
||||
options: AssetSearchBuilderV3Options,
|
||||
scope: AssetSearchScope,
|
||||
) {
|
||||
const items = await withSearchOrder(searchAssetBuilder(this.db, options, scope), options.order)
|
||||
.select(columns.searchAsset)
|
||||
.limit(pagination.size + 1)
|
||||
.offset(pagination.offset ?? 0)
|
||||
.execute();
|
||||
return paginationHelper(items, pagination.size);
|
||||
}
|
||||
|
||||
// TODO(v4): drop the V3 suffix once the legacy methods are removed
|
||||
@GenerateSql(...searchRandomV3Examples)
|
||||
searchRandomV3(
|
||||
size: number,
|
||||
options: Omit<AssetSearchBuilderV3Options, 'order'>,
|
||||
scope: AssetSearchScope,
|
||||
): Promise<MapAsset[]> {
|
||||
return searchAssetBuilder(this.db, options, scope)
|
||||
.select(columns.searchAsset)
|
||||
.orderBy(sql`random()`)
|
||||
.limit(size)
|
||||
.execute();
|
||||
}
|
||||
|
||||
// TODO(v4): drop the V3 suffix once the legacy methods are removed
|
||||
@GenerateSql(...searchSmartV3Examples)
|
||||
searchSmartV3(
|
||||
pagination: AssetSearchPaginationV3Options,
|
||||
options: Omit<AssetSearchBuilderV3Options, 'order'> & { embedding: string },
|
||||
scope: AssetSearchScope,
|
||||
) {
|
||||
return this.db.transaction().execute(async (trx) => {
|
||||
await sql`set local vchordrq.probes = ${sql.lit(probes[VectorIndex.Clip])}`.execute(trx);
|
||||
const items = await searchAssetBuilder(trx, options, scope)
|
||||
.select(columns.searchAsset)
|
||||
.innerJoin('smart_search', 'asset.id', 'smart_search.assetId')
|
||||
.orderBy(sql`smart_search.embedding <=> ${options.embedding}`)
|
||||
.orderBy('asset.id', 'asc')
|
||||
.limit(pagination.size + 1)
|
||||
.offset(pagination.offset ?? 0)
|
||||
.execute();
|
||||
return paginationHelper(items, pagination.size);
|
||||
});
|
||||
}
|
||||
|
||||
// TODO(v4): drop the V3 suffix once the legacy methods are removed
|
||||
@GenerateSql(...searchStatisticsV3Examples)
|
||||
searchStatisticsV3(options: AssetSearchBuilderV3Options, scope: AssetSearchScope) {
|
||||
return searchAssetBuilder(this.db, options, scope)
|
||||
.select((qb) => qb.fn.countAll<number>().as('total'))
|
||||
.executeTakeFirstOrThrow();
|
||||
}
|
||||
|
||||
private getExifField(field: 'city' | 'state' | 'country' | 'make' | 'model' | 'lensModel', userIds: string[]) {
|
||||
return this.db
|
||||
.selectFrom('asset_exif')
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { BadRequestException, UnauthorizedException } from '@nestjs/common';
|
||||
import { mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { SearchSuggestionType } from 'src/dtos/search.dto';
|
||||
import { AssetVisibility } from 'src/enum';
|
||||
import { SearchService } from 'src/services/search.service';
|
||||
import { AssetFactory } from 'test/factories/asset.factory';
|
||||
import { AuthFactory } from 'test/factories/auth.factory';
|
||||
import { authStub } from 'test/fixtures/auth.stub';
|
||||
import { getForAsset } from 'test/mappers';
|
||||
import { newUuid } from 'test/small.factory';
|
||||
import { newTestService, ServiceMocks } from 'test/utils';
|
||||
import { beforeEach, vitest } from 'vitest';
|
||||
|
||||
@@ -215,6 +217,69 @@ describe(SearchService.name, () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('new shape routing', () => {
|
||||
it('should route a filter request to the V3 search and a flat request to the legacy search', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
|
||||
mocks.search.searchMetadataV3.mockResolvedValue({ hasNextPage: false, items: [] });
|
||||
await sut.searchMetadata(auth, { size: 250, filter: {} });
|
||||
expect(mocks.search.searchMetadataV3).toHaveBeenCalled();
|
||||
expect(mocks.search.searchMetadata).not.toHaveBeenCalled();
|
||||
|
||||
mocks.search.searchMetadata.mockResolvedValue({ hasNextPage: false, items: [] });
|
||||
await sut.searchMetadata(auth, { size: 250, city: 'Oslo' });
|
||||
expect(mocks.search.searchMetadata).toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should route statistics, random, and smart filter requests to their V3 search', async () => {
|
||||
const auth = AuthFactory.create();
|
||||
|
||||
mocks.search.searchStatisticsV3.mockResolvedValue({ total: 0 });
|
||||
await expect(sut.searchStatistics(auth, { filter: {} })).resolves.toEqual({ total: 0 });
|
||||
|
||||
mocks.search.searchRandomV3.mockResolvedValue([]);
|
||||
await expect(sut.searchRandom(auth, { size: 250, filter: {} })).resolves.toEqual([]);
|
||||
|
||||
mocks.search.searchSmartV3.mockResolvedValue({ hasNextPage: false, items: [] });
|
||||
mocks.machineLearning.encodeText.mockResolvedValue('[1, 2, 3]');
|
||||
await sut.searchSmart(auth, { size: 100, filter: {}, query: 'test' });
|
||||
expect(mocks.search.searchSmartV3).toHaveBeenCalledWith(
|
||||
{ size: 100, offset: 0 },
|
||||
expect.objectContaining({ embedding: '[1, 2, 3]' }),
|
||||
expect.objectContaining({ lockedOwnerId: expect.any(String) }),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject an invalid cursor', async () => {
|
||||
await expect(sut.searchMetadata(AuthFactory.create(), { size: 250, cursor: '???' })).rejects.toThrowError(
|
||||
new BadRequestException('Invalid cursor'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject an unelevated session whose filter could match locked assets', async () => {
|
||||
const filter = { visibility: { in: [AssetVisibility.Locked, AssetVisibility.Timeline] } };
|
||||
await expect(sut.searchMetadata(AuthFactory.create(), { size: 250, filter })).rejects.toBeInstanceOf(
|
||||
UnauthorizedException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should reject a shared link without a top-level album constraint', async () => {
|
||||
const auth = AuthFactory.from().sharedLink().build();
|
||||
|
||||
await expect(sut.searchMetadata(auth, { size: 250, filter: {} })).rejects.toThrowError(
|
||||
new BadRequestException('Shared link access is only allowed in combination with an albumIds filter'),
|
||||
);
|
||||
|
||||
const albumId = newUuid();
|
||||
mocks.access.album.checkSharedLinkAccess.mockResolvedValue(new Set([albumId]));
|
||||
await expect(
|
||||
sut.searchMetadata(auth, { size: 250, filter: { or: [{ albumIds: { any: [albumId] } }] } }),
|
||||
).rejects.toThrowError(
|
||||
new BadRequestException('Shared link access is only allowed in combination with an albumIds filter'),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('searchSmart', () => {
|
||||
beforeEach(() => {
|
||||
mocks.search.searchSmart.mockResolvedValue({ hasNextPage: false, items: [] });
|
||||
@@ -226,7 +291,7 @@ describe(SearchService.name, () => {
|
||||
machineLearning: { enabled: false },
|
||||
});
|
||||
|
||||
await expect(sut.searchSmart(authStub.user1, { query: 'test' })).rejects.toThrowError(
|
||||
await expect(sut.searchSmart(authStub.user1, { size: 100, query: 'test' })).rejects.toThrowError(
|
||||
new BadRequestException('Smart search is not enabled'),
|
||||
);
|
||||
});
|
||||
@@ -236,13 +301,13 @@ describe(SearchService.name, () => {
|
||||
machineLearning: { clip: { enabled: false } },
|
||||
});
|
||||
|
||||
await expect(sut.searchSmart(authStub.user1, { query: 'test' })).rejects.toThrowError(
|
||||
await expect(sut.searchSmart(authStub.user1, { size: 100, query: 'test' })).rejects.toThrowError(
|
||||
new BadRequestException('Smart search is not enabled'),
|
||||
);
|
||||
});
|
||||
|
||||
it('should work', async () => {
|
||||
await sut.searchSmart(authStub.user1, { query: 'test' });
|
||||
await sut.searchSmart(authStub.user1, { size: 100, query: 'test' });
|
||||
|
||||
expect(mocks.machineLearning.encodeText).toHaveBeenCalledWith(
|
||||
'test',
|
||||
@@ -250,7 +315,13 @@ describe(SearchService.name, () => {
|
||||
);
|
||||
expect(mocks.search.searchSmart).toHaveBeenCalledWith(
|
||||
{ page: 1, size: 100 },
|
||||
{ query: 'test', embedding: '[1, 2, 3]', userIds: [authStub.user1.user.id], visibility: 'not-locked' },
|
||||
{
|
||||
query: 'test',
|
||||
size: 100,
|
||||
embedding: '[1, 2, 3]',
|
||||
userIds: [authStub.user1.user.id],
|
||||
visibility: 'not-locked',
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
@@ -272,7 +343,7 @@ describe(SearchService.name, () => {
|
||||
machineLearning: { clip: { modelName: 'ViT-B-16-SigLIP__webli' } },
|
||||
});
|
||||
|
||||
await sut.searchSmart(authStub.user1, { query: 'test' });
|
||||
await sut.searchSmart(authStub.user1, { size: 100, query: 'test' });
|
||||
|
||||
expect(mocks.machineLearning.encodeText).toHaveBeenCalledWith(
|
||||
'test',
|
||||
@@ -281,7 +352,7 @@ describe(SearchService.name, () => {
|
||||
});
|
||||
|
||||
it('should use language specified in request', async () => {
|
||||
await sut.searchSmart(authStub.user1, { query: 'test', language: 'de' });
|
||||
await sut.searchSmart(authStub.user1, { size: 100, query: 'test', language: 'de' });
|
||||
|
||||
expect(mocks.machineLearning.encodeText).toHaveBeenCalledWith(
|
||||
'test',
|
||||
|
||||
@@ -1,14 +1,17 @@
|
||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||
import { LRUMap } from 'mnemonist';
|
||||
import { SystemConfig } from 'src/config';
|
||||
import { AssetMapOptions, AssetResponseDto, MapAsset, mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { mapPerson, PersonResponseDto } from 'src/dtos/person.dto';
|
||||
import {
|
||||
isNewShapeRequest,
|
||||
LargeAssetSearchDto,
|
||||
mapPlaces,
|
||||
MetadataSearchDto,
|
||||
PlacesResponseDto,
|
||||
RandomSearchDto,
|
||||
SearchFilter,
|
||||
SearchPeopleDto,
|
||||
SearchPlacesDto,
|
||||
SearchResponseDto,
|
||||
@@ -19,10 +22,17 @@ import {
|
||||
StatisticsSearchDto,
|
||||
} from 'src/dtos/search.dto';
|
||||
import { AssetOrder, AssetVisibility, Permission } from 'src/enum';
|
||||
import { AssetSearchScope } from 'src/repositories/search.repository';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { requireElevatedPermission } from 'src/utils/access';
|
||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||
import { isSmartSearchEnabled } from 'src/utils/misc';
|
||||
import { decodeSearchCursor, encodeSearchCursor } from 'src/utils/search-cursor';
|
||||
import {
|
||||
applyLockedVisibilityPolicy,
|
||||
collectFilterIds,
|
||||
hasTopLevelPositiveIdsConstraint,
|
||||
} from 'src/utils/search-filter';
|
||||
|
||||
@Injectable()
|
||||
export class SearchService extends BaseService {
|
||||
@@ -63,6 +73,10 @@ export class SearchService extends BaseService {
|
||||
}
|
||||
|
||||
async searchMetadata(auth: AuthDto, dto: MetadataSearchDto): Promise<SearchResponseDto> {
|
||||
if (isNewShapeRequest(dto)) {
|
||||
return this.searchMetadataV3(auth, dto);
|
||||
}
|
||||
|
||||
if (dto.visibility === AssetVisibility.Locked) {
|
||||
requireElevatedPermission(auth);
|
||||
}
|
||||
@@ -84,7 +98,7 @@ export class SearchService extends BaseService {
|
||||
}
|
||||
|
||||
const page = dto.page ?? 1;
|
||||
const size = dto.size || 250;
|
||||
const size = dto.size;
|
||||
const { hasNextPage, items } = await this.searchRepository.searchMetadata(
|
||||
{ page, size },
|
||||
{
|
||||
@@ -96,10 +110,14 @@ export class SearchService extends BaseService {
|
||||
},
|
||||
);
|
||||
|
||||
return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth });
|
||||
return this.mapResponse(items, { auth }, { nextPage: hasNextPage ? (page + 1).toString() : null });
|
||||
}
|
||||
|
||||
async searchStatistics(auth: AuthDto, dto: StatisticsSearchDto): Promise<SearchStatisticsResponseDto> {
|
||||
if (isNewShapeRequest(dto)) {
|
||||
return this.searchStatisticsV3(auth, dto);
|
||||
}
|
||||
|
||||
const userIds = await this.getUserIdsToSearch(auth, dto.visibility);
|
||||
if (dto.visibility === AssetVisibility.Locked) {
|
||||
requireElevatedPermission(auth);
|
||||
@@ -113,12 +131,16 @@ export class SearchService extends BaseService {
|
||||
}
|
||||
|
||||
async searchRandom(auth: AuthDto, dto: RandomSearchDto): Promise<AssetResponseDto[]> {
|
||||
if (isNewShapeRequest(dto)) {
|
||||
return this.searchRandomV3(auth, dto);
|
||||
}
|
||||
|
||||
if (dto.visibility === AssetVisibility.Locked) {
|
||||
requireElevatedPermission(auth);
|
||||
}
|
||||
|
||||
const userIds = await this.getUserIdsToSearch(auth, dto.visibility);
|
||||
const items = await this.searchRepository.searchRandom(dto.size || 250, {
|
||||
const items = await this.searchRepository.searchRandom(dto.size, {
|
||||
...dto,
|
||||
visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
|
||||
userIds,
|
||||
@@ -132,7 +154,7 @@ export class SearchService extends BaseService {
|
||||
}
|
||||
|
||||
const userIds = await this.getUserIdsToSearch(auth, dto.visibility);
|
||||
const items = await this.searchRepository.searchLargeAssets(dto.size || 250, {
|
||||
const items = await this.searchRepository.searchLargeAssets(dto.size, {
|
||||
...dto,
|
||||
visibility: dto.visibility ?? (auth.session?.hasElevatedPermission ? undefined : 'not-locked'),
|
||||
userIds,
|
||||
@@ -141,6 +163,10 @@ export class SearchService extends BaseService {
|
||||
}
|
||||
|
||||
async searchSmart(auth: AuthDto, dto: SmartSearchDto): Promise<SearchResponseDto> {
|
||||
if (isNewShapeRequest(dto)) {
|
||||
return this.searchSmartV3(auth, dto);
|
||||
}
|
||||
|
||||
if (dto.visibility === AssetVisibility.Locked) {
|
||||
requireElevatedPermission(auth);
|
||||
}
|
||||
@@ -151,30 +177,9 @@ export class SearchService extends BaseService {
|
||||
}
|
||||
|
||||
const userIds = this.getUserIdsToSearch(auth, dto.visibility);
|
||||
let embedding;
|
||||
if (dto.query) {
|
||||
const key = machineLearning.clip.modelName + dto.query + dto.language;
|
||||
embedding = this.embeddingCache.get(key);
|
||||
if (!embedding) {
|
||||
embedding = await this.machineLearningRepository.encodeText(dto.query, {
|
||||
modelName: machineLearning.clip.modelName,
|
||||
language: dto.language,
|
||||
});
|
||||
this.embeddingCache.set(key, embedding);
|
||||
}
|
||||
} else if (dto.queryAssetId) {
|
||||
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.queryAssetId] });
|
||||
const getEmbeddingResponse = await this.searchRepository.getEmbedding(dto.queryAssetId);
|
||||
const assetEmbedding = getEmbeddingResponse?.embedding;
|
||||
if (!assetEmbedding) {
|
||||
throw new BadRequestException(`Asset ${dto.queryAssetId} has no embedding`);
|
||||
}
|
||||
embedding = assetEmbedding;
|
||||
} else {
|
||||
throw new BadRequestException('Either `query` or `queryAssetId` must be set');
|
||||
}
|
||||
const embedding = await this.resolveEmbedding(auth, dto, machineLearning);
|
||||
const page = dto.page ?? 1;
|
||||
const size = dto.size || 100;
|
||||
const size = dto.size;
|
||||
const { hasNextPage, items } = await this.searchRepository.searchSmart(
|
||||
{ page, size },
|
||||
{
|
||||
@@ -185,7 +190,7 @@ export class SearchService extends BaseService {
|
||||
},
|
||||
);
|
||||
|
||||
return this.mapResponse(items, hasNextPage ? (page + 1).toString() : null, { auth });
|
||||
return this.mapResponse(items, { auth }, { nextPage: hasNextPage ? (page + 1).toString() : null });
|
||||
}
|
||||
|
||||
async getAssetsByCity(auth: AuthDto): Promise<AssetResponseDto[]> {
|
||||
@@ -229,6 +234,122 @@ export class SearchService extends BaseService {
|
||||
}
|
||||
}
|
||||
|
||||
private async searchMetadataV3(auth: AuthDto, dto: MetadataSearchDto): Promise<SearchResponseDto> {
|
||||
const { filter, scope } = await this.resolveSearchScopeV3(auth, dto);
|
||||
|
||||
const { offset } = decodeSearchCursor(dto.cursor);
|
||||
const size = dto.size;
|
||||
const { hasNextPage, items } = await this.searchRepository.searchMetadataV3(
|
||||
{ size, offset },
|
||||
{
|
||||
filter,
|
||||
withExif: dto.withExif,
|
||||
withPeople: dto.withPeople,
|
||||
withStacked: dto.withStacked,
|
||||
order: dto.orderBy,
|
||||
},
|
||||
scope,
|
||||
);
|
||||
|
||||
return this.mapResponse(items, { auth }, { nextCursor: hasNextPage ? encodeSearchCursor(offset + size) : null });
|
||||
}
|
||||
|
||||
private async searchStatisticsV3(auth: AuthDto, dto: StatisticsSearchDto): Promise<SearchStatisticsResponseDto> {
|
||||
const { filter, scope } = await this.resolveSearchScopeV3(auth, dto);
|
||||
return this.searchRepository.searchStatisticsV3({ filter }, scope);
|
||||
}
|
||||
|
||||
private async searchRandomV3(auth: AuthDto, dto: RandomSearchDto): Promise<AssetResponseDto[]> {
|
||||
const { filter, scope } = await this.resolveSearchScopeV3(auth, dto);
|
||||
const items = await this.searchRepository.searchRandomV3(
|
||||
dto.size,
|
||||
{
|
||||
filter,
|
||||
withExif: dto.withExif,
|
||||
withPeople: dto.withPeople,
|
||||
withStacked: dto.withStacked,
|
||||
},
|
||||
scope,
|
||||
);
|
||||
return items.map((item) => mapAsset(item, { auth }));
|
||||
}
|
||||
|
||||
private async searchSmartV3(auth: AuthDto, dto: SmartSearchDto): Promise<SearchResponseDto> {
|
||||
const { machineLearning } = await this.getConfig({ withCache: false });
|
||||
if (!isSmartSearchEnabled(machineLearning)) {
|
||||
throw new BadRequestException('Smart search is not enabled');
|
||||
}
|
||||
|
||||
const [{ filter, scope }, embedding] = await Promise.all([
|
||||
this.resolveSearchScopeV3(auth, dto),
|
||||
this.resolveEmbedding(auth, dto, machineLearning),
|
||||
]);
|
||||
|
||||
const { offset } = decodeSearchCursor(dto.cursor);
|
||||
const size = dto.size;
|
||||
const { hasNextPage, items } = await this.searchRepository.searchSmartV3(
|
||||
{ size, offset },
|
||||
{ filter, withExif: dto.withExif, embedding },
|
||||
scope,
|
||||
);
|
||||
|
||||
return this.mapResponse(items, { auth }, { nextCursor: hasNextPage ? encodeSearchCursor(offset + size) : null });
|
||||
}
|
||||
|
||||
private async resolveSearchScopeV3(
|
||||
auth: AuthDto,
|
||||
dto: { filter?: SearchFilter },
|
||||
): Promise<{ filter: SearchFilter; scope: AssetSearchScope }> {
|
||||
const filter = dto.filter ?? {};
|
||||
const effectiveFilter = applyLockedVisibilityPolicy(auth, filter);
|
||||
|
||||
// only a top-level any/all constrains every result to accessible albums; anything else keeps
|
||||
// the ownership scope so an OR branch cannot widen the search to other users' assets
|
||||
const hasTopLevelAlbums = hasTopLevelPositiveIdsConstraint(filter, 'albumIds');
|
||||
if (auth.sharedLink && !hasTopLevelAlbums) {
|
||||
throw new BadRequestException('Shared link access is only allowed in combination with an albumIds filter');
|
||||
}
|
||||
|
||||
const albumIds = collectFilterIds(filter, 'albumIds');
|
||||
const [userIds] = await Promise.all([
|
||||
hasTopLevelAlbums ? undefined : this.getUserIdsToSearch(auth),
|
||||
albumIds.length > 0 ? this.requireAccess({ auth, ids: albumIds, permission: Permission.AlbumRead }) : undefined,
|
||||
]);
|
||||
|
||||
return { filter: effectiveFilter, scope: { userIds, lockedOwnerId: auth.user.id } };
|
||||
}
|
||||
|
||||
private async resolveEmbedding(
|
||||
auth: AuthDto,
|
||||
dto: SmartSearchDto,
|
||||
machineLearning: SystemConfig['machineLearning'],
|
||||
): Promise<string> {
|
||||
if (dto.query) {
|
||||
const key = machineLearning.clip.modelName + dto.query + dto.language;
|
||||
let embedding = this.embeddingCache.get(key);
|
||||
if (!embedding) {
|
||||
embedding = await this.machineLearningRepository.encodeText(dto.query, {
|
||||
modelName: machineLearning.clip.modelName,
|
||||
language: dto.language,
|
||||
});
|
||||
this.embeddingCache.set(key, embedding);
|
||||
}
|
||||
return embedding;
|
||||
}
|
||||
|
||||
if (dto.queryAssetId) {
|
||||
await this.requireAccess({ auth, permission: Permission.AssetRead, ids: [dto.queryAssetId] });
|
||||
const getEmbeddingResponse = await this.searchRepository.getEmbedding(dto.queryAssetId);
|
||||
const assetEmbedding = getEmbeddingResponse?.embedding;
|
||||
if (!assetEmbedding) {
|
||||
throw new BadRequestException(`Asset ${dto.queryAssetId} has no embedding`);
|
||||
}
|
||||
return assetEmbedding;
|
||||
}
|
||||
|
||||
throw new BadRequestException('Either `query` or `queryAssetId` must be set');
|
||||
}
|
||||
|
||||
private async getUserIdsToSearch(auth: AuthDto, visibility?: AssetVisibility): Promise<string[]> {
|
||||
// Locked assets are personal. Never include partner IDs, regardless of A's elevated session.
|
||||
if (visibility === AssetVisibility.Locked) {
|
||||
@@ -242,7 +363,11 @@ export class SearchService extends BaseService {
|
||||
return [auth.user.id, ...partnerIds];
|
||||
}
|
||||
|
||||
private mapResponse(assets: MapAsset[], nextPage: string | null, options: AssetMapOptions): SearchResponseDto {
|
||||
private mapResponse(
|
||||
assets: MapAsset[],
|
||||
options: AssetMapOptions,
|
||||
page: { nextPage?: string | null; nextCursor?: string | null } = {},
|
||||
): SearchResponseDto {
|
||||
return {
|
||||
albums: { total: 0, count: 0, items: [], facets: [] },
|
||||
assets: {
|
||||
@@ -250,7 +375,8 @@ export class SearchService extends BaseService {
|
||||
count: assets.length,
|
||||
items: assets.map((asset) => mapAsset(asset, options)),
|
||||
facets: [],
|
||||
nextPage,
|
||||
nextPage: page.nextPage ?? null,
|
||||
nextCursor: page.nextCursor ?? null,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -7,21 +7,46 @@ import {
|
||||
Kysely,
|
||||
KyselyConfig,
|
||||
NotNull,
|
||||
OperandValueExpression,
|
||||
ReferenceExpression,
|
||||
Selectable,
|
||||
SelectQueryBuilder,
|
||||
ShallowDehydrateObject,
|
||||
sql,
|
||||
SqlBool,
|
||||
} from 'kysely';
|
||||
import { PostgresJSDialect } from 'kysely-postgres-js';
|
||||
import { jsonArrayFrom, jsonObjectFrom } from 'kysely/helpers/postgres';
|
||||
import { Notice, PostgresError } from 'postgres';
|
||||
import { columns, lockableProperties, LockableProperty, Person } from 'src/database';
|
||||
import { DummyValue, GenerateSqlQueries } from 'src/decorators';
|
||||
import { AssetEditActionItem } from 'src/dtos/editing.dto';
|
||||
import { AssetFileType, AssetOrderBy, AssetVisibility, DatabaseExtension, ExifOrientation } from 'src/enum';
|
||||
import { AssetSearchBuilderOptions } from 'src/repositories/search.repository';
|
||||
import {
|
||||
DEFAULT_SEARCH_ORDER,
|
||||
IdsFilter,
|
||||
SearchFilterBranch,
|
||||
SearchOrder,
|
||||
StringFilter,
|
||||
StringPatternFilter,
|
||||
} from 'src/dtos/search.dto';
|
||||
import {
|
||||
AssetFileType,
|
||||
AssetOrder,
|
||||
AssetOrderBy,
|
||||
AssetVisibility,
|
||||
DatabaseExtension,
|
||||
ExifOrientation,
|
||||
SearchOrderField,
|
||||
} from 'src/enum';
|
||||
import {
|
||||
AssetSearchBuilderOptions,
|
||||
AssetSearchBuilderV3Options,
|
||||
AssetSearchScope,
|
||||
} from 'src/repositories/search.repository';
|
||||
import { DB } from 'src/schema';
|
||||
import { AssetExifTable } from 'src/schema/tables/asset-exif.table';
|
||||
import { AudioStreamInfo, VectorExtension, VideoFormat, VideoPacketInfo, VideoStreamInfo } from 'src/types';
|
||||
import { fromChecksum } from 'src/utils/request';
|
||||
|
||||
export const getKyselyConfig = (connection: DatabaseConnectionParams): KyselyConfig => {
|
||||
return {
|
||||
@@ -54,6 +79,8 @@ export const getKyselyConfig = (connection: DatabaseConnectionParams): KyselyCon
|
||||
};
|
||||
};
|
||||
|
||||
const uniqueIds = (ids: string[]) => [...new Set(ids)];
|
||||
|
||||
export const asUuid = (id: string | Expression<string>) => sql<string>`${id}::uuid`;
|
||||
|
||||
export const anyUuid = (ids: string[]) => sql<string>`any(${`{${ids}}`}::uuid[])`;
|
||||
@@ -85,16 +112,15 @@ export function withDefaultVisibility<O>(qb: SelectQueryBuilder<DB, 'asset', O>)
|
||||
return qb.where('asset.visibility', 'in', [sql.lit(AssetVisibility.Archive), sql.lit(AssetVisibility.Timeline)]);
|
||||
}
|
||||
|
||||
const selectExifInfo = (eb: AssetExpressionBuilder) =>
|
||||
eb.fn
|
||||
.toJson(eb.table('asset_exif'))
|
||||
.$castTo<ShallowDehydrateObject<Selectable<AssetExifTable>> | null>()
|
||||
.as('exifInfo');
|
||||
|
||||
// TODO come up with a better query that only selects the fields we need
|
||||
export function withExif<O>(qb: SelectQueryBuilder<DB, 'asset', O>) {
|
||||
return qb
|
||||
.leftJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
|
||||
.select((eb) =>
|
||||
eb.fn
|
||||
.toJson(eb.table('asset_exif'))
|
||||
.$castTo<ShallowDehydrateObject<Selectable<AssetExifTable>> | null>()
|
||||
.as('exifInfo'),
|
||||
);
|
||||
return qb.leftJoin('asset_exif', 'asset.id', 'asset_exif.assetId').select(selectExifInfo);
|
||||
}
|
||||
|
||||
export function withExifInner<O>(qb: SelectQueryBuilder<DB, 'asset', O>) {
|
||||
@@ -373,7 +399,7 @@ export function withEdits(eb: ExpressionBuilder<DB, 'asset'>): AliasedEditAction
|
||||
const joinDeduplicationPlugin = new DeduplicateJoinsPlugin();
|
||||
/** TODO: This should only be used for search-related queries, not as a general purpose query builder */
|
||||
|
||||
export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuilderOptions) {
|
||||
export function searchAssetBuilderLegacy(kysely: Kysely<DB>, options: AssetSearchBuilderOptions) {
|
||||
options.withDeleted ||= !!(options.trashedAfter || options.trashedBefore || options.isOffline);
|
||||
|
||||
return kysely
|
||||
@@ -493,6 +519,528 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
|
||||
.$if(!options.withDeleted, (qb) => qb.where('asset.deletedAt', 'is', null));
|
||||
}
|
||||
|
||||
type AssetExpressionBuilder = ExpressionBuilder<DB, 'asset' | 'asset_exif'>;
|
||||
|
||||
const albumAssets = (eb: AssetExpressionBuilder) =>
|
||||
eb.selectFrom('album_asset').whereRef('album_asset.assetId', '=', 'asset.id');
|
||||
|
||||
const visibleFaces = (eb: AssetExpressionBuilder) =>
|
||||
eb
|
||||
.selectFrom('asset_face')
|
||||
.whereRef('asset_face.assetId', '=', 'asset.id')
|
||||
.where('asset_face.deletedAt', 'is', null)
|
||||
.where('asset_face.isVisible', '=', true);
|
||||
|
||||
const tagAssets = (eb: AssetExpressionBuilder) =>
|
||||
eb.selectFrom('tag_asset').whereRef('tag_asset.assetId', '=', 'asset.id');
|
||||
|
||||
// shared any/all/none mechanics; `matchesAll` only receives deduplicated multi-id lists,
|
||||
// so its `count(distinct id) = ids.length` check stays satisfiable
|
||||
function idsPredicates(
|
||||
eb: AssetExpressionBuilder,
|
||||
{ any, all, none }: IdsFilter = {},
|
||||
ops: {
|
||||
matchesAny: (ids: string[]) => Expression<SqlBool>;
|
||||
matchesAll: (ids: string[]) => Expression<SqlBool>;
|
||||
},
|
||||
) {
|
||||
const predicates: Expression<SqlBool>[] = [];
|
||||
if (any) {
|
||||
predicates.push(ops.matchesAny(any));
|
||||
}
|
||||
if (all) {
|
||||
const ids = uniqueIds(all);
|
||||
predicates.push(ids.length === 1 ? ops.matchesAny(ids) : ops.matchesAll(ids));
|
||||
}
|
||||
if (none) {
|
||||
predicates.push(eb.not(ops.matchesAny(none)));
|
||||
}
|
||||
return predicates;
|
||||
}
|
||||
|
||||
function albumIdsPredicates(eb: AssetExpressionBuilder, filter?: IdsFilter) {
|
||||
const matching = (ids: string[]) => albumAssets(eb).where('album_asset.albumId', '=', anyUuid(ids));
|
||||
return idsPredicates(eb, filter, {
|
||||
matchesAny: (ids) => eb.exists(matching(ids)),
|
||||
matchesAll: (ids) =>
|
||||
eb.exists(
|
||||
matching(ids)
|
||||
.select('album_asset.assetId')
|
||||
.groupBy('album_asset.assetId')
|
||||
.having((eb) => eb.fn.count('album_asset.albumId').distinct(), '=', ids.length),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function personIdsPredicates(eb: AssetExpressionBuilder, filter?: IdsFilter) {
|
||||
const matching = (ids: string[]) => visibleFaces(eb).where('asset_face.personId', '=', anyUuid(ids));
|
||||
return idsPredicates(eb, filter, {
|
||||
matchesAny: (ids) => eb.exists(matching(ids)),
|
||||
matchesAll: (ids) =>
|
||||
eb.exists(
|
||||
matching(ids)
|
||||
.select('asset_face.assetId')
|
||||
.groupBy('asset_face.assetId')
|
||||
.having((eb) => eb.fn.count('asset_face.personId').distinct(), '=', ids.length),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
function tagIdsPredicates(eb: AssetExpressionBuilder, filter?: IdsFilter) {
|
||||
const matching = (ids: string[]) =>
|
||||
tagAssets(eb)
|
||||
.innerJoin('tag_closure', 'tag_asset.tagId', 'tag_closure.id_descendant')
|
||||
.where('tag_closure.id_ancestor', '=', anyUuid(ids));
|
||||
return idsPredicates(eb, filter, {
|
||||
matchesAny: (ids) => eb.exists(matching(ids)),
|
||||
matchesAll: (ids) =>
|
||||
eb.exists(
|
||||
matching(ids)
|
||||
.select('tag_asset.assetId')
|
||||
.groupBy('tag_asset.assetId')
|
||||
.having((eb) => eb.fn.count('tag_closure.id_ancestor').distinct(), '=', ids.length),
|
||||
),
|
||||
});
|
||||
}
|
||||
|
||||
type ComparisonFilter<T> = {
|
||||
eq?: T | null;
|
||||
ne?: T | null;
|
||||
lt?: T;
|
||||
lte?: T;
|
||||
gt?: T;
|
||||
gte?: T;
|
||||
in?: T[];
|
||||
notIn?: T[];
|
||||
};
|
||||
|
||||
// one operator dispatch for every filter shape; the DTO schemas constrain which
|
||||
// operators (and null literals) each filter can actually carry
|
||||
function comparisonPredicates<TB extends keyof DB, RE extends ReferenceExpression<DB, TB>>(
|
||||
eb: ExpressionBuilder<DB, TB>,
|
||||
column: RE,
|
||||
filter: ComparisonFilter<OperandValueExpression<DB, TB, RE>> = {},
|
||||
) {
|
||||
const predicates: Expression<SqlBool>[] = [];
|
||||
if (filter.eq !== undefined) {
|
||||
predicates.push(filter.eq === null ? eb(column, 'is', null) : eb(column, '=', filter.eq));
|
||||
}
|
||||
if (filter.ne !== undefined) {
|
||||
predicates.push(filter.ne === null ? eb(column, 'is not', null) : eb(column, '!=', filter.ne));
|
||||
}
|
||||
if (filter.lt !== undefined) {
|
||||
predicates.push(eb(column, '<', filter.lt));
|
||||
}
|
||||
if (filter.lte !== undefined) {
|
||||
predicates.push(eb(column, '<=', filter.lte));
|
||||
}
|
||||
if (filter.gt !== undefined) {
|
||||
predicates.push(eb(column, '>', filter.gt));
|
||||
}
|
||||
if (filter.gte !== undefined) {
|
||||
predicates.push(eb(column, '>=', filter.gte));
|
||||
}
|
||||
if (filter.in !== undefined) {
|
||||
predicates.push(eb(column, 'in', filter.in));
|
||||
}
|
||||
if (filter.notIn !== undefined) {
|
||||
predicates.push(eb(column, 'not in', filter.notIn));
|
||||
}
|
||||
return predicates;
|
||||
}
|
||||
|
||||
type StringColumn =
|
||||
| 'asset_exif.city'
|
||||
| 'asset_exif.state'
|
||||
| 'asset_exif.country'
|
||||
| 'asset_exif.make'
|
||||
| 'asset_exif.model'
|
||||
| 'asset_exif.lensModel'
|
||||
| 'asset_exif.description'
|
||||
| 'asset.originalFileName'
|
||||
| 'asset.originalPath';
|
||||
|
||||
function stringPatternPredicates(eb: AssetExpressionBuilder, column: StringColumn, filter: StringPatternFilter = {}) {
|
||||
const ref = sql.ref(column);
|
||||
const predicates = comparisonPredicates(eb, column, filter);
|
||||
if (filter.like !== undefined) {
|
||||
predicates.push(sql<SqlBool>`f_unaccent(${ref}) ilike ('%' || f_unaccent(${filter.like}) || '%')`);
|
||||
}
|
||||
if (filter.notLike !== undefined) {
|
||||
predicates.push(sql<SqlBool>`f_unaccent(${ref}) not ilike ('%' || f_unaccent(${filter.notLike}) || '%')`);
|
||||
}
|
||||
if (filter.startsWith !== undefined) {
|
||||
predicates.push(sql<SqlBool>`f_unaccent(${ref}) ilike (f_unaccent(${filter.startsWith}) || '%')`);
|
||||
}
|
||||
if (filter.endsWith !== undefined) {
|
||||
predicates.push(sql<SqlBool>`f_unaccent(${ref}) ilike ('%' || f_unaccent(${filter.endsWith}))`);
|
||||
}
|
||||
return predicates;
|
||||
}
|
||||
|
||||
function checksumPredicates(eb: AssetExpressionBuilder, filter: StringFilter = {}) {
|
||||
return comparisonPredicates(eb, 'asset.checksum', {
|
||||
eq: filter.eq === undefined ? undefined : fromChecksum(filter.eq),
|
||||
ne: filter.ne === undefined ? undefined : fromChecksum(filter.ne),
|
||||
in: filter.in?.map((checksum) => fromChecksum(checksum)),
|
||||
notIn: filter.notIn?.map((checksum) => fromChecksum(checksum)),
|
||||
});
|
||||
}
|
||||
|
||||
const encodedVideoFiles = (eb: AssetExpressionBuilder) =>
|
||||
eb
|
||||
.selectFrom('asset_file')
|
||||
.whereRef('asset_file.assetId', '=', 'asset.id')
|
||||
.where('asset_file.type', '=', AssetFileType.EncodedVideo);
|
||||
|
||||
function existsPredicates(
|
||||
eb: AssetExpressionBuilder,
|
||||
filter: { eq: boolean } | undefined,
|
||||
subquery: () => Expression<unknown>,
|
||||
): Expression<SqlBool>[] {
|
||||
if (!filter) {
|
||||
return [];
|
||||
}
|
||||
const exists = eb.exists(subquery());
|
||||
return [filter.eq ? exists : eb.not(exists)];
|
||||
}
|
||||
|
||||
// predicates are collected as expressions rather than chained `where` calls so the same
|
||||
// helpers can build each `or` branch, which must compose into eb.and/eb.or
|
||||
function branchPredicates(eb: AssetExpressionBuilder, branch: SearchFilterBranch) {
|
||||
const { encodedVideoPath } = branch;
|
||||
return [
|
||||
...comparisonPredicates(eb, 'asset.id', branch.id),
|
||||
...comparisonPredicates(eb, 'asset.libraryId', branch.libraryId),
|
||||
...comparisonPredicates(eb, 'asset.type', branch.type),
|
||||
...comparisonPredicates(eb, 'asset.visibility', branch.visibility),
|
||||
...(branch.isFavorite ? [eb('asset.isFavorite', '=', branch.isFavorite.eq)] : []),
|
||||
...(branch.isOffline ? [eb('asset.isOffline', '=', branch.isOffline.eq)] : []),
|
||||
...(branch.isMotion ? [eb('asset.livePhotoVideoId', branch.isMotion.eq ? 'is not' : 'is', null)] : []),
|
||||
...existsPredicates(eb, branch.isEncoded, () => encodedVideoFiles(eb)),
|
||||
...existsPredicates(eb, branch.hasAlbums, () => albumAssets(eb)),
|
||||
...existsPredicates(eb, branch.hasPeople, () => visibleFaces(eb)),
|
||||
...existsPredicates(eb, branch.hasTags, () => tagAssets(eb)),
|
||||
...comparisonPredicates(eb, 'asset_exif.city', branch.city),
|
||||
...comparisonPredicates(eb, 'asset_exif.state', branch.state),
|
||||
...comparisonPredicates(eb, 'asset_exif.country', branch.country),
|
||||
...comparisonPredicates(eb, 'asset_exif.make', branch.make),
|
||||
...comparisonPredicates(eb, 'asset_exif.model', branch.model),
|
||||
...comparisonPredicates(eb, 'asset_exif.lensModel', branch.lensModel),
|
||||
...stringPatternPredicates(eb, 'asset_exif.description', branch.description),
|
||||
...stringPatternPredicates(eb, 'asset.originalFileName', branch.originalFileName),
|
||||
...stringPatternPredicates(eb, 'asset.originalPath', branch.originalPath),
|
||||
...(branch.ocr
|
||||
? [
|
||||
eb.exists(
|
||||
eb
|
||||
.selectFrom('ocr_search')
|
||||
.whereRef('ocr_search.assetId', '=', 'asset.id')
|
||||
.where(
|
||||
sql<SqlBool>`f_unaccent(ocr_search.text) %>> f_unaccent(${tokenizeForSearch(branch.ocr.matches).join(' ')})`,
|
||||
),
|
||||
),
|
||||
]
|
||||
: []),
|
||||
...comparisonPredicates(eb, 'asset_exif.rating', branch.rating),
|
||||
...comparisonPredicates(eb, 'asset_exif.fileSizeInByte', branch.fileSizeInBytes),
|
||||
...comparisonPredicates(eb, 'asset.fileCreatedAt', branch.takenAt),
|
||||
...comparisonPredicates(eb, 'asset.createdAt', branch.createdAt),
|
||||
...comparisonPredicates(eb, 'asset.updatedAt', branch.updatedAt),
|
||||
...comparisonPredicates(eb, 'asset.deletedAt', branch.trashedAt),
|
||||
...albumIdsPredicates(eb, branch.albumIds),
|
||||
...personIdsPredicates(eb, branch.personIds),
|
||||
...tagIdsPredicates(eb, branch.tagIds),
|
||||
...checksumPredicates(eb, branch.checksum),
|
||||
...(encodedVideoPath
|
||||
? [
|
||||
eb.exists(
|
||||
encodedVideoFiles(eb)
|
||||
.where('asset_file.isEdited', '=', false)
|
||||
.where((eb) => eb.and(comparisonPredicates(eb, 'asset_file.path', encodedVideoPath))),
|
||||
),
|
||||
]
|
||||
: []),
|
||||
];
|
||||
}
|
||||
|
||||
// ordering is deliberately left to the caller so aggregate-only consumers (counts, stats)
|
||||
// can compose the same filters without stripping an order by
|
||||
export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuilderV3Options, scope: AssetSearchScope) {
|
||||
const filter = options.filter ?? {};
|
||||
|
||||
return (
|
||||
kysely
|
||||
.withPlugin(joinDeduplicationPlugin)
|
||||
.selectFrom('asset')
|
||||
// postgres eliminates the left join when no exif column is referenced, so unused joins are free
|
||||
.leftJoin('asset_exif', 'asset.id', 'asset_exif.assetId')
|
||||
.$if(!!options.withExif, (qb) => qb.select(selectExifInfo))
|
||||
.$if(!!scope.userIds && scope.userIds.length > 0, (qb) => qb.where('asset.ownerId', '=', anyUuid(scope.userIds!)))
|
||||
.where((eb) =>
|
||||
eb.or([eb('asset.visibility', '!=', AssetVisibility.Locked), eb('asset.ownerId', '=', scope.lockedOwnerId)]),
|
||||
)
|
||||
.$if(!!(options.withFaces || options.withPeople), (qb) => qb.select(withFacesAndPeople))
|
||||
.$if(options.withStacked === false, (qb) => qb.where('asset.stackId', 'is', null))
|
||||
.where((eb) => {
|
||||
const predicates = branchPredicates(eb, filter);
|
||||
if (filter.or && filter.or.length > 0) {
|
||||
predicates.push(eb.or(filter.or.map((branch) => eb.and(branchPredicates(eb, branch)))));
|
||||
}
|
||||
return predicates.length > 0 ? eb.and(predicates) : eb.lit(true);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
const searchOrderColumns = {
|
||||
[SearchOrderField.FileCreatedAt]: { column: 'asset.fileCreatedAt', nullable: false },
|
||||
[SearchOrderField.LocalDateTime]: { column: 'asset.localDateTime', nullable: false },
|
||||
[SearchOrderField.FileSizeInBytes]: { column: 'asset_exif.fileSizeInByte', nullable: true },
|
||||
[SearchOrderField.Rating]: { column: 'asset_exif.rating', nullable: true },
|
||||
} as const;
|
||||
|
||||
export function withSearchOrder(qb: ReturnType<typeof searchAssetBuilder>, order?: SearchOrder) {
|
||||
const { field, direction } = order ?? DEFAULT_SEARCH_ORDER;
|
||||
const { column, nullable } = searchOrderColumns[field];
|
||||
return (
|
||||
qb
|
||||
.orderBy(column, (ob) => {
|
||||
const ordered = direction === AssetOrder.Asc ? ob.asc() : ob.desc();
|
||||
// nulls last: assets without an asset_exif row would otherwise lead descending results
|
||||
return nullable ? ordered.nullsLast() : ordered;
|
||||
})
|
||||
// id tie-break for deterministic pagination
|
||||
.orderBy('asset.id', direction)
|
||||
);
|
||||
}
|
||||
|
||||
const scopeExample: AssetSearchScope = { userIds: [DummyValue.UUID], lockedOwnerId: DummyValue.UUID };
|
||||
|
||||
export const searchMetadataV3Examples: GenerateSqlQueries[] = [
|
||||
{ name: 'baseline', params: [{ size: 100 }, {}, scopeExample] },
|
||||
{ name: 'empty', params: [{ size: 100 }, {}, { lockedOwnerId: DummyValue.UUID }] },
|
||||
{
|
||||
name: 'or-exif-only',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: { or: [{ city: { eq: DummyValue.STRING } }] },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'string-eq-null',
|
||||
params: [{ size: 100 }, { filter: { city: { eq: null } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'string-pattern-like',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: { description: { like: DummyValue.STRING } },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'string-pattern-notLike',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: { description: { notLike: DummyValue.STRING } },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'string-pattern-startsWith',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: { originalFileName: { startsWith: DummyValue.STRING } },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'string-similarity-ocr',
|
||||
params: [{ size: 100 }, { filter: { ocr: { matches: DummyValue.STRING } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'ids-any',
|
||||
params: [{ size: 100 }, { filter: { albumIds: { any: [DummyValue.UUID] } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'ids-all',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: { personIds: { all: [DummyValue.UUID, DummyValue.UUID_1] } },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'ids-all-single',
|
||||
params: [{ size: 100 }, { filter: { albumIds: { all: [DummyValue.UUID] } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'ids-none',
|
||||
params: [{ size: 100 }, { filter: { tagIds: { none: [DummyValue.UUID] } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'ids-tags-all',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: { tagIds: { all: [DummyValue.UUID, DummyValue.UUID_1] } },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'has-albums-false',
|
||||
params: [{ size: 100 }, { filter: { hasAlbums: { eq: false } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'is-encoded',
|
||||
params: [{ size: 100 }, { filter: { isEncoded: { eq: true } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'number-range',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: { fileSizeInBytes: { gte: 100, lte: 1000 } },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'date-eq',
|
||||
params: [{ size: 100 }, { filter: { takenAt: { eq: DummyValue.DATE } } }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'date-range',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: { takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE } },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'order-fileSize-noExif',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
order: { field: SearchOrderField.FileSizeInBytes, direction: AssetOrder.Desc },
|
||||
withExif: false,
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'order-rating-withExif',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
order: { field: SearchOrderField.Rating, direction: AssetOrder.Asc },
|
||||
withExif: true,
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'or-branches',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: {
|
||||
or: [{ isFavorite: { eq: true } }, { personIds: { any: [DummyValue.UUID] } }],
|
||||
},
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'or-with-top-level',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
filter: {
|
||||
takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE },
|
||||
or: [{ isFavorite: { eq: true } }, { albumIds: { any: [DummyValue.UUID] } }],
|
||||
},
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cursor-offset',
|
||||
params: [{ size: 100, offset: 100 }, { filter: { isFavorite: { eq: true } } }, scopeExample],
|
||||
},
|
||||
];
|
||||
|
||||
export const searchRandomV3Examples: GenerateSqlQueries[] = [
|
||||
{ name: 'baseline', params: [100, {}, scopeExample] },
|
||||
{
|
||||
name: 'with-filter',
|
||||
params: [100, { filter: { isFavorite: { eq: true } } }, scopeExample],
|
||||
},
|
||||
];
|
||||
|
||||
export const searchSmartV3Examples: GenerateSqlQueries[] = [
|
||||
{
|
||||
name: 'baseline',
|
||||
params: [{ size: 100 }, { embedding: DummyValue.VECTOR }, scopeExample],
|
||||
},
|
||||
{
|
||||
name: 'with-filter',
|
||||
params: [
|
||||
{ size: 100 },
|
||||
{
|
||||
embedding: DummyValue.VECTOR,
|
||||
filter: { takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE } },
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'cursor-offset',
|
||||
params: [{ size: 100, offset: 100 }, { embedding: DummyValue.VECTOR }, scopeExample],
|
||||
},
|
||||
];
|
||||
|
||||
export const searchStatisticsV3Examples: GenerateSqlQueries[] = [
|
||||
{ name: 'baseline', params: [{}, scopeExample] },
|
||||
{
|
||||
name: 'with-filter',
|
||||
params: [
|
||||
{
|
||||
filter: {
|
||||
takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE },
|
||||
fileSizeInBytes: { gte: 100 },
|
||||
},
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'with-or',
|
||||
params: [
|
||||
{
|
||||
filter: {
|
||||
or: [{ isFavorite: { eq: true } }, { hasAlbums: { eq: false } }],
|
||||
},
|
||||
},
|
||||
scopeExample,
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export type ReindexVectorIndexOptions = { indexName: string; lists?: number };
|
||||
|
||||
type VectorIndexQueryOptions = { table: string; vectorExtension: VectorExtension } & ReindexVectorIndexOptions;
|
||||
|
||||
38
server/src/utils/search-cursor.spec.ts
Normal file
38
server/src/utils/search-cursor.spec.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import { decodeSearchCursor, encodeSearchCursor } from 'src/utils/search-cursor';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('encodeSearchCursor', () => {
|
||||
it('should produce an opaque base64url string', () => {
|
||||
const cursor = encodeSearchCursor(250);
|
||||
expect(cursor).toMatch(/^[\w-]+$/);
|
||||
});
|
||||
|
||||
it('should round-trip an offset', () => {
|
||||
for (const offset of [0, 1, 250, 1_000_000]) {
|
||||
expect(decodeSearchCursor(encodeSearchCursor(offset))).toEqual({ offset });
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('decodeSearchCursor', () => {
|
||||
const encode = (value: unknown) => Buffer.from(JSON.stringify(value)).toString('base64url');
|
||||
|
||||
it('should treat a missing cursor as the first page', () => {
|
||||
expect(decodeSearchCursor(undefined)).toEqual({ offset: 0 });
|
||||
});
|
||||
|
||||
it.each([
|
||||
['empty string', ''],
|
||||
['garbage that is not base64url', '!!!not-base64!!!'],
|
||||
['base64 of non-JSON', Buffer.from('not json').toString('base64url')],
|
||||
['JSON without an offset', encode({})],
|
||||
['JSON with a non-object payload', encode(42)],
|
||||
['null payload', encode(null)],
|
||||
['negative offset', encode({ o: -1 })],
|
||||
['fractional offset', encode({ o: 1.5 })],
|
||||
['string offset', encode({ o: '5' })],
|
||||
])('should reject %s', (_, cursor) => {
|
||||
expect(() => decodeSearchCursor(cursor)).toThrowError(new BadRequestException('Invalid cursor'));
|
||||
});
|
||||
});
|
||||
22
server/src/utils/search-cursor.ts
Normal file
22
server/src/utils/search-cursor.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import { BadRequestException } from '@nestjs/common';
|
||||
import z from 'zod';
|
||||
|
||||
const SearchCursorPayloadSchema = z.object({
|
||||
o: z.int().min(0),
|
||||
});
|
||||
|
||||
export const encodeSearchCursor = (offset: number): string =>
|
||||
Buffer.from(JSON.stringify({ o: offset } satisfies z.infer<typeof SearchCursorPayloadSchema>)).toString('base64url');
|
||||
|
||||
export const decodeSearchCursor = (cursor?: string): { offset: number } => {
|
||||
if (cursor === undefined) {
|
||||
return { offset: 0 };
|
||||
}
|
||||
|
||||
try {
|
||||
const payload = SearchCursorPayloadSchema.parse(JSON.parse(Buffer.from(cursor, 'base64url').toString('utf8')));
|
||||
return { offset: payload.o };
|
||||
} catch {
|
||||
throw new BadRequestException('Invalid cursor');
|
||||
}
|
||||
};
|
||||
102
server/src/utils/search-filter.spec.ts
Normal file
102
server/src/utils/search-filter.spec.ts
Normal file
@@ -0,0 +1,102 @@
|
||||
import { UnauthorizedException } from '@nestjs/common';
|
||||
import { SearchFilter } from 'src/dtos/search.dto';
|
||||
import { AssetVisibility } from 'src/enum';
|
||||
import {
|
||||
applyLockedVisibilityPolicy,
|
||||
collectFilterIds,
|
||||
hasTopLevelPositiveIdsConstraint,
|
||||
} from 'src/utils/search-filter';
|
||||
import { AuthFactory } from 'test/factories/auth.factory';
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
const elevatedAuth = () => AuthFactory.from().session({ hasElevatedPermission: true }).build();
|
||||
const unelevatedAuth = () => AuthFactory.from().session().build();
|
||||
|
||||
describe(applyLockedVisibilityPolicy.name, () => {
|
||||
it('should let an elevated session query locked assets', () => {
|
||||
const filter = { visibility: { eq: AssetVisibility.Locked } };
|
||||
expect(applyLockedVisibilityPolicy(elevatedAuth(), filter)).toBe(filter);
|
||||
});
|
||||
|
||||
it('should reject an unelevated session when any operator permits locked', () => {
|
||||
for (const visibility of [
|
||||
{ eq: AssetVisibility.Locked },
|
||||
{ ne: AssetVisibility.Timeline },
|
||||
{ in: [AssetVisibility.Locked, AssetVisibility.Timeline] },
|
||||
{ notIn: [AssetVisibility.Timeline] },
|
||||
]) {
|
||||
expect(() => applyLockedVisibilityPolicy(unelevatedAuth(), { visibility })).toThrow(UnauthorizedException);
|
||||
}
|
||||
});
|
||||
|
||||
it('should keep a filter whose top-level visibility excludes locked', () => {
|
||||
for (const visibility of [
|
||||
{ eq: AssetVisibility.Timeline },
|
||||
{ ne: AssetVisibility.Locked },
|
||||
{ in: [AssetVisibility.Timeline, AssetVisibility.Archive] },
|
||||
{ notIn: [AssetVisibility.Locked] },
|
||||
]) {
|
||||
const filter = { visibility };
|
||||
expect(applyLockedVisibilityPolicy(unelevatedAuth(), filter)).toBe(filter);
|
||||
}
|
||||
});
|
||||
|
||||
it('should let a safe top-level visibility decide over could-match branches', () => {
|
||||
const filter = { visibility: { ne: AssetVisibility.Locked }, or: [{ visibility: { eq: AssetVisibility.Locked } }] };
|
||||
expect(applyLockedVisibilityPolicy(unelevatedAuth(), filter)).toBe(filter);
|
||||
});
|
||||
|
||||
it('should reject a branch that permits locked when the top level has no visibility', () => {
|
||||
const filter = {
|
||||
or: [{ city: { eq: 'Oslo' } }, { visibility: { in: [AssetVisibility.Locked, AssetVisibility.Timeline] } }],
|
||||
};
|
||||
expect(() => applyLockedVisibilityPolicy(unelevatedAuth(), filter)).toThrow(UnauthorizedException);
|
||||
});
|
||||
|
||||
it('should otherwise inject visibility != locked without mutating the input', () => {
|
||||
const filter = { city: { eq: 'Oslo' }, visibility: undefined };
|
||||
expect(applyLockedVisibilityPolicy(unelevatedAuth(), filter)).toEqual({
|
||||
city: { eq: 'Oslo' },
|
||||
visibility: { ne: AssetVisibility.Locked },
|
||||
});
|
||||
expect(filter.visibility).toBeUndefined();
|
||||
|
||||
const branched = { or: [{ isFavorite: { eq: true } }, { visibility: { eq: AssetVisibility.Timeline } }] };
|
||||
expect(applyLockedVisibilityPolicy(unelevatedAuth(), branched).visibility).toEqual({ ne: AssetVisibility.Locked });
|
||||
});
|
||||
});
|
||||
|
||||
describe(collectFilterIds.name, () => {
|
||||
it('should union and dedupe ids across operators and branches', () => {
|
||||
const [albumA, albumB, albumC] = [
|
||||
'00000000-0000-4000-8000-00000000000a',
|
||||
'00000000-0000-4000-8000-00000000000b',
|
||||
'00000000-0000-4000-8000-00000000000c',
|
||||
];
|
||||
const filter: SearchFilter = {
|
||||
albumIds: { any: [albumA], none: [albumB] },
|
||||
or: [{ albumIds: { all: [albumC, albumA] } }, { city: { eq: 'Oslo' } }],
|
||||
};
|
||||
expect(collectFilterIds(filter, 'albumIds').toSorted()).toEqual([albumA, albumB, albumC]);
|
||||
expect(collectFilterIds({}, 'albumIds')).toEqual([]);
|
||||
});
|
||||
|
||||
it('should only collect ids of the requested field', () => {
|
||||
const albumId = '00000000-0000-4000-8000-00000000000a';
|
||||
const personId = '00000000-0000-4000-8000-00000000000b';
|
||||
const filter = { albumIds: { any: [albumId] }, personIds: { any: [personId] } };
|
||||
expect(collectFilterIds(filter, 'personIds')).toEqual([personId]);
|
||||
});
|
||||
});
|
||||
|
||||
describe(hasTopLevelPositiveIdsConstraint.name, () => {
|
||||
it('should detect only top-level any/all constraints', () => {
|
||||
const albumId = '00000000-0000-4000-8000-00000000000a';
|
||||
expect(hasTopLevelPositiveIdsConstraint({ albumIds: { any: [albumId] } }, 'albumIds')).toBe(true);
|
||||
expect(hasTopLevelPositiveIdsConstraint({ albumIds: { all: [albumId] } }, 'albumIds')).toBe(true);
|
||||
expect(hasTopLevelPositiveIdsConstraint({ albumIds: { none: [albumId] } }, 'albumIds')).toBe(false);
|
||||
expect(hasTopLevelPositiveIdsConstraint({ or: [{ albumIds: { any: [albumId] } }] }, 'albumIds')).toBe(false);
|
||||
expect(hasTopLevelPositiveIdsConstraint({ albumIds: { any: [albumId] } }, 'tagIds')).toBe(false);
|
||||
expect(hasTopLevelPositiveIdsConstraint({}, 'albumIds')).toBe(false);
|
||||
});
|
||||
});
|
||||
70
server/src/utils/search-filter.ts
Normal file
70
server/src/utils/search-filter.ts
Normal file
@@ -0,0 +1,70 @@
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { SearchFilter, SearchFilterBranch } from 'src/dtos/search.dto';
|
||||
import { AssetVisibility } from 'src/enum';
|
||||
import { requireElevatedPermission } from 'src/utils/access';
|
||||
|
||||
type EnumField = 'type' | 'visibility';
|
||||
type EnumOperator = keyof NonNullable<SearchFilterBranch[EnumField]>;
|
||||
type EnumOperandMap<T> = { eq: T; ne: T; in: T[]; notIn: T[] };
|
||||
type EnumCondition<T> = { [K in EnumOperator]?: EnumOperandMap<T>[K] };
|
||||
type IdsFilterField = 'albumIds' | 'personIds' | 'tagIds';
|
||||
|
||||
const filterBranches = (filter: SearchFilter): SearchFilterBranch[] => [filter, ...(filter.or ?? [])];
|
||||
|
||||
/** Whether a row with `value` can satisfy the condition. A missing operator allows any value. */
|
||||
const canMatch = <T>(condition: EnumCondition<T>, value: T): boolean =>
|
||||
(condition.eq === undefined || condition.eq === value) &&
|
||||
(condition.ne === undefined || condition.ne !== value) &&
|
||||
(condition.in === undefined || condition.in.includes(value)) &&
|
||||
(condition.notIn === undefined || !condition.notIn.includes(value));
|
||||
|
||||
/**
|
||||
* The conditions that decide which `field` values the filter can return. A top-level condition decides alone, otherwise each branch itself.
|
||||
*/
|
||||
const decidingConditions = <F extends EnumField>(filter: SearchFilter, field: F) => {
|
||||
if (filter[field] !== undefined) {
|
||||
return [filter[field]];
|
||||
}
|
||||
|
||||
return filterBranches(filter)
|
||||
.map((branch) => branch[field])
|
||||
.filter((condition) => condition !== undefined);
|
||||
};
|
||||
|
||||
/**
|
||||
* Keeps locked assets out of search results unless the session is elevated: a filter that asks for
|
||||
* them is rejected with 401, and any other filter gets `visibility != locked` ANDed in.
|
||||
*/
|
||||
export const applyLockedVisibilityPolicy = (auth: AuthDto, filter: SearchFilter): SearchFilter => {
|
||||
if (auth.session?.hasElevatedPermission) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
if (decidingConditions(filter, 'visibility').some((condition) => canMatch(condition, AssetVisibility.Locked))) {
|
||||
requireElevatedPermission(auth);
|
||||
}
|
||||
|
||||
if (filter.visibility !== undefined) {
|
||||
return filter;
|
||||
}
|
||||
|
||||
return { ...filter, visibility: { ne: AssetVisibility.Locked } };
|
||||
};
|
||||
|
||||
export const collectFilterIds = (filter: SearchFilter, field: IdsFilterField): string[] => {
|
||||
const ids = new Set<string>();
|
||||
|
||||
for (const branch of filterBranches(filter)) {
|
||||
for (const operator of ['any', 'all', 'none'] as const) {
|
||||
for (const id of branch[field]?.[operator] ?? []) {
|
||||
ids.add(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return [...ids];
|
||||
};
|
||||
|
||||
/** Whether the top level constrains results to specific `field` ids (`any`/`all`; `none` only excludes). */
|
||||
export const hasTopLevelPositiveIdsConstraint = (filter: SearchFilter, field: IdsFilterField): boolean =>
|
||||
filter[field]?.any !== undefined || filter[field]?.all !== undefined;
|
||||
@@ -42,7 +42,7 @@ export function nonEmptyPartial<T extends z.ZodRawShape>(shape: T) {
|
||||
.object(shape)
|
||||
.partial()
|
||||
.refine((data) => Object.values(data as Record<string, unknown>).some((value) => value !== undefined), {
|
||||
message: 'At least one field must be provided',
|
||||
message: `At least one of the following fields is required: ${Object.keys(shape).join(', ')}`,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { Kysely } from 'kysely';
|
||||
import { SearchSuggestionType } from 'src/dtos/search.dto';
|
||||
import { AlbumUserRole, AssetVisibility } from 'src/enum';
|
||||
import { AlbumUserRole, AssetOrder, AssetVisibility, SearchOrderField } from 'src/enum';
|
||||
import { AccessRepository } from 'src/repositories/access.repository';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { DatabaseRepository } from 'src/repositories/database.repository';
|
||||
@@ -16,6 +16,8 @@ import { getKyselyDB } from 'test/utils';
|
||||
|
||||
let defaultDatabase: Kysely<DB>;
|
||||
|
||||
const unitVector = (index: number) => JSON.stringify(Array.from({ length: 512 }, (_, i) => (i === index ? 1 : 0)));
|
||||
|
||||
const setup = (db?: Kysely<DB>) => {
|
||||
return newMediumService(SearchService, {
|
||||
database: db || defaultDatabase,
|
||||
@@ -56,7 +58,7 @@ describe(SearchService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
await expect(sut.searchLargeAssets(auth, {})).resolves.toEqual([
|
||||
await expect(sut.searchLargeAssets(auth, { size: 250 })).resolves.toEqual([
|
||||
expect.objectContaining({ id: assets[2].id }),
|
||||
expect.objectContaining({ id: assets[0].id }),
|
||||
expect.objectContaining({ id: assets[1].id }),
|
||||
@@ -120,7 +122,7 @@ describe(SearchService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
const response = await sut.searchMetadata(auth, { withStacked: false });
|
||||
const response = await sut.searchMetadata(auth, { size: 250, withStacked: false });
|
||||
|
||||
expect(response.assets.items.length).toBe(1);
|
||||
expect(response.assets.items[0].id).toBe(unstackedAsset.id);
|
||||
@@ -135,7 +137,7 @@ describe(SearchService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
const response = await sut.searchMetadata(auth, { withStacked: false });
|
||||
const response = await sut.searchMetadata(auth, { size: 250, withStacked: false });
|
||||
|
||||
expect(response.assets.items.length).toBe(0);
|
||||
});
|
||||
@@ -148,7 +150,7 @@ describe(SearchService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id }, session: { hasElevatedPermission: true } });
|
||||
|
||||
const response = await sut.searchMetadata(auth, { withStacked: false });
|
||||
const response = await sut.searchMetadata(auth, { size: 250, withStacked: false });
|
||||
|
||||
expect(response.assets.items.length).toBe(1);
|
||||
});
|
||||
@@ -168,7 +170,7 @@ describe(SearchService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
const response = await sut.searchMetadata(auth, { albumIds: [album.id] });
|
||||
const response = await sut.searchMetadata(auth, { size: 250, albumIds: [album.id] });
|
||||
|
||||
expect(response.assets.items.length).toBe(1);
|
||||
});
|
||||
@@ -186,7 +188,7 @@ describe(SearchService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
await expect(sut.searchMetadata(auth, { albumIds: [album.id] })).rejects.toThrow(
|
||||
await expect(sut.searchMetadata(auth, { size: 250, albumIds: [album.id] })).rejects.toThrow(
|
||||
'Not found or no album.read access',
|
||||
);
|
||||
});
|
||||
@@ -222,9 +224,196 @@ describe(SearchService.name, () => {
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
const response = await sut.searchRandom(auth, {});
|
||||
const response = await sut.searchRandom(auth, { size: 250 });
|
||||
|
||||
expect(response.length).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('new search shape', () => {
|
||||
it('should filter by an exif field and return a cursor-less single page', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({ assetId: asset.id, city: 'Oslo' });
|
||||
const { asset: other } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({ assetId: other.id, city: 'Bergen' });
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
const response = await sut.searchMetadata(auth, { size: 250, filter: { city: { eq: 'Oslo' } } });
|
||||
|
||||
expect(response.assets.items).toEqual([expect.objectContaining({ id: asset.id })]);
|
||||
expect(response.assets.nextPage).toBeNull();
|
||||
expect(response.assets.nextCursor).toBeNull();
|
||||
});
|
||||
|
||||
it('should combine OR branches', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset: oslo } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({ assetId: oslo.id, city: 'Oslo' });
|
||||
const { asset: favorite } = await ctx.newAsset({ ownerId: user.id, isFavorite: true });
|
||||
await ctx.newAsset({ ownerId: user.id });
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
const response = await sut.searchMetadata(auth, {
|
||||
size: 250,
|
||||
filter: { or: [{ city: { eq: 'Oslo' } }, { isFavorite: { eq: true } }] },
|
||||
});
|
||||
|
||||
expect(response.assets.items.map(({ id }) => id).toSorted()).toEqual([oslo.id, favorite.id].toSorted());
|
||||
});
|
||||
|
||||
it('should widen to album assets only for a top-level album constraint', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { asset } = await ctx.newAsset({ ownerId: otherUser.id });
|
||||
const { album } = await ctx.newAlbum({ ownerId: user.id });
|
||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
const topLevel = await sut.searchMetadata(auth, { size: 250, filter: { albumIds: { any: [album.id] } } });
|
||||
expect(topLevel.assets.items).toEqual([expect.objectContaining({ id: asset.id })]);
|
||||
|
||||
const branchOnly = await sut.searchMetadata(auth, {
|
||||
size: 250,
|
||||
filter: { or: [{ albumIds: { any: [album.id] } }] },
|
||||
});
|
||||
expect(branchOnly.assets.items).toEqual([]);
|
||||
});
|
||||
|
||||
it('should reject an inaccessible album anywhere in the filter', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: otherUser } = await ctx.newUser();
|
||||
const { album } = await ctx.newAlbum({ ownerId: otherUser.id });
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
await expect(
|
||||
sut.searchMetadata(auth, { size: 250, filter: { or: [{ albumIds: { none: [album.id] } }] } }),
|
||||
).rejects.toThrow('Not found or no album.read access');
|
||||
});
|
||||
|
||||
it('should return locked assets only to an elevated session that asks for them', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { asset: timeline } = await ctx.newAsset({ ownerId: user.id });
|
||||
const { asset: locked } = await ctx.newAsset({ ownerId: user.id, visibility: AssetVisibility.Locked });
|
||||
|
||||
const unelevated = await sut.searchMetadata(factory.auth({ user: { id: user.id } }), { size: 250, filter: {} });
|
||||
expect(unelevated.assets.items).toEqual([expect.objectContaining({ id: timeline.id })]);
|
||||
|
||||
const elevatedAuth = factory.auth({ user: { id: user.id }, session: { hasElevatedPermission: true } });
|
||||
const elevated = await sut.searchMetadata(elevatedAuth, {
|
||||
size: 250,
|
||||
filter: { visibility: { eq: AssetVisibility.Locked } },
|
||||
});
|
||||
expect(elevated.assets.items).toEqual([expect.objectContaining({ id: locked.id })]);
|
||||
});
|
||||
|
||||
it('should exclude partner assets from a locked-only search', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: partner } = await ctx.newUser();
|
||||
await ctx.newPartner({ sharedById: partner.id, sharedWithId: user.id });
|
||||
const { asset: ownLocked } = await ctx.newAsset({ ownerId: user.id, visibility: AssetVisibility.Locked });
|
||||
await ctx.newAsset({ ownerId: partner.id, visibility: AssetVisibility.Locked });
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id }, session: { hasElevatedPermission: true } });
|
||||
const response = await sut.searchMetadata(auth, {
|
||||
size: 250,
|
||||
filter: { visibility: { eq: AssetVisibility.Locked } },
|
||||
});
|
||||
|
||||
expect(response.assets.items).toEqual([expect.objectContaining({ id: ownLocked.id })]);
|
||||
});
|
||||
|
||||
it('should never return partner locked assets, even for locked-matching mixed filters', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const { user: partner } = await ctx.newUser();
|
||||
await ctx.newPartner({ sharedById: partner.id, sharedWithId: user.id });
|
||||
const { asset: ownLocked } = await ctx.newAsset({ ownerId: user.id, visibility: AssetVisibility.Locked });
|
||||
const { asset: partnerTimeline } = await ctx.newAsset({ ownerId: partner.id });
|
||||
await ctx.newAsset({ ownerId: partner.id, visibility: AssetVisibility.Locked });
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id }, session: { hasElevatedPermission: true } });
|
||||
const response = await sut.searchMetadata(auth, {
|
||||
size: 250,
|
||||
filter: { visibility: { in: [AssetVisibility.Locked, AssetVisibility.Timeline] } },
|
||||
});
|
||||
|
||||
const ids = response.assets.items.map(({ id }) => id);
|
||||
expect(ids.toSorted()).toEqual([ownLocked.id, partnerTimeline.id].toSorted());
|
||||
});
|
||||
|
||||
it('should paginate with an opaque cursor', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
for (let i = 0; i < 3; i++) {
|
||||
await ctx.newAsset({ ownerId: user.id });
|
||||
}
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
|
||||
const firstPage = await sut.searchMetadata(auth, { filter: {}, size: 2 });
|
||||
expect(firstPage.assets.items.length).toBe(2);
|
||||
expect(firstPage.assets.nextPage).toBeNull();
|
||||
expect(firstPage.assets.nextCursor).toEqual(expect.any(String));
|
||||
|
||||
const secondPage = await sut.searchMetadata(auth, { cursor: firstPage.assets.nextCursor!, size: 2 });
|
||||
expect(secondPage.assets.items.length).toBe(1);
|
||||
expect(secondPage.assets.nextCursor).toBeNull();
|
||||
|
||||
const ids = [...firstPage.assets.items, ...secondPage.assets.items].map(({ id }) => id);
|
||||
expect(new Set(ids).size).toBe(3);
|
||||
});
|
||||
|
||||
it('should order by fileSizeInBytes', async () => {
|
||||
const { sut, ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const sizes = [12_334, 599, 123_456];
|
||||
const assetIds: string[] = [];
|
||||
for (const fileSizeInByte of sizes) {
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await ctx.newExif({ assetId: asset.id, fileSizeInByte });
|
||||
assetIds.push(asset.id);
|
||||
}
|
||||
|
||||
const auth = factory.auth({ user: { id: user.id } });
|
||||
const response = await sut.searchMetadata(auth, {
|
||||
size: 250,
|
||||
orderBy: { field: SearchOrderField.FileSizeInBytes, direction: AssetOrder.Asc },
|
||||
});
|
||||
|
||||
expect(response.assets.items.map(({ id }) => id)).toEqual([assetIds[1], assetIds[0], assetIds[2]]);
|
||||
});
|
||||
|
||||
it('should order smart search results by embedding distance with cursor offsets', async () => {
|
||||
const { ctx } = setup();
|
||||
const { user } = await ctx.newUser();
|
||||
const searchRepository = ctx.get(SearchRepository);
|
||||
|
||||
const assetIds: string[] = [];
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const { asset } = await ctx.newAsset({ ownerId: user.id });
|
||||
await searchRepository.upsert(asset.id, unitVector(i));
|
||||
assetIds.push(asset.id);
|
||||
}
|
||||
|
||||
const options = { filter: {}, embedding: unitVector(0) };
|
||||
const scope = { userIds: [user.id], lockedOwnerId: user.id };
|
||||
const firstPage = await searchRepository.searchSmartV3({ size: 2 }, options, scope);
|
||||
expect(firstPage.items.length).toBe(2);
|
||||
expect(firstPage.items[0].id).toBe(assetIds[0]);
|
||||
expect(firstPage.hasNextPage).toBe(true);
|
||||
|
||||
const secondPage = await searchRepository.searchSmartV3({ size: 2, offset: 2 }, options, scope);
|
||||
expect(secondPage.items.length).toBe(1);
|
||||
expect(secondPage.hasNextPage).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { Icon } from '@immich/ui';
|
||||
import { mdiChevronRight } from '@mdi/js';
|
||||
import { mdiChevronRight, mdiChevronLeft } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import NavigationArea from '../NavigationArea.svelte';
|
||||
import { languageManager } from '$lib/managers/language-manager.svelte';
|
||||
|
||||
interface Props {
|
||||
onNextAsset: () => void;
|
||||
@@ -20,5 +21,5 @@
|
||||
/>
|
||||
|
||||
<NavigationArea onClick={onNextAsset} label={$t('view_next_asset')}>
|
||||
<Icon icon={mdiChevronRight} size="36" aria-hidden />
|
||||
<Icon icon={languageManager.rtl ? mdiChevronLeft : mdiChevronRight} size="36" aria-hidden />
|
||||
</NavigationArea>
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
<script lang="ts">
|
||||
import { shortcuts } from '$lib/actions/shortcut';
|
||||
import { Icon } from '@immich/ui';
|
||||
import { mdiChevronLeft } from '@mdi/js';
|
||||
import { mdiChevronLeft, mdiChevronRight } from '@mdi/js';
|
||||
import { t } from 'svelte-i18n';
|
||||
import NavigationArea from '../NavigationArea.svelte';
|
||||
import { languageManager } from '$lib/managers/language-manager.svelte';
|
||||
|
||||
interface Props {
|
||||
onPreviousAsset: () => void;
|
||||
@@ -20,5 +21,5 @@
|
||||
/>
|
||||
|
||||
<NavigationArea onClick={onPreviousAsset} label={$t('view_previous_asset')}>
|
||||
<Icon icon={mdiChevronLeft} size="36" aria-hidden />
|
||||
<Icon icon={languageManager.rtl ? mdiChevronRight : mdiChevronLeft} size="36" aria-hidden />
|
||||
</NavigationArea>
|
||||
|
||||
@@ -95,6 +95,8 @@
|
||||
const getPreviousRoute = $page.url.searchParams.get(QueryParameter.PREVIOUS_ROUTE);
|
||||
if (getPreviousRoute && !isExternalUrl(getPreviousRoute)) {
|
||||
previousRoute = getPreviousRoute;
|
||||
} else if ($page.params.assetId) {
|
||||
previousRoute = Route.viewPerson(data.person);
|
||||
}
|
||||
if (action == 'merge') {
|
||||
viewMode = PersonPageViewMode.MERGE_PEOPLE;
|
||||
|
||||
Reference in New Issue
Block a user