Compare commits

...

12 Commits

Author SHA1 Message Date
timonrieger
c2b6e911e8 split search opts from scope 2026-07-24 01:06:09 +02:00
timonrieger
7a5f8d954a enum op exhaustion 2026-07-24 00:38:21 +02:00
timonrieger
43b7c669e4 page defaults 2026-07-24 00:38:18 +02:00
timonrieger
0231169135 tests 2026-07-24 00:20:06 +02:00
timonrieger
1a13dff3c1 regen openapi 2026-07-23 23:37:05 +02:00
timonrieger
3a7bb760d3 deprecate controller 2026-07-23 23:34:06 +02:00
timonrieger
702a14cd76 fmt 2026-07-23 23:29:17 +02:00
timonrieger
ae428c20a8 services and simplify 2026-07-23 23:27:39 +02:00
timonrieger
55aa05b4c9 repos 2026-07-23 18:16:45 +02:00
timonrieger
fc4edf8d06 dtos 2026-07-23 18:07:45 +02:00
timonrieger
4f5e3cc234 enum condition filters 2026-07-23 17:34:02 +02:00
timonrieger
2f83615195 cursor en-/decoder 2026-07-23 16:08:27 +02:00
43 changed files with 8946 additions and 327 deletions

View File

@@ -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)

View File

@@ -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';

View File

@@ -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.

View File

@@ -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':

View File

@@ -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();
}

View 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
View 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>{
};
}

View 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>{
};
}

View 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>{
};
}

View 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
View 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>{
};
}

View 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
View 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>{
};
}

View File

@@ -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(),

View 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>{
};
}

View 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>{
};
}

View File

@@ -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(),

View File

@@ -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',
};

View 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>{
};
}

View 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
View 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>{
};
}

View 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;
}

View File

@@ -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(),

View File

@@ -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(),

View 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>{
};
}

View 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>{
};
}

View 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>{
};
}

View 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',
};
}

File diff suppressed because it is too large Load Diff

View File

@@ -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",

View File

@@ -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');

View File

@@ -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);

View File

@@ -14,32 +14,40 @@ import {
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)
@@ -52,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'),
@@ -319,6 +300,88 @@ 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) {}
@@ -372,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' });

View File

@@ -449,12 +449,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and true
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$2
$4
offset
$5
-- SearchRepository.searchMetadataV3 (empty)
select
@@ -490,12 +496,18 @@ from
"asset"
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
true
(
"asset"."visibility" != $1
or "asset"."ownerId" = $2
)
and true
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$1
$3
offset
$4
-- SearchRepository.searchMetadataV3 (or-exif-only)
select
@@ -532,12 +544,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and "asset_exif"."city" = $2
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and "asset_exif"."city" = $4
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (string-eq-null)
select
@@ -574,12 +592,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and "asset_exif"."city" is null
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$2
$4
offset
$5
-- SearchRepository.searchMetadataV3 (string-pattern-like)
select
@@ -616,12 +640,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and f_unaccent ("asset_exif"."description") ilike ('%' || f_unaccent ($2) || '%')
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and f_unaccent ("asset_exif"."description") ilike ('%' || f_unaccent ($4) || '%')
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (string-pattern-notLike)
select
@@ -658,12 +688,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and f_unaccent ("asset_exif"."description") not ilike ('%' || f_unaccent ($2) || '%')
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and f_unaccent ("asset_exif"."description") not ilike ('%' || f_unaccent ($4) || '%')
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (string-pattern-startsWith)
select
@@ -700,12 +736,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and f_unaccent ("asset"."originalFileName") ilike (f_unaccent ($2) || '%')
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and f_unaccent ("asset"."originalFileName") ilike (f_unaccent ($4) || '%')
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (string-similarity-ocr)
select
@@ -742,19 +784,25 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and exists (
select
from
"ocr_search"
where
"ocr_search"."assetId" = "asset"."id"
and f_unaccent (ocr_search.text) %>> f_unaccent ($2)
and f_unaccent (ocr_search.text) %>> f_unaccent ($4)
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (ids-any)
select
@@ -791,19 +839,25 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and exists (
select
from
"album_asset"
where
"album_asset"."assetId" = "asset"."id"
and "album_asset"."albumId" = any ($2::uuid[])
and "album_asset"."albumId" = any ($4::uuid[])
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (ids-all)
select
@@ -840,6 +894,10 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and exists (
select
"asset_face"."assetId"
@@ -848,18 +906,20 @@ where
where
"asset_face"."assetId" = "asset"."id"
and "asset_face"."deletedAt" is null
and "asset_face"."isVisible" = $2
and "asset_face"."personId" = any ($3::uuid[])
and "asset_face"."isVisible" = $4
and "asset_face"."personId" = any ($5::uuid[])
group by
"asset_face"."assetId"
having
count(distinct "asset_face"."personId") = $4
count(distinct "asset_face"."personId") = $6
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$5
$7
offset
$8
-- SearchRepository.searchMetadataV3 (ids-all-single)
select
@@ -896,19 +956,25 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and exists (
select
from
"album_asset"
where
"album_asset"."assetId" = "asset"."id"
and "album_asset"."albumId" = any ($2::uuid[])
and "album_asset"."albumId" = any ($4::uuid[])
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (ids-none)
select
@@ -945,6 +1011,10 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and not exists (
select
from
@@ -952,13 +1022,15 @@ where
inner join "tag_closure" on "tag_asset"."tagId" = "tag_closure"."id_descendant"
where
"tag_asset"."assetId" = "asset"."id"
and "tag_closure"."id_ancestor" = any ($2::uuid[])
and "tag_closure"."id_ancestor" = any ($4::uuid[])
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (ids-tags-all)
select
@@ -995,6 +1067,10 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and exists (
select
"tag_asset"."assetId"
@@ -1003,17 +1079,19 @@ where
inner join "tag_closure" on "tag_asset"."tagId" = "tag_closure"."id_descendant"
where
"tag_asset"."assetId" = "asset"."id"
and "tag_closure"."id_ancestor" = any ($2::uuid[])
and "tag_closure"."id_ancestor" = any ($4::uuid[])
group by
"tag_asset"."assetId"
having
count(distinct "tag_closure"."id_ancestor") = $3
count(distinct "tag_closure"."id_ancestor") = $5
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$4
$6
offset
$7
-- SearchRepository.searchMetadataV3 (has-albums-false)
select
@@ -1050,6 +1128,10 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and not exists (
select
from
@@ -1061,7 +1143,9 @@ order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$2
$4
offset
$5
-- SearchRepository.searchMetadataV3 (is-encoded)
select
@@ -1098,19 +1182,25 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and exists (
select
from
"asset_file"
where
"asset_file"."assetId" = "asset"."id"
and "asset_file"."type" = $2
and "asset_file"."type" = $4
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (number-range)
select
@@ -1148,14 +1238,20 @@ from
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset_exif"."fileSizeInByte" <= $2
and "asset_exif"."fileSizeInByte" >= $3
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and (
"asset_exif"."fileSizeInByte" <= $4
and "asset_exif"."fileSizeInByte" >= $5
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$4
$6
offset
$7
-- SearchRepository.searchMetadataV3 (date-eq)
select
@@ -1192,12 +1288,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and "asset"."fileCreatedAt" = $2
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and "asset"."fileCreatedAt" = $4
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$3
$5
offset
$6
-- SearchRepository.searchMetadataV3 (date-range)
select
@@ -1235,14 +1337,20 @@ from
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."fileCreatedAt" < $2
and "asset"."fileCreatedAt" >= $3
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and (
"asset"."fileCreatedAt" < $4
and "asset"."fileCreatedAt" >= $5
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$4
$6
offset
$7
-- SearchRepository.searchMetadataV3 (order-fileSize-noExif)
select
@@ -1279,12 +1387,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and true
order by
"asset_exif"."fileSizeInByte" desc nulls last,
"asset"."id" desc
limit
$2
$4
offset
$5
-- SearchRepository.searchMetadataV3 (order-rating-withExif)
select
@@ -1322,12 +1436,18 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and true
order by
"asset_exif"."rating" asc nulls last,
"asset"."id" asc
limit
$2
$4
offset
$5
-- SearchRepository.searchMetadataV3 (or-branches)
select
@@ -1365,7 +1485,11 @@ from
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."isFavorite" = $2
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and (
"asset"."isFavorite" = $4
or exists (
select
from
@@ -1373,15 +1497,17 @@ where
where
"asset_face"."assetId" = "asset"."id"
and "asset_face"."deletedAt" is null
and "asset_face"."isVisible" = $3
and "asset_face"."personId" = any ($4::uuid[])
and "asset_face"."isVisible" = $5
and "asset_face"."personId" = any ($6::uuid[])
)
)
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$5
$7
offset
$8
-- SearchRepository.searchMetadataV3 (or-with-top-level)
select
@@ -1419,17 +1545,21 @@ from
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."fileCreatedAt" < $2
and "asset"."fileCreatedAt" >= $3
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and (
"asset"."fileCreatedAt" < $4
and "asset"."fileCreatedAt" >= $5
and (
"asset"."isFavorite" = $4
"asset"."isFavorite" = $6
or exists (
select
from
"album_asset"
where
"album_asset"."assetId" = "asset"."id"
and "album_asset"."albumId" = any ($5::uuid[])
and "album_asset"."albumId" = any ($7::uuid[])
)
)
)
@@ -1437,8 +1567,310 @@ order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$8
offset
$9
-- SearchRepository.searchMetadataV3 (cursor-offset)
select
"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"
from
"asset"
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and "asset"."isFavorite" = $4
order by
"asset"."fileCreatedAt" desc,
"asset"."id" desc
limit
$5
offset
$6
-- SearchRepository.searchRandomV3 (baseline)
select
"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"
from
"asset"
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and true
order by
random()
limit
$4
-- SearchRepository.searchRandomV3 (with-filter)
select
"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"
from
"asset"
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and "asset"."isFavorite" = $4
order by
random()
limit
$5
-- SearchRepository.searchSmartV3 (baseline)
begin
set
local vchordrq.probes = 1
select
"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"
from
"asset"
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
inner join "smart_search" on "asset"."id" = "smart_search"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and true
order by
smart_search.embedding <=> $4,
"asset"."id" asc
limit
$5
offset
$6
commit
-- SearchRepository.searchSmartV3 (with-filter)
begin
set
local vchordrq.probes = 1
select
"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"
from
"asset"
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
inner join "smart_search" on "asset"."id" = "smart_search"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and (
"asset"."fileCreatedAt" < $4
and "asset"."fileCreatedAt" >= $5
)
order by
smart_search.embedding <=> $6,
"asset"."id" asc
limit
$7
offset
$8
commit
-- SearchRepository.searchSmartV3 (cursor-offset)
begin
set
local vchordrq.probes = 1
select
"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"
from
"asset"
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
inner join "smart_search" on "asset"."id" = "smart_search"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and true
order by
smart_search.embedding <=> $4,
"asset"."id" asc
limit
$5
offset
$6
commit
-- SearchRepository.searchStatisticsV3 (baseline)
select
count(*) as "total"
@@ -1447,6 +1879,10 @@ from
left join "asset_exif" on "asset"."id" = "asset_exif"."assetId"
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and true
-- SearchRepository.searchStatisticsV3 (with-filter)
@@ -1458,9 +1894,13 @@ from
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset_exif"."fileSizeInByte" >= $2
and "asset"."fileCreatedAt" < $3
and "asset"."fileCreatedAt" >= $4
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and (
"asset_exif"."fileSizeInByte" >= $4
and "asset"."fileCreatedAt" < $5
and "asset"."fileCreatedAt" >= $6
)
-- SearchRepository.searchStatisticsV3 (with-or)
@@ -1472,7 +1912,11 @@ from
where
"asset"."ownerId" = any ($1::uuid[])
and (
"asset"."isFavorite" = $2
"asset"."visibility" != $2
or "asset"."ownerId" = $3
)
and (
"asset"."isFavorite" = $4
or not exists (
select
from

View File

@@ -14,6 +14,8 @@ import {
searchAssetBuilder,
searchAssetBuilderLegacy,
searchMetadataV3Examples,
searchRandomV3Examples,
searchSmartV3Examples,
searchStatisticsV3Examples,
withExifInner,
withSearchOrder,
@@ -133,10 +135,13 @@ export type AssetSearchOptions = Omit<BaseAssetSearchOptions, 'visibility'> &
export type AssetSearchBuilderOptions = Omit<AssetSearchOptions, 'orderDirection'>;
export interface AssetSearchScope {
userIds?: string[];
lockedOwnerId: string;
}
export interface AssetSearchBuilderV3Options {
filter?: SearchFilter;
/** Server-derived ownership scope. Never client-controlled. */
userIds?: string[];
withExif?: boolean;
withFaces?: boolean;
withPeople?: boolean;
@@ -146,6 +151,7 @@ export interface AssetSearchBuilderV3Options {
export interface AssetSearchPaginationV3Options {
size: number;
offset?: number;
}
export type SmartSearchOptions = SearchDateOptions &
@@ -208,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 },
@@ -233,6 +240,7 @@ export class SearchRepository {
return paginationHelper(items, pagination.size);
}
// TODO(v4): remove with the deprecated flat-field search API
@GenerateSql({
params: [
{
@@ -249,6 +257,7 @@ export class SearchRepository {
.executeTakeFirstOrThrow();
}
// TODO(v4): remove with the deprecated flat-field search API
@GenerateSql({
params: [
100,
@@ -269,6 +278,7 @@ export class SearchRepository {
.execute();
}
// TODO(v4): remove with the deprecated flat-field search API
@GenerateSql({
params: [
100,
@@ -292,6 +302,7 @@ export class SearchRepository {
.execute();
}
// TODO(v4): remove with the deprecated flat-field search API
@GenerateSql({
params: [
{ page: 1, size: 200 },
@@ -518,20 +529,60 @@ export class SearchRepository {
return res.map((row) => row.lensModel!);
}
// TODO(v4): drop the V3 suffix once the legacy methods are removed
@GenerateSql(...searchMetadataV3Examples)
searchMetadataV3(
async searchMetadataV3(
pagination: AssetSearchPaginationV3Options,
options: AssetSearchBuilderV3Options,
): Promise<MapAsset[]> {
return withSearchOrder(searchAssetBuilder(this.db, options), options.order)
scope: AssetSearchScope,
) {
const items = await withSearchOrder(searchAssetBuilder(this.db, options, scope), options.order)
.select(columns.searchAsset)
.limit(pagination.size)
.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) {
return searchAssetBuilder(this.db, options)
searchStatisticsV3(options: AssetSearchBuilderV3Options, scope: AssetSearchScope) {
return searchAssetBuilder(this.db, options, scope)
.select((qb) => qb.fn.countAll<number>().as('total'))
.executeTakeFirstOrThrow();
}

View File

@@ -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',

View File

@@ -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,
},
};
}

View File

@@ -38,7 +38,11 @@ import {
ExifOrientation,
SearchOrderField,
} from 'src/enum';
import { AssetSearchBuilderOptions, AssetSearchBuilderV3Options } from 'src/repositories/search.repository';
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';
@@ -762,7 +766,7 @@ function branchPredicates(eb: AssetExpressionBuilder, branch: SearchFilterBranch
// 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) {
export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuilderV3Options, scope: AssetSearchScope) {
const filter = options.filter ?? {};
return (
@@ -772,8 +776,9 @@ export function searchAssetBuilder(kysely: Kysely<DB>, options: AssetSearchBuild
// 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(!!options.userIds && options.userIds.length > 0, (qb) =>
qb.where('asset.ownerId', '=', anyUuid(options.userIds!)),
.$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))
@@ -809,86 +814,121 @@ export function withSearchOrder(qb: ReturnType<typeof searchAssetBuilder>, order
);
}
const scopeExample: AssetSearchScope = { userIds: [DummyValue.UUID], lockedOwnerId: DummyValue.UUID };
export const searchMetadataV3Examples: GenerateSqlQueries[] = [
{ name: 'baseline', params: [{ size: 100 }, { userIds: [DummyValue.UUID] }] },
{ name: 'empty', params: [{ size: 100 }, {}] },
{ name: 'baseline', params: [{ size: 100 }, {}, scopeExample] },
{ name: 'empty', params: [{ size: 100 }, {}, { lockedOwnerId: DummyValue.UUID }] },
{
name: 'or-exif-only',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { or: [{ city: { eq: DummyValue.STRING } }] } }],
params: [
{ size: 100 },
{
filter: { or: [{ city: { eq: DummyValue.STRING } }] },
},
scopeExample,
],
},
{
name: 'string-eq-null',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { city: { eq: null } } }],
params: [{ size: 100 }, { filter: { city: { eq: null } } }, scopeExample],
},
{
name: 'string-pattern-like',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { description: { like: DummyValue.STRING } } }],
params: [
{ size: 100 },
{
filter: { description: { like: DummyValue.STRING } },
},
scopeExample,
],
},
{
name: 'string-pattern-notLike',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { description: { notLike: DummyValue.STRING } } }],
params: [
{ size: 100 },
{
filter: { description: { notLike: DummyValue.STRING } },
},
scopeExample,
],
},
{
name: 'string-pattern-startsWith',
params: [
{ size: 100 },
{ userIds: [DummyValue.UUID], filter: { originalFileName: { startsWith: DummyValue.STRING } } },
{
filter: { originalFileName: { startsWith: DummyValue.STRING } },
},
scopeExample,
],
},
{
name: 'string-similarity-ocr',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { ocr: { matches: DummyValue.STRING } } }],
params: [{ size: 100 }, { filter: { ocr: { matches: DummyValue.STRING } } }, scopeExample],
},
{
name: 'ids-any',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { albumIds: { any: [DummyValue.UUID] } } }],
params: [{ size: 100 }, { filter: { albumIds: { any: [DummyValue.UUID] } } }, scopeExample],
},
{
name: 'ids-all',
params: [
{ size: 100 },
{ userIds: [DummyValue.UUID], filter: { personIds: { all: [DummyValue.UUID, DummyValue.UUID_1] } } },
{
filter: { personIds: { all: [DummyValue.UUID, DummyValue.UUID_1] } },
},
scopeExample,
],
},
{
name: 'ids-all-single',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { albumIds: { all: [DummyValue.UUID] } } }],
params: [{ size: 100 }, { filter: { albumIds: { all: [DummyValue.UUID] } } }, scopeExample],
},
{
name: 'ids-none',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { tagIds: { none: [DummyValue.UUID] } } }],
params: [{ size: 100 }, { filter: { tagIds: { none: [DummyValue.UUID] } } }, scopeExample],
},
{
name: 'ids-tags-all',
params: [
{ size: 100 },
{ userIds: [DummyValue.UUID], filter: { tagIds: { all: [DummyValue.UUID, DummyValue.UUID_1] } } },
{
filter: { tagIds: { all: [DummyValue.UUID, DummyValue.UUID_1] } },
},
scopeExample,
],
},
{
name: 'has-albums-false',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { hasAlbums: { eq: false } } }],
params: [{ size: 100 }, { filter: { hasAlbums: { eq: false } } }, scopeExample],
},
{
name: 'is-encoded',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { isEncoded: { eq: true } } }],
params: [{ size: 100 }, { filter: { isEncoded: { eq: true } } }, scopeExample],
},
{
name: 'number-range',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { fileSizeInBytes: { gte: 100, lte: 1000 } } }],
params: [
{ size: 100 },
{
filter: { fileSizeInBytes: { gte: 100, lte: 1000 } },
},
scopeExample,
],
},
{
name: 'date-eq',
params: [{ size: 100 }, { userIds: [DummyValue.UUID], filter: { takenAt: { eq: DummyValue.DATE } } }],
params: [{ size: 100 }, { filter: { takenAt: { eq: DummyValue.DATE } } }, scopeExample],
},
{
name: 'date-range',
params: [
{ size: 100 },
{
userIds: [DummyValue.UUID],
filter: { takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE } },
},
scopeExample,
],
},
{
@@ -896,10 +936,10 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
params: [
{ size: 100 },
{
userIds: [DummyValue.UUID],
order: { field: SearchOrderField.FileSizeInBytes, direction: AssetOrder.Desc },
withExif: false,
},
scopeExample,
],
},
{
@@ -907,10 +947,10 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
params: [
{ size: 100 },
{
userIds: [DummyValue.UUID],
order: { field: SearchOrderField.Rating, direction: AssetOrder.Asc },
withExif: true,
},
scopeExample,
],
},
{
@@ -918,11 +958,11 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
params: [
{ size: 100 },
{
userIds: [DummyValue.UUID],
filter: {
or: [{ isFavorite: { eq: true } }, { personIds: { any: [DummyValue.UUID] } }],
},
},
scopeExample,
],
},
{
@@ -930,39 +970,73 @@ export const searchMetadataV3Examples: GenerateSqlQueries[] = [
params: [
{ size: 100 },
{
userIds: [DummyValue.UUID],
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: [{ userIds: [DummyValue.UUID] }] },
{ name: 'baseline', params: [{}, scopeExample] },
{
name: 'with-filter',
params: [
{
userIds: [DummyValue.UUID],
filter: {
takenAt: { gte: DummyValue.DATE, lt: DummyValue.DATE },
fileSizeInBytes: { gte: 100 },
},
},
scopeExample,
],
},
{
name: 'with-or',
params: [
{
userIds: [DummyValue.UUID],
filter: {
or: [{ isFavorite: { eq: true } }, { hasAlbums: { eq: false } }],
},
},
scopeExample,
],
},
];

View 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'));
});
});

View 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');
}
};

View 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);
});
});

View 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;

View File

@@ -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);
});
});
});