mirror of
https://github.com/immich-app/immich.git
synced 2025-06-03 13:44:16 -04:00
feat(server): lighter buckets
This commit is contained in:
parent
37f5e6e2cb
commit
bfefa36f04
23
.vscode/settings.json
vendored
23
.vscode/settings.json
vendored
@ -8,7 +8,11 @@
|
|||||||
"[typescript]": {
|
"[typescript]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.tabSize": 2,
|
"editor.tabSize": 2,
|
||||||
"editor.formatOnSave": true
|
"editor.formatOnSave": true,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.removeUnusedImports": "explicit",
|
||||||
|
"source.organizeImports": "explicit"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"[css]": {
|
"[css]": {
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
@ -17,13 +21,14 @@
|
|||||||
},
|
},
|
||||||
"[svelte]": {
|
"[svelte]": {
|
||||||
"editor.defaultFormatter": "svelte.svelte-vscode",
|
"editor.defaultFormatter": "svelte.svelte-vscode",
|
||||||
"editor.tabSize": 2
|
"editor.tabSize": 2,
|
||||||
|
"editor.codeActionsOnSave": {
|
||||||
|
"source.removeUnusedImports": "explicit",
|
||||||
|
"source.organizeImports": "explicit"
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"svelte.enable-ts-plugin": true,
|
"svelte.enable-ts-plugin": true,
|
||||||
"eslint.validate": [
|
"eslint.validate": ["javascript", "svelte"],
|
||||||
"javascript",
|
|
||||||
"svelte"
|
|
||||||
],
|
|
||||||
"typescript.preferences.importModuleSpecifier": "non-relative",
|
"typescript.preferences.importModuleSpecifier": "non-relative",
|
||||||
"[dart]": {
|
"[dart]": {
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
@ -34,12 +39,10 @@
|
|||||||
"editor.wordBasedSuggestions": "off",
|
"editor.wordBasedSuggestions": "off",
|
||||||
"editor.defaultFormatter": "Dart-Code.dart-code"
|
"editor.defaultFormatter": "Dart-Code.dart-code"
|
||||||
},
|
},
|
||||||
"cSpell.words": [
|
"cSpell.words": ["immich"],
|
||||||
"immich"
|
|
||||||
],
|
|
||||||
"explorer.fileNesting.enabled": true,
|
"explorer.fileNesting.enabled": true,
|
||||||
"explorer.fileNesting.patterns": {
|
"explorer.fileNesting.patterns": {
|
||||||
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
|
"*.ts": "${capture}.spec.ts,${capture}.mock.ts",
|
||||||
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart"
|
"*.dart": "${capture}.g.dart,${capture}.gr.dart,${capture}.drift.dart"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
194
api.mustache
Normal file
194
api.mustache
Normal file
@ -0,0 +1,194 @@
|
|||||||
|
{{>header}}
|
||||||
|
{{>part_of}}
|
||||||
|
{{#operations}}
|
||||||
|
|
||||||
|
class {{{classname}}} {
|
||||||
|
{{{classname}}}([ApiClient? apiClient]) : apiClient = apiClient ?? defaultApiClient;
|
||||||
|
|
||||||
|
final ApiClient apiClient;
|
||||||
|
{{#operation}}
|
||||||
|
|
||||||
|
{{#summary}}
|
||||||
|
/// {{{.}}}
|
||||||
|
{{/summary}}
|
||||||
|
{{#notes}}
|
||||||
|
{{#summary}}
|
||||||
|
///
|
||||||
|
{{/summary}}
|
||||||
|
/// {{{notes}}}
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
{{/notes}}
|
||||||
|
{{^notes}}
|
||||||
|
{{#summary}}
|
||||||
|
///
|
||||||
|
/// Note: This method returns the HTTP [Response].
|
||||||
|
{{/summary}}
|
||||||
|
{{^summary}}
|
||||||
|
/// Performs an HTTP '{{{httpMethod}}} {{{path}}}' operation and returns the [Response].
|
||||||
|
{{/summary}}
|
||||||
|
{{/notes}}
|
||||||
|
{{#hasParams}}
|
||||||
|
{{#summary}}
|
||||||
|
///
|
||||||
|
{{/summary}}
|
||||||
|
{{^summary}}
|
||||||
|
{{#notes}}
|
||||||
|
///
|
||||||
|
{{/notes}}
|
||||||
|
{{/summary}}
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
{{/hasParams}}
|
||||||
|
{{#allParams}}
|
||||||
|
/// * [{{{dataType}}}] {{{paramName}}}{{#required}} (required){{/required}}{{#optional}} (optional){{/optional}}:
|
||||||
|
{{#description}}
|
||||||
|
/// {{{.}}}
|
||||||
|
{{/description}}
|
||||||
|
{{^-last}}
|
||||||
|
///
|
||||||
|
{{/-last}}
|
||||||
|
{{/allParams}}
|
||||||
|
Future<Response> {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
|
||||||
|
// ignore: prefer_const_declarations
|
||||||
|
final path = r'{{{path}}}'{{#pathParams}}
|
||||||
|
.replaceAll({{=<% %>=}}'{<% baseName %>}'<%={{ }}=%>, {{{paramName}}}{{^isString}}.toString(){{/isString}}){{/pathParams}};
|
||||||
|
|
||||||
|
// ignore: prefer_final_locals
|
||||||
|
Object? postBody{{#bodyParam}} = {{{paramName}}}{{/bodyParam}};
|
||||||
|
|
||||||
|
final queryParams = <QueryParam>[];
|
||||||
|
final headerParams = <String, String>{};
|
||||||
|
final formParams = <String, String>{};
|
||||||
|
{{#hasQueryParams}}
|
||||||
|
|
||||||
|
{{#queryParams}}
|
||||||
|
{{^required}}
|
||||||
|
if ({{{paramName}}} != null) {
|
||||||
|
{{/required}}
|
||||||
|
queryParams.addAll(_queryParams('{{{collectionFormat}}}', '{{{baseName}}}', {{{paramName}}}));
|
||||||
|
{{^required}}
|
||||||
|
}
|
||||||
|
{{/required}}
|
||||||
|
{{/queryParams}}
|
||||||
|
{{/hasQueryParams}}
|
||||||
|
{{#hasHeaderParams}}
|
||||||
|
|
||||||
|
{{#headerParams}}
|
||||||
|
{{#required}}
|
||||||
|
headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
|
||||||
|
{{/required}}
|
||||||
|
{{^required}}
|
||||||
|
if ({{{paramName}}} != null) {
|
||||||
|
headerParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
|
||||||
|
}
|
||||||
|
{{/required}}
|
||||||
|
{{/headerParams}}
|
||||||
|
{{/hasHeaderParams}}
|
||||||
|
|
||||||
|
const contentTypes = <String>[{{#prioritizedContentTypes}}'{{{mediaType}}}'{{^-last}}, {{/-last}}{{/prioritizedContentTypes}}];
|
||||||
|
|
||||||
|
{{#isMultipart}}
|
||||||
|
bool hasFields = false;
|
||||||
|
final mp = MultipartRequest('{{{httpMethod}}}', Uri.parse(path));
|
||||||
|
{{#formParams}}
|
||||||
|
{{^isFile}}
|
||||||
|
if ({{{paramName}}} != null) {
|
||||||
|
hasFields = true;
|
||||||
|
mp.fields[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
|
||||||
|
}
|
||||||
|
{{/isFile}}
|
||||||
|
{{#isFile}}
|
||||||
|
if ({{{paramName}}} != null) {
|
||||||
|
hasFields = true;
|
||||||
|
mp.fields[r'{{{baseName}}}'] = {{{paramName}}}.field;
|
||||||
|
mp.files.add({{{paramName}}});
|
||||||
|
}
|
||||||
|
{{/isFile}}
|
||||||
|
{{/formParams}}
|
||||||
|
if (hasFields) {
|
||||||
|
postBody = mp;
|
||||||
|
}
|
||||||
|
{{/isMultipart}}
|
||||||
|
{{^isMultipart}}
|
||||||
|
{{#formParams}}
|
||||||
|
{{^isFile}}
|
||||||
|
if ({{{paramName}}} != null) {
|
||||||
|
formParams[r'{{{baseName}}}'] = parameterToString({{{paramName}}});
|
||||||
|
}
|
||||||
|
{{/isFile}}
|
||||||
|
{{/formParams}}
|
||||||
|
{{/isMultipart}}
|
||||||
|
|
||||||
|
return apiClient.invokeAPI(
|
||||||
|
path,
|
||||||
|
'{{{httpMethod}}}',
|
||||||
|
queryParams,
|
||||||
|
postBody,
|
||||||
|
headerParams,
|
||||||
|
formParams,
|
||||||
|
contentTypes.isEmpty ? null : contentTypes.first,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{{#summary}}
|
||||||
|
/// {{{.}}}
|
||||||
|
{{/summary}}
|
||||||
|
{{#notes}}
|
||||||
|
{{#summary}}
|
||||||
|
///
|
||||||
|
{{/summary}}
|
||||||
|
/// {{{notes}}}
|
||||||
|
{{/notes}}
|
||||||
|
{{#hasParams}}
|
||||||
|
{{#summary}}
|
||||||
|
///
|
||||||
|
{{/summary}}
|
||||||
|
{{^summary}}
|
||||||
|
{{#notes}}
|
||||||
|
///
|
||||||
|
{{/notes}}
|
||||||
|
{{/summary}}
|
||||||
|
/// Parameters:
|
||||||
|
///
|
||||||
|
{{/hasParams}}
|
||||||
|
{{#allParams}}
|
||||||
|
/// * [{{{dataType}}}] {{{paramName}}}{{#required}} (required){{/required}}{{#optional}} (optional){{/optional}}:
|
||||||
|
{{#description}}
|
||||||
|
/// {{{.}}}
|
||||||
|
{{/description}}
|
||||||
|
{{^-last}}
|
||||||
|
///
|
||||||
|
{{/-last}}
|
||||||
|
{{/allParams}}
|
||||||
|
Future<{{#returnType}}{{{.}}}?{{/returnType}}{{^returnType}}void{{/returnType}}> {{{nickname}}}({{#allParams}}{{#required}}{{{dataType}}} {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}}{ {{#allParams}}{{^required}}{{{dataType}}}? {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} }{{/hasOptionalParams}}) async {
|
||||||
|
final response = await {{{nickname}}}WithHttpInfo({{#allParams}}{{#required}}{{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}}{{#hasOptionalParams}} {{#allParams}}{{^required}}{{{paramName}}}: {{{paramName}}},{{^-last}} {{/-last}}{{/required}}{{/allParams}} {{/hasOptionalParams}});
|
||||||
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
}
|
||||||
|
{{#returnType}}
|
||||||
|
// 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) {
|
||||||
|
{{#native_serialization}}
|
||||||
|
{{#isArray}}
|
||||||
|
final responseBody = await _decodeBodyBytes(response);
|
||||||
|
return (await apiClient.deserializeAsync(responseBody, '{{{returnType}}}') as List)
|
||||||
|
.cast<{{{returnBaseType}}}>()
|
||||||
|
.{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}};
|
||||||
|
{{/isArray}}
|
||||||
|
{{^isArray}}
|
||||||
|
{{#isMap}}
|
||||||
|
return {{{returnType}}}.from(await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}'),);
|
||||||
|
{{/isMap}}
|
||||||
|
{{^isMap}}
|
||||||
|
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), '{{{returnType}}}',) as {{{returnType}}};
|
||||||
|
{{/isMap}}{{/isArray}}{{/native_serialization}}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
{{/returnType}}
|
||||||
|
}
|
||||||
|
{{/operation}}
|
||||||
|
}
|
||||||
|
{{/operations}}
|
5
mobile/openapi/README.md
generated
5
mobile/openapi/README.md
generated
@ -475,8 +475,11 @@ Class | Method | HTTP request | Description
|
|||||||
- [TemplateDto](doc//TemplateDto.md)
|
- [TemplateDto](doc//TemplateDto.md)
|
||||||
- [TemplateResponseDto](doc//TemplateResponseDto.md)
|
- [TemplateResponseDto](doc//TemplateResponseDto.md)
|
||||||
- [TestEmailResponseDto](doc//TestEmailResponseDto.md)
|
- [TestEmailResponseDto](doc//TestEmailResponseDto.md)
|
||||||
|
- [TimeBucketAssetResponseDto](doc//TimeBucketAssetResponseDto.md)
|
||||||
|
- [TimeBucketAssetResponseDtoDurationInner](doc//TimeBucketAssetResponseDtoDurationInner.md)
|
||||||
- [TimeBucketResponseDto](doc//TimeBucketResponseDto.md)
|
- [TimeBucketResponseDto](doc//TimeBucketResponseDto.md)
|
||||||
- [TimeBucketSize](doc//TimeBucketSize.md)
|
- [TimeBucketsResponseDto](doc//TimeBucketsResponseDto.md)
|
||||||
|
- [TimelineStackResponseDto](doc//TimelineStackResponseDto.md)
|
||||||
- [ToneMapping](doc//ToneMapping.md)
|
- [ToneMapping](doc//ToneMapping.md)
|
||||||
- [TranscodeHWAccel](doc//TranscodeHWAccel.md)
|
- [TranscodeHWAccel](doc//TranscodeHWAccel.md)
|
||||||
- [TranscodePolicy](doc//TranscodePolicy.md)
|
- [TranscodePolicy](doc//TranscodePolicy.md)
|
||||||
|
5
mobile/openapi/lib/api.dart
generated
5
mobile/openapi/lib/api.dart
generated
@ -282,8 +282,11 @@ part 'model/tags_update.dart';
|
|||||||
part 'model/template_dto.dart';
|
part 'model/template_dto.dart';
|
||||||
part 'model/template_response_dto.dart';
|
part 'model/template_response_dto.dart';
|
||||||
part 'model/test_email_response_dto.dart';
|
part 'model/test_email_response_dto.dart';
|
||||||
|
part 'model/time_bucket_asset_response_dto.dart';
|
||||||
|
part 'model/time_bucket_asset_response_dto_duration_inner.dart';
|
||||||
part 'model/time_bucket_response_dto.dart';
|
part 'model/time_bucket_response_dto.dart';
|
||||||
part 'model/time_bucket_size.dart';
|
part 'model/time_buckets_response_dto.dart';
|
||||||
|
part 'model/timeline_stack_response_dto.dart';
|
||||||
part 'model/tone_mapping.dart';
|
part 'model/tone_mapping.dart';
|
||||||
part 'model/transcode_hw_accel.dart';
|
part 'model/transcode_hw_accel.dart';
|
||||||
part 'model/transcode_policy.dart';
|
part 'model/transcode_policy.dart';
|
||||||
|
47
mobile/openapi/lib/api/timeline_api.dart
generated
47
mobile/openapi/lib/api/timeline_api.dart
generated
@ -19,8 +19,6 @@ class TimelineApi {
|
|||||||
/// Performs an HTTP 'GET /timeline/bucket' operation and returns the [Response].
|
/// Performs an HTTP 'GET /timeline/bucket' operation and returns the [Response].
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [TimeBucketSize] size (required):
|
|
||||||
///
|
|
||||||
/// * [String] timeBucket (required):
|
/// * [String] timeBucket (required):
|
||||||
///
|
///
|
||||||
/// * [String] albumId:
|
/// * [String] albumId:
|
||||||
@ -35,6 +33,10 @@ class TimelineApi {
|
|||||||
///
|
///
|
||||||
/// * [AssetOrder] order:
|
/// * [AssetOrder] order:
|
||||||
///
|
///
|
||||||
|
/// * [num] page:
|
||||||
|
///
|
||||||
|
/// * [num] pageSize:
|
||||||
|
///
|
||||||
/// * [String] personId:
|
/// * [String] personId:
|
||||||
///
|
///
|
||||||
/// * [String] tagId:
|
/// * [String] tagId:
|
||||||
@ -44,7 +46,7 @@ class TimelineApi {
|
|||||||
/// * [bool] withPartners:
|
/// * [bool] withPartners:
|
||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
Future<Response> getTimeBucketWithHttpInfo(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
Future<Response> getTimeBucketWithHttpInfo(String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, num? page, num? pageSize, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/timeline/bucket';
|
final apiPath = r'/timeline/bucket';
|
||||||
|
|
||||||
@ -73,10 +75,15 @@ class TimelineApi {
|
|||||||
if (order != null) {
|
if (order != null) {
|
||||||
queryParams.addAll(_queryParams('', 'order', order));
|
queryParams.addAll(_queryParams('', 'order', order));
|
||||||
}
|
}
|
||||||
|
if (page != null) {
|
||||||
|
queryParams.addAll(_queryParams('', 'page', page));
|
||||||
|
}
|
||||||
|
if (pageSize != null) {
|
||||||
|
queryParams.addAll(_queryParams('', 'pageSize', pageSize));
|
||||||
|
}
|
||||||
if (personId != null) {
|
if (personId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'personId', personId));
|
queryParams.addAll(_queryParams('', 'personId', personId));
|
||||||
}
|
}
|
||||||
queryParams.addAll(_queryParams('', 'size', size));
|
|
||||||
if (tagId != null) {
|
if (tagId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'tagId', tagId));
|
queryParams.addAll(_queryParams('', 'tagId', tagId));
|
||||||
}
|
}
|
||||||
@ -107,8 +114,6 @@ class TimelineApi {
|
|||||||
|
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [TimeBucketSize] size (required):
|
|
||||||
///
|
|
||||||
/// * [String] timeBucket (required):
|
/// * [String] timeBucket (required):
|
||||||
///
|
///
|
||||||
/// * [String] albumId:
|
/// * [String] albumId:
|
||||||
@ -123,6 +128,10 @@ class TimelineApi {
|
|||||||
///
|
///
|
||||||
/// * [AssetOrder] order:
|
/// * [AssetOrder] order:
|
||||||
///
|
///
|
||||||
|
/// * [num] page:
|
||||||
|
///
|
||||||
|
/// * [num] pageSize:
|
||||||
|
///
|
||||||
/// * [String] personId:
|
/// * [String] personId:
|
||||||
///
|
///
|
||||||
/// * [String] tagId:
|
/// * [String] tagId:
|
||||||
@ -132,8 +141,8 @@ class TimelineApi {
|
|||||||
/// * [bool] withPartners:
|
/// * [bool] withPartners:
|
||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
Future<List<AssetResponseDto>?> getTimeBucket(TimeBucketSize size, String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
Future<TimeBucketResponseDto?> getTimeBucket(String timeBucket, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, num? page, num? pageSize, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||||
final response = await getTimeBucketWithHttpInfo(size, timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
final response = await getTimeBucketWithHttpInfo(timeBucket, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, page: page, pageSize: pageSize, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
@ -141,11 +150,8 @@ class TimelineApi {
|
|||||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||||
// FormatException when trying to decode an empty string.
|
// FormatException when trying to decode an empty string.
|
||||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
final responseBody = await _decodeBodyBytes(response);
|
return await apiClient.deserializeAsync(await _decodeBodyBytes(response), 'TimeBucketResponseDto',) as TimeBucketResponseDto;
|
||||||
return (await apiClient.deserializeAsync(responseBody, 'List<AssetResponseDto>') as List)
|
|
||||||
.cast<AssetResponseDto>()
|
|
||||||
.toList(growable: false);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@ -153,8 +159,6 @@ class TimelineApi {
|
|||||||
/// Performs an HTTP 'GET /timeline/buckets' operation and returns the [Response].
|
/// Performs an HTTP 'GET /timeline/buckets' operation and returns the [Response].
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [TimeBucketSize] size (required):
|
|
||||||
///
|
|
||||||
/// * [String] albumId:
|
/// * [String] albumId:
|
||||||
///
|
///
|
||||||
/// * [bool] isArchived:
|
/// * [bool] isArchived:
|
||||||
@ -176,7 +180,7 @@ class TimelineApi {
|
|||||||
/// * [bool] withPartners:
|
/// * [bool] withPartners:
|
||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
Future<Response> getTimeBucketsWithHttpInfo(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
Future<Response> getTimeBucketsWithHttpInfo({ String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/timeline/buckets';
|
final apiPath = r'/timeline/buckets';
|
||||||
|
|
||||||
@ -208,7 +212,6 @@ class TimelineApi {
|
|||||||
if (personId != null) {
|
if (personId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'personId', personId));
|
queryParams.addAll(_queryParams('', 'personId', personId));
|
||||||
}
|
}
|
||||||
queryParams.addAll(_queryParams('', 'size', size));
|
|
||||||
if (tagId != null) {
|
if (tagId != null) {
|
||||||
queryParams.addAll(_queryParams('', 'tagId', tagId));
|
queryParams.addAll(_queryParams('', 'tagId', tagId));
|
||||||
}
|
}
|
||||||
@ -238,8 +241,6 @@ class TimelineApi {
|
|||||||
|
|
||||||
/// Parameters:
|
/// Parameters:
|
||||||
///
|
///
|
||||||
/// * [TimeBucketSize] size (required):
|
|
||||||
///
|
|
||||||
/// * [String] albumId:
|
/// * [String] albumId:
|
||||||
///
|
///
|
||||||
/// * [bool] isArchived:
|
/// * [bool] isArchived:
|
||||||
@ -261,8 +262,8 @@ class TimelineApi {
|
|||||||
/// * [bool] withPartners:
|
/// * [bool] withPartners:
|
||||||
///
|
///
|
||||||
/// * [bool] withStacked:
|
/// * [bool] withStacked:
|
||||||
Future<List<TimeBucketResponseDto>?> getTimeBuckets(TimeBucketSize size, { String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
Future<List<TimeBucketsResponseDto>?> getTimeBuckets({ String? albumId, bool? isArchived, bool? isFavorite, bool? isTrashed, String? key, AssetOrder? order, String? personId, String? tagId, String? userId, bool? withPartners, bool? withStacked, }) async {
|
||||||
final response = await getTimeBucketsWithHttpInfo(size, albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
final response = await getTimeBucketsWithHttpInfo( albumId: albumId, isArchived: isArchived, isFavorite: isFavorite, isTrashed: isTrashed, key: key, order: order, personId: personId, tagId: tagId, userId: userId, withPartners: withPartners, withStacked: withStacked, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
}
|
}
|
||||||
@ -271,8 +272,8 @@ class TimelineApi {
|
|||||||
// FormatException when trying to decode an empty string.
|
// FormatException when trying to decode an empty string.
|
||||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||||
final responseBody = await _decodeBodyBytes(response);
|
final responseBody = await _decodeBodyBytes(response);
|
||||||
return (await apiClient.deserializeAsync(responseBody, 'List<TimeBucketResponseDto>') as List)
|
return (await apiClient.deserializeAsync(responseBody, 'List<TimeBucketsResponseDto>') as List)
|
||||||
.cast<TimeBucketResponseDto>()
|
.cast<TimeBucketsResponseDto>()
|
||||||
.toList(growable: false);
|
.toList(growable: false);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
10
mobile/openapi/lib/api_client.dart
generated
10
mobile/openapi/lib/api_client.dart
generated
@ -620,10 +620,16 @@ class ApiClient {
|
|||||||
return TemplateResponseDto.fromJson(value);
|
return TemplateResponseDto.fromJson(value);
|
||||||
case 'TestEmailResponseDto':
|
case 'TestEmailResponseDto':
|
||||||
return TestEmailResponseDto.fromJson(value);
|
return TestEmailResponseDto.fromJson(value);
|
||||||
|
case 'TimeBucketAssetResponseDto':
|
||||||
|
return TimeBucketAssetResponseDto.fromJson(value);
|
||||||
|
case 'TimeBucketAssetResponseDtoDurationInner':
|
||||||
|
return TimeBucketAssetResponseDtoDurationInner.fromJson(value);
|
||||||
case 'TimeBucketResponseDto':
|
case 'TimeBucketResponseDto':
|
||||||
return TimeBucketResponseDto.fromJson(value);
|
return TimeBucketResponseDto.fromJson(value);
|
||||||
case 'TimeBucketSize':
|
case 'TimeBucketsResponseDto':
|
||||||
return TimeBucketSizeTypeTransformer().decode(value);
|
return TimeBucketsResponseDto.fromJson(value);
|
||||||
|
case 'TimelineStackResponseDto':
|
||||||
|
return TimelineStackResponseDto.fromJson(value);
|
||||||
case 'ToneMapping':
|
case 'ToneMapping':
|
||||||
return ToneMappingTypeTransformer().decode(value);
|
return ToneMappingTypeTransformer().decode(value);
|
||||||
case 'TranscodeHWAccel':
|
case 'TranscodeHWAccel':
|
||||||
|
3
mobile/openapi/lib/api_helper.dart
generated
3
mobile/openapi/lib/api_helper.dart
generated
@ -133,9 +133,6 @@ String parameterToString(dynamic value) {
|
|||||||
if (value is SyncRequestType) {
|
if (value is SyncRequestType) {
|
||||||
return SyncRequestTypeTypeTransformer().encode(value).toString();
|
return SyncRequestTypeTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
if (value is TimeBucketSize) {
|
|
||||||
return TimeBucketSizeTypeTransformer().encode(value).toString();
|
|
||||||
}
|
|
||||||
if (value is ToneMapping) {
|
if (value is ToneMapping) {
|
||||||
return ToneMappingTypeTransformer().encode(value).toString();
|
return ToneMappingTypeTransformer().encode(value).toString();
|
||||||
}
|
}
|
||||||
|
219
mobile/openapi/lib/model/time_bucket_asset_response_dto.dart
generated
Normal file
219
mobile/openapi/lib/model/time_bucket_asset_response_dto.dart
generated
Normal file
@ -0,0 +1,219 @@
|
|||||||
|
//
|
||||||
|
// 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 TimeBucketAssetResponseDto {
|
||||||
|
/// Returns a new [TimeBucketAssetResponseDto] instance.
|
||||||
|
TimeBucketAssetResponseDto({
|
||||||
|
this.duration = const [],
|
||||||
|
this.id = const [],
|
||||||
|
this.isArchived = const [],
|
||||||
|
this.isFavorite = const [],
|
||||||
|
this.isImage = const [],
|
||||||
|
this.isTrashed = const [],
|
||||||
|
this.isVideo = const [],
|
||||||
|
this.livePhotoVideoId = const [],
|
||||||
|
this.localDateTime = const [],
|
||||||
|
this.ownerId = const [],
|
||||||
|
this.projectionType = const [],
|
||||||
|
this.ratio = const [],
|
||||||
|
this.stack = const [],
|
||||||
|
this.thumbhash = const [],
|
||||||
|
});
|
||||||
|
|
||||||
|
List<TimeBucketAssetResponseDtoDurationInner> duration;
|
||||||
|
|
||||||
|
List<String> id;
|
||||||
|
|
||||||
|
List<num> isArchived;
|
||||||
|
|
||||||
|
List<num> isFavorite;
|
||||||
|
|
||||||
|
List<num> isImage;
|
||||||
|
|
||||||
|
List<num> isTrashed;
|
||||||
|
|
||||||
|
List<num> isVideo;
|
||||||
|
|
||||||
|
List<TimeBucketAssetResponseDtoDurationInner> livePhotoVideoId;
|
||||||
|
|
||||||
|
List<DateTime> localDateTime;
|
||||||
|
|
||||||
|
List<String> ownerId;
|
||||||
|
|
||||||
|
List<TimeBucketAssetResponseDtoDurationInner> projectionType;
|
||||||
|
|
||||||
|
List<num> ratio;
|
||||||
|
|
||||||
|
List<TimelineStackResponseDto> stack;
|
||||||
|
|
||||||
|
List<TimeBucketAssetResponseDtoDurationInner> thumbhash;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is TimeBucketAssetResponseDto &&
|
||||||
|
_deepEquality.equals(other.duration, duration) &&
|
||||||
|
_deepEquality.equals(other.id, id) &&
|
||||||
|
_deepEquality.equals(other.isArchived, isArchived) &&
|
||||||
|
_deepEquality.equals(other.isFavorite, isFavorite) &&
|
||||||
|
_deepEquality.equals(other.isImage, isImage) &&
|
||||||
|
_deepEquality.equals(other.isTrashed, isTrashed) &&
|
||||||
|
_deepEquality.equals(other.isVideo, isVideo) &&
|
||||||
|
_deepEquality.equals(other.livePhotoVideoId, livePhotoVideoId) &&
|
||||||
|
_deepEquality.equals(other.localDateTime, localDateTime) &&
|
||||||
|
_deepEquality.equals(other.ownerId, ownerId) &&
|
||||||
|
_deepEquality.equals(other.projectionType, projectionType) &&
|
||||||
|
_deepEquality.equals(other.ratio, ratio) &&
|
||||||
|
_deepEquality.equals(other.stack, stack) &&
|
||||||
|
_deepEquality.equals(other.thumbhash, thumbhash);
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(duration.hashCode) +
|
||||||
|
(id.hashCode) +
|
||||||
|
(isArchived.hashCode) +
|
||||||
|
(isFavorite.hashCode) +
|
||||||
|
(isImage.hashCode) +
|
||||||
|
(isTrashed.hashCode) +
|
||||||
|
(isVideo.hashCode) +
|
||||||
|
(livePhotoVideoId.hashCode) +
|
||||||
|
(localDateTime.hashCode) +
|
||||||
|
(ownerId.hashCode) +
|
||||||
|
(projectionType.hashCode) +
|
||||||
|
(ratio.hashCode) +
|
||||||
|
(stack.hashCode) +
|
||||||
|
(thumbhash.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'TimeBucketAssetResponseDto[duration=$duration, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isImage=$isImage, isTrashed=$isTrashed, isVideo=$isVideo, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, ownerId=$ownerId, projectionType=$projectionType, ratio=$ratio, stack=$stack, thumbhash=$thumbhash]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'duration'] = this.duration;
|
||||||
|
json[r'id'] = this.id;
|
||||||
|
json[r'isArchived'] = this.isArchived;
|
||||||
|
json[r'isFavorite'] = this.isFavorite;
|
||||||
|
json[r'isImage'] = this.isImage;
|
||||||
|
json[r'isTrashed'] = this.isTrashed;
|
||||||
|
json[r'isVideo'] = this.isVideo;
|
||||||
|
json[r'livePhotoVideoId'] = this.livePhotoVideoId;
|
||||||
|
json[r'localDateTime'] = this.localDateTime;
|
||||||
|
json[r'ownerId'] = this.ownerId;
|
||||||
|
json[r'projectionType'] = this.projectionType;
|
||||||
|
json[r'ratio'] = this.ratio;
|
||||||
|
json[r'stack'] = this.stack;
|
||||||
|
json[r'thumbhash'] = this.thumbhash;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [TimeBucketAssetResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static TimeBucketAssetResponseDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "TimeBucketAssetResponseDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return TimeBucketAssetResponseDto(
|
||||||
|
duration: TimeBucketAssetResponseDtoDurationInner.listFromJson(json[r'duration']),
|
||||||
|
id: json[r'id'] is Iterable
|
||||||
|
? (json[r'id'] as Iterable).cast<String>().toList(growable: false)
|
||||||
|
: const [],
|
||||||
|
isArchived: json[r'isArchived'] is Iterable
|
||||||
|
? (json[r'isArchived'] as Iterable).cast<num>().toList(growable: false)
|
||||||
|
: const [],
|
||||||
|
isFavorite: json[r'isFavorite'] is Iterable
|
||||||
|
? (json[r'isFavorite'] as Iterable).cast<num>().toList(growable: false)
|
||||||
|
: const [],
|
||||||
|
isImage: json[r'isImage'] is Iterable
|
||||||
|
? (json[r'isImage'] as Iterable).cast<num>().toList(growable: false)
|
||||||
|
: const [],
|
||||||
|
isTrashed: json[r'isTrashed'] is Iterable
|
||||||
|
? (json[r'isTrashed'] as Iterable).cast<num>().toList(growable: false)
|
||||||
|
: const [],
|
||||||
|
isVideo: json[r'isVideo'] is Iterable
|
||||||
|
? (json[r'isVideo'] as Iterable).cast<num>().toList(growable: false)
|
||||||
|
: const [],
|
||||||
|
livePhotoVideoId: TimeBucketAssetResponseDtoDurationInner.listFromJson(json[r'livePhotoVideoId']),
|
||||||
|
localDateTime: DateTime.listFromJson(json[r'localDateTime']),
|
||||||
|
ownerId: json[r'ownerId'] is Iterable
|
||||||
|
? (json[r'ownerId'] as Iterable).cast<String>().toList(growable: false)
|
||||||
|
: const [],
|
||||||
|
projectionType: TimeBucketAssetResponseDtoDurationInner.listFromJson(json[r'projectionType']),
|
||||||
|
ratio: json[r'ratio'] is Iterable
|
||||||
|
? (json[r'ratio'] as Iterable).cast<num>().toList(growable: false)
|
||||||
|
: const [],
|
||||||
|
stack: TimelineStackResponseDto.listFromJson(json[r'stack']),
|
||||||
|
thumbhash: TimeBucketAssetResponseDtoDurationInner.listFromJson(json[r'thumbhash']),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<TimeBucketAssetResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <TimeBucketAssetResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = TimeBucketAssetResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, TimeBucketAssetResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, TimeBucketAssetResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = TimeBucketAssetResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of TimeBucketAssetResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<TimeBucketAssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<TimeBucketAssetResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = TimeBucketAssetResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'duration',
|
||||||
|
'id',
|
||||||
|
'isArchived',
|
||||||
|
'isFavorite',
|
||||||
|
'isImage',
|
||||||
|
'isTrashed',
|
||||||
|
'isVideo',
|
||||||
|
'livePhotoVideoId',
|
||||||
|
'localDateTime',
|
||||||
|
'ownerId',
|
||||||
|
'projectionType',
|
||||||
|
'ratio',
|
||||||
|
'stack',
|
||||||
|
'thumbhash',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
91
mobile/openapi/lib/model/time_bucket_asset_response_dto_duration_inner.dart
generated
Normal file
91
mobile/openapi/lib/model/time_bucket_asset_response_dto_duration_inner.dart
generated
Normal file
@ -0,0 +1,91 @@
|
|||||||
|
//
|
||||||
|
// 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 TimeBucketAssetResponseDtoDurationInner {
|
||||||
|
/// Returns a new [TimeBucketAssetResponseDtoDurationInner] instance.
|
||||||
|
TimeBucketAssetResponseDtoDurationInner({
|
||||||
|
});
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is TimeBucketAssetResponseDtoDurationInner &&
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'TimeBucketAssetResponseDtoDurationInner[]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [TimeBucketAssetResponseDtoDurationInner] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static TimeBucketAssetResponseDtoDurationInner? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "TimeBucketAssetResponseDtoDurationInner");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return TimeBucketAssetResponseDtoDurationInner(
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<TimeBucketAssetResponseDtoDurationInner> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <TimeBucketAssetResponseDtoDurationInner>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = TimeBucketAssetResponseDtoDurationInner.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, TimeBucketAssetResponseDtoDurationInner> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, TimeBucketAssetResponseDtoDurationInner>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = TimeBucketAssetResponseDtoDurationInner.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of TimeBucketAssetResponseDtoDurationInner-objects as value to a dart map
|
||||||
|
static Map<String, List<TimeBucketAssetResponseDtoDurationInner>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<TimeBucketAssetResponseDtoDurationInner>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = TimeBucketAssetResponseDtoDurationInner.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
@ -13,32 +13,32 @@ part of openapi.api;
|
|||||||
class TimeBucketResponseDto {
|
class TimeBucketResponseDto {
|
||||||
/// Returns a new [TimeBucketResponseDto] instance.
|
/// Returns a new [TimeBucketResponseDto] instance.
|
||||||
TimeBucketResponseDto({
|
TimeBucketResponseDto({
|
||||||
required this.count,
|
required this.bucketAssets,
|
||||||
required this.timeBucket,
|
required this.hasNextPage,
|
||||||
});
|
});
|
||||||
|
|
||||||
int count;
|
TimeBucketAssetResponseDto bucketAssets;
|
||||||
|
|
||||||
String timeBucket;
|
bool hasNextPage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
bool operator ==(Object other) => identical(this, other) || other is TimeBucketResponseDto &&
|
bool operator ==(Object other) => identical(this, other) || other is TimeBucketResponseDto &&
|
||||||
other.count == count &&
|
other.bucketAssets == bucketAssets &&
|
||||||
other.timeBucket == timeBucket;
|
other.hasNextPage == hasNextPage;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
int get hashCode =>
|
int get hashCode =>
|
||||||
// ignore: unnecessary_parenthesis
|
// ignore: unnecessary_parenthesis
|
||||||
(count.hashCode) +
|
(bucketAssets.hashCode) +
|
||||||
(timeBucket.hashCode);
|
(hasNextPage.hashCode);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String toString() => 'TimeBucketResponseDto[count=$count, timeBucket=$timeBucket]';
|
String toString() => 'TimeBucketResponseDto[bucketAssets=$bucketAssets, hasNextPage=$hasNextPage]';
|
||||||
|
|
||||||
Map<String, dynamic> toJson() {
|
Map<String, dynamic> toJson() {
|
||||||
final json = <String, dynamic>{};
|
final json = <String, dynamic>{};
|
||||||
json[r'count'] = this.count;
|
json[r'bucketAssets'] = this.bucketAssets;
|
||||||
json[r'timeBucket'] = this.timeBucket;
|
json[r'hasNextPage'] = this.hasNextPage;
|
||||||
return json;
|
return json;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -51,8 +51,8 @@ class TimeBucketResponseDto {
|
|||||||
final json = value.cast<String, dynamic>();
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
return TimeBucketResponseDto(
|
return TimeBucketResponseDto(
|
||||||
count: mapValueOfType<int>(json, r'count')!,
|
bucketAssets: TimeBucketAssetResponseDto.fromJson(json[r'bucketAssets'])!,
|
||||||
timeBucket: mapValueOfType<String>(json, r'timeBucket')!,
|
hasNextPage: mapValueOfType<bool>(json, r'hasNextPage')!,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
@ -100,8 +100,8 @@ class TimeBucketResponseDto {
|
|||||||
|
|
||||||
/// The list of required keys that must be present in a JSON.
|
/// The list of required keys that must be present in a JSON.
|
||||||
static const requiredKeys = <String>{
|
static const requiredKeys = <String>{
|
||||||
'count',
|
'bucketAssets',
|
||||||
'timeBucket',
|
'hasNextPage',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
85
mobile/openapi/lib/model/time_bucket_size.dart
generated
85
mobile/openapi/lib/model/time_bucket_size.dart
generated
@ -1,85 +0,0 @@
|
|||||||
//
|
|
||||||
// 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 TimeBucketSize {
|
|
||||||
/// Instantiate a new enum with the provided [value].
|
|
||||||
const TimeBucketSize._(this.value);
|
|
||||||
|
|
||||||
/// The underlying value of this enum member.
|
|
||||||
final String value;
|
|
||||||
|
|
||||||
@override
|
|
||||||
String toString() => value;
|
|
||||||
|
|
||||||
String toJson() => value;
|
|
||||||
|
|
||||||
static const DAY = TimeBucketSize._(r'DAY');
|
|
||||||
static const MONTH = TimeBucketSize._(r'MONTH');
|
|
||||||
|
|
||||||
/// List of all possible values in this [enum][TimeBucketSize].
|
|
||||||
static const values = <TimeBucketSize>[
|
|
||||||
DAY,
|
|
||||||
MONTH,
|
|
||||||
];
|
|
||||||
|
|
||||||
static TimeBucketSize? fromJson(dynamic value) => TimeBucketSizeTypeTransformer().decode(value);
|
|
||||||
|
|
||||||
static List<TimeBucketSize> listFromJson(dynamic json, {bool growable = false,}) {
|
|
||||||
final result = <TimeBucketSize>[];
|
|
||||||
if (json is List && json.isNotEmpty) {
|
|
||||||
for (final row in json) {
|
|
||||||
final value = TimeBucketSize.fromJson(row);
|
|
||||||
if (value != null) {
|
|
||||||
result.add(value);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result.toList(growable: growable);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Transformation class that can [encode] an instance of [TimeBucketSize] to String,
|
|
||||||
/// and [decode] dynamic data back to [TimeBucketSize].
|
|
||||||
class TimeBucketSizeTypeTransformer {
|
|
||||||
factory TimeBucketSizeTypeTransformer() => _instance ??= const TimeBucketSizeTypeTransformer._();
|
|
||||||
|
|
||||||
const TimeBucketSizeTypeTransformer._();
|
|
||||||
|
|
||||||
String encode(TimeBucketSize data) => data.value;
|
|
||||||
|
|
||||||
/// Decodes a [dynamic value][data] to a TimeBucketSize.
|
|
||||||
///
|
|
||||||
/// 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.
|
|
||||||
TimeBucketSize? decode(dynamic data, {bool allowNull = true}) {
|
|
||||||
if (data != null) {
|
|
||||||
switch (data) {
|
|
||||||
case r'DAY': return TimeBucketSize.DAY;
|
|
||||||
case r'MONTH': return TimeBucketSize.MONTH;
|
|
||||||
default:
|
|
||||||
if (!allowNull) {
|
|
||||||
throw ArgumentError('Unknown enum value to decode: $data');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Singleton [TimeBucketSizeTypeTransformer] instance.
|
|
||||||
static TimeBucketSizeTypeTransformer? _instance;
|
|
||||||
}
|
|
||||||
|
|
107
mobile/openapi/lib/model/time_buckets_response_dto.dart
generated
Normal file
107
mobile/openapi/lib/model/time_buckets_response_dto.dart
generated
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
//
|
||||||
|
// 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 TimeBucketsResponseDto {
|
||||||
|
/// Returns a new [TimeBucketsResponseDto] instance.
|
||||||
|
TimeBucketsResponseDto({
|
||||||
|
required this.count,
|
||||||
|
required this.timeBucket,
|
||||||
|
});
|
||||||
|
|
||||||
|
int count;
|
||||||
|
|
||||||
|
String timeBucket;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is TimeBucketsResponseDto &&
|
||||||
|
other.count == count &&
|
||||||
|
other.timeBucket == timeBucket;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(count.hashCode) +
|
||||||
|
(timeBucket.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'TimeBucketsResponseDto[count=$count, timeBucket=$timeBucket]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'count'] = this.count;
|
||||||
|
json[r'timeBucket'] = this.timeBucket;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [TimeBucketsResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static TimeBucketsResponseDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "TimeBucketsResponseDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return TimeBucketsResponseDto(
|
||||||
|
count: mapValueOfType<int>(json, r'count')!,
|
||||||
|
timeBucket: mapValueOfType<String>(json, r'timeBucket')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<TimeBucketsResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <TimeBucketsResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = TimeBucketsResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, TimeBucketsResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, TimeBucketsResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = TimeBucketsResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of TimeBucketsResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<TimeBucketsResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<TimeBucketsResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = TimeBucketsResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'count',
|
||||||
|
'timeBucket',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
115
mobile/openapi/lib/model/timeline_stack_response_dto.dart
generated
Normal file
115
mobile/openapi/lib/model/timeline_stack_response_dto.dart
generated
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
//
|
||||||
|
// 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 TimelineStackResponseDto {
|
||||||
|
/// Returns a new [TimelineStackResponseDto] instance.
|
||||||
|
TimelineStackResponseDto({
|
||||||
|
required this.assetCount,
|
||||||
|
required this.id,
|
||||||
|
required this.primaryAssetId,
|
||||||
|
});
|
||||||
|
|
||||||
|
num assetCount;
|
||||||
|
|
||||||
|
String id;
|
||||||
|
|
||||||
|
String primaryAssetId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is TimelineStackResponseDto &&
|
||||||
|
other.assetCount == assetCount &&
|
||||||
|
other.id == id &&
|
||||||
|
other.primaryAssetId == primaryAssetId;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(assetCount.hashCode) +
|
||||||
|
(id.hashCode) +
|
||||||
|
(primaryAssetId.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'TimelineStackResponseDto[assetCount=$assetCount, id=$id, primaryAssetId=$primaryAssetId]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'assetCount'] = this.assetCount;
|
||||||
|
json[r'id'] = this.id;
|
||||||
|
json[r'primaryAssetId'] = this.primaryAssetId;
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [TimelineStackResponseDto] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static TimelineStackResponseDto? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "TimelineStackResponseDto");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return TimelineStackResponseDto(
|
||||||
|
assetCount: num.parse('${json[r'assetCount']}'),
|
||||||
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
|
primaryAssetId: mapValueOfType<String>(json, r'primaryAssetId')!,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<TimelineStackResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <TimelineStackResponseDto>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = TimelineStackResponseDto.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, TimelineStackResponseDto> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, TimelineStackResponseDto>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = TimelineStackResponseDto.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of TimelineStackResponseDto-objects as value to a dart map
|
||||||
|
static Map<String, List<TimelineStackResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<TimelineStackResponseDto>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = TimelineStackResponseDto.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'assetCount',
|
||||||
|
'id',
|
||||||
|
'primaryAssetId',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
301
open-api/bin/native_class.mustache
Normal file
301
open-api/bin/native_class.mustache
Normal file
@ -0,0 +1,301 @@
|
|||||||
|
class {{{classname}}} {
|
||||||
|
{{>dart_constructor}}
|
||||||
|
{{#vars}}
|
||||||
|
{{#description}}
|
||||||
|
/// {{{.}}}
|
||||||
|
{{/description}}
|
||||||
|
{{^isEnum}}
|
||||||
|
{{#minimum}}
|
||||||
|
{{#description}}
|
||||||
|
///
|
||||||
|
{{/description}}
|
||||||
|
/// Minimum value: {{{.}}}
|
||||||
|
{{/minimum}}
|
||||||
|
{{#maximum}}
|
||||||
|
{{#description}}
|
||||||
|
{{^minimum}}
|
||||||
|
///
|
||||||
|
{{/minimum}}
|
||||||
|
{{/description}}
|
||||||
|
/// Maximum value: {{{.}}}
|
||||||
|
{{/maximum}}
|
||||||
|
{{^isNullable}}
|
||||||
|
{{^required}}
|
||||||
|
{{^defaultValue}}
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
{{/defaultValue}}
|
||||||
|
{{/required}}
|
||||||
|
{{/isNullable}}
|
||||||
|
{{/isEnum}}
|
||||||
|
{{{datatypeWithEnum}}}{{#isNullable}}?{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}?{{/defaultValue}}{{/required}}{{/isNullable}} {{{name}}};
|
||||||
|
|
||||||
|
{{/vars}}
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is {{{classname}}} &&
|
||||||
|
{{#vars}}
|
||||||
|
{{#isMap}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isMap}}{{^isMap}}{{#isArray}}_deepEquality.equals(other.{{{name}}}, {{{name}}}){{/isArray}}{{^isArray}}other.{{{name}}} == {{{name}}}{{/isArray}}{{/isMap}}{{^-last}} &&{{/-last}}{{#-last}};{{/-last}}
|
||||||
|
{{/vars}}
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
{{#vars}}
|
||||||
|
({{#isNullable}}{{{name}}} == null ? 0 : {{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}{{{name}}} == null ? 0 : {{/defaultValue}}{{/required}}{{/isNullable}}{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.hashCode){{^-last}} +{{/-last}}{{#-last}};{{/-last}}
|
||||||
|
{{/vars}}
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => '{{{classname}}}[{{#vars}}{{{name}}}=${{{name}}}{{^-last}}, {{/-last}}{{/vars}}]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
{{#vars}}
|
||||||
|
{{#isNullable}}
|
||||||
|
if (this.{{{name}}} != null) {
|
||||||
|
{{/isNullable}}
|
||||||
|
{{^isNullable}}
|
||||||
|
{{^required}}
|
||||||
|
{{^defaultValue}}
|
||||||
|
if (this.{{{name}}} != null) {
|
||||||
|
{{/defaultValue}}
|
||||||
|
{{/required}}
|
||||||
|
{{/isNullable}}
|
||||||
|
{{#isDateTime}}
|
||||||
|
{{#pattern}}
|
||||||
|
json[r'{{{baseName}}}'] = _isEpochMarker(r'{{{pattern}}}')
|
||||||
|
? this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.millisecondsSinceEpoch
|
||||||
|
: this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc().toIso8601String();
|
||||||
|
{{/pattern}}
|
||||||
|
{{^pattern}}
|
||||||
|
json[r'{{{baseName}}}'] = this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc().toIso8601String();
|
||||||
|
{{/pattern}}
|
||||||
|
{{/isDateTime}}
|
||||||
|
{{#isDate}}
|
||||||
|
{{#pattern}}
|
||||||
|
json[r'{{{baseName}}}'] = _isEpochMarker(r'{{{pattern}}}')
|
||||||
|
? this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.millisecondsSinceEpoch
|
||||||
|
: _dateFormatter.format(this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc());
|
||||||
|
{{/pattern}}
|
||||||
|
{{^pattern}}
|
||||||
|
json[r'{{{baseName}}}'] = _dateFormatter.format(this.{{{name}}}{{#isNullable}}!{{/isNullable}}{{^isNullable}}{{^required}}{{^defaultValue}}!{{/defaultValue}}{{/required}}{{/isNullable}}.toUtc());
|
||||||
|
{{/pattern}}
|
||||||
|
{{/isDate}}
|
||||||
|
{{^isDateTime}}
|
||||||
|
{{^isDate}}
|
||||||
|
json[r'{{{baseName}}}'] = this.{{{name}}}{{#isArray}}{{#uniqueItems}}{{#isNullable}}!{{/isNullable}}.toList(growable: false){{/uniqueItems}}{{/isArray}};
|
||||||
|
{{/isDate}}
|
||||||
|
{{/isDateTime}}
|
||||||
|
{{#isNullable}}
|
||||||
|
} else {
|
||||||
|
json[r'{{{baseName}}}'] = null;
|
||||||
|
}
|
||||||
|
{{/isNullable}}
|
||||||
|
{{^isNullable}}
|
||||||
|
{{^required}}
|
||||||
|
{{^defaultValue}}
|
||||||
|
} else {
|
||||||
|
json[r'{{{baseName}}}'] = null;
|
||||||
|
}
|
||||||
|
{{/defaultValue}}
|
||||||
|
{{/required}}
|
||||||
|
{{/isNullable}}
|
||||||
|
{{/vars}}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [{{{classname}}}] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static {{{classname}}}? fromJson(dynamic value) {
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
// Ensure that the map contains the required keys.
|
||||||
|
// Note 1: the values aren't checked for validity beyond being non-null.
|
||||||
|
// Note 2: this code is stripped in release mode!
|
||||||
|
assert(() {
|
||||||
|
requiredKeys.forEach((key) {
|
||||||
|
assert(json.containsKey(key), 'Required key "{{{classname}}}[$key]" is missing from JSON.');
|
||||||
|
assert(json[key] != null, 'Required key "{{{classname}}}[$key]" has a null value in JSON.');
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}());
|
||||||
|
|
||||||
|
return {{{classname}}}(
|
||||||
|
{{#vars}}
|
||||||
|
{{#isDateTime}}
|
||||||
|
{{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/isDateTime}}
|
||||||
|
{{#isDate}}
|
||||||
|
{{{name}}}: mapDateTime(json, r'{{{baseName}}}', r'{{{pattern}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/isDate}}
|
||||||
|
{{^isDateTime}}
|
||||||
|
{{^isDate}}
|
||||||
|
{{#complexType}}
|
||||||
|
{{#isArray}}
|
||||||
|
{{#items.isArray}}
|
||||||
|
{{{name}}}: json[r'{{{baseName}}}'] is List
|
||||||
|
? (json[r'{{{baseName}}}'] as List).map((e) =>
|
||||||
|
{{#items.complexType}}
|
||||||
|
{{items.complexType}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}}
|
||||||
|
{{/items.complexType}}
|
||||||
|
{{^items.complexType}}
|
||||||
|
e == null ? {{#items.isNullable}}null{{/items.isNullable}}{{^items.isNullable}}const <{{items.items.dataType}}>[]{{/items.isNullable}} : (e as List).cast<{{items.items.dataType}}>()
|
||||||
|
{{/items.complexType}}
|
||||||
|
).toList()
|
||||||
|
: {{#isNullable}}null{{/isNullable}}{{^isNullable}}const []{{/isNullable}},
|
||||||
|
{{/items.isArray}}
|
||||||
|
{{^items.isArray}}
|
||||||
|
{{{name}}}: {{{complexType}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}},
|
||||||
|
{{/items.isArray}}
|
||||||
|
{{/isArray}}
|
||||||
|
{{^isArray}}
|
||||||
|
{{#isMap}}
|
||||||
|
{{#items.isArray}}
|
||||||
|
{{{name}}}: json[r'{{{baseName}}}'] == null
|
||||||
|
? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}
|
||||||
|
{{#items.complexType}}
|
||||||
|
: {{items.complexType}}.mapListFromJson(json[r'{{{baseName}}}']),
|
||||||
|
{{/items.complexType}}
|
||||||
|
{{^items.complexType}}
|
||||||
|
: mapCastOfType<String, List>(json, r'{{{baseName}}}'),
|
||||||
|
{{/items.complexType}}
|
||||||
|
{{/items.isArray}}
|
||||||
|
{{^items.isArray}}
|
||||||
|
{{#items.isMap}}
|
||||||
|
{{#items.complexType}}
|
||||||
|
{{{name}}}: {{items.complexType}}.mapFromJson(json[r'{{{baseName}}}']),
|
||||||
|
{{/items.complexType}}
|
||||||
|
{{^items.complexType}}
|
||||||
|
{{{name}}}: mapCastOfType<String, dynamic>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/items.complexType}}
|
||||||
|
{{/items.isMap}}
|
||||||
|
{{^items.isMap}}
|
||||||
|
{{#items.complexType}}
|
||||||
|
{{{name}}}: {{{items.complexType}}}.mapFromJson(json[r'{{{baseName}}}']),
|
||||||
|
{{/items.complexType}}
|
||||||
|
{{^items.complexType}}
|
||||||
|
{{{name}}}: mapCastOfType<String, {{items.dataType}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/items.complexType}}
|
||||||
|
{{/items.isMap}}
|
||||||
|
{{/items.isArray}}
|
||||||
|
{{/isMap}}
|
||||||
|
{{^isMap}}
|
||||||
|
{{#isBinary}}
|
||||||
|
{{{name}}}: null, // No support for decoding binary content from JSON
|
||||||
|
{{/isBinary}}
|
||||||
|
{{^isBinary}}
|
||||||
|
{{{name}}}: {{{complexType}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/isBinary}}
|
||||||
|
{{/isMap}}
|
||||||
|
{{/isArray}}
|
||||||
|
{{/complexType}}
|
||||||
|
{{^complexType}}
|
||||||
|
{{#isArray}}
|
||||||
|
{{#isEnum}}
|
||||||
|
{{{name}}}: {{{items.datatypeWithEnum}}}.listFromJson(json[r'{{{baseName}}}']){{#uniqueItems}}.toSet(){{/uniqueItems}},
|
||||||
|
{{/isEnum}}
|
||||||
|
{{^isEnum}}
|
||||||
|
{{{name}}}: json[r'{{{baseName}}}'] is Iterable
|
||||||
|
? (json[r'{{{baseName}}}'] as Iterable).cast<{{{items.datatype}}}>().{{#uniqueItems}}toSet(){{/uniqueItems}}{{^uniqueItems}}toList(growable: false){{/uniqueItems}}
|
||||||
|
: {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}},
|
||||||
|
{{/isEnum}}
|
||||||
|
{{/isArray}}
|
||||||
|
{{^isArray}}
|
||||||
|
{{#isMap}}
|
||||||
|
{{{name}}}: mapCastOfType<String, {{{items.datatype}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/isMap}}
|
||||||
|
{{^isMap}}
|
||||||
|
{{#isNumber}}
|
||||||
|
{{{name}}}: {{#isNullable}}json[r'{{{baseName}}}'] == null
|
||||||
|
? {{#defaultValue}}{{{.}}}{{/defaultValue}}{{^defaultValue}}null{{/defaultValue}}
|
||||||
|
: {{/isNullable}}{{{datatypeWithEnum}}}.parse('${json[r'{{{baseName}}}']}'),
|
||||||
|
{{/isNumber}}
|
||||||
|
{{^isNumber}}
|
||||||
|
{{^isEnum}}
|
||||||
|
{{{name}}}: mapValueOfType<{{{datatypeWithEnum}}}>(json, r'{{{baseName}}}'){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/isEnum}}
|
||||||
|
{{#isEnum}}
|
||||||
|
{{{name}}}: {{{enumName}}}.fromJson(json[r'{{{baseName}}}']){{#required}}{{^isNullable}}!{{/isNullable}}{{/required}}{{^required}}{{#defaultValue}} ?? {{{.}}}{{/defaultValue}}{{/required}},
|
||||||
|
{{/isEnum}}
|
||||||
|
{{/isNumber}}
|
||||||
|
{{/isMap}}
|
||||||
|
{{/isArray}}
|
||||||
|
{{/complexType}}
|
||||||
|
{{/isDate}}
|
||||||
|
{{/isDateTime}}
|
||||||
|
{{/vars}}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<{{{classname}}}> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <{{{classname}}}>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = {{{classname}}}.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, {{{classname}}}> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, {{{classname}}}>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = {{{classname}}}.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of {{{classname}}}-objects as value to a dart map
|
||||||
|
static Map<String, List<{{{classname}}}>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<{{{classname}}}>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = {{{classname}}}.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
{{#vars}}
|
||||||
|
{{#required}}
|
||||||
|
'{{{baseName}}}',
|
||||||
|
{{/required}}
|
||||||
|
{{/vars}}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
{{#vars}}
|
||||||
|
{{^isModel}}
|
||||||
|
{{#isEnum}}
|
||||||
|
{{^isContainer}}
|
||||||
|
|
||||||
|
{{>serialization/native/native_enum_inline}}
|
||||||
|
{{/isContainer}}
|
||||||
|
{{#isContainer}}
|
||||||
|
{{#mostInnerItems}}
|
||||||
|
|
||||||
|
{{>serialization/native/native_enum_inline}}
|
||||||
|
{{/mostInnerItems}}
|
||||||
|
{{/isContainer}}
|
||||||
|
{{/isEnum}}
|
||||||
|
{{/isModel}}
|
||||||
|
{{/vars}}
|
@ -6815,6 +6815,23 @@
|
|||||||
"$ref": "#/components/schemas/AssetOrder"
|
"$ref": "#/components/schemas/AssetOrder"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "page",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"minimum": 1,
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "pageSize",
|
||||||
|
"required": false,
|
||||||
|
"in": "query",
|
||||||
|
"schema": {
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "personId",
|
"name": "personId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@ -6824,14 +6841,6 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "size",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/TimeBucketSize"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "tagId",
|
"name": "tagId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@ -6880,10 +6889,7 @@
|
|||||||
"content": {
|
"content": {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"items": {
|
"$ref": "#/components/schemas/TimeBucketResponseDto"
|
||||||
"$ref": "#/components/schemas/AssetResponseDto"
|
|
||||||
},
|
|
||||||
"type": "array"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -6968,14 +6974,6 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "size",
|
|
||||||
"required": true,
|
|
||||||
"in": "query",
|
|
||||||
"schema": {
|
|
||||||
"$ref": "#/components/schemas/TimeBucketSize"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "tagId",
|
"name": "tagId",
|
||||||
"required": false,
|
"required": false,
|
||||||
@ -7017,7 +7015,7 @@
|
|||||||
"application/json": {
|
"application/json": {
|
||||||
"schema": {
|
"schema": {
|
||||||
"items": {
|
"items": {
|
||||||
"$ref": "#/components/schemas/TimeBucketResponseDto"
|
"$ref": "#/components/schemas/TimeBucketsResponseDto"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
}
|
}
|
||||||
@ -13405,7 +13403,170 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"TimeBucketAssetResponseDto": {
|
||||||
|
"properties": {
|
||||||
|
"duration": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"isArchived": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"isFavorite": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"isImage": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"isTrashed": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"isVideo": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"livePhotoVideoId": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"localDateTime": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"format": "date-time",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"ownerId": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"projectionType": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"ratio": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"type": "number"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"stack": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"$ref": "#/components/schemas/TimelineStackResponseDto"
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
},
|
||||||
|
"thumbhash": {
|
||||||
|
"default": [],
|
||||||
|
"items": {
|
||||||
|
"oneOf": [
|
||||||
|
{
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "number"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"type": "array"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"duration",
|
||||||
|
"id",
|
||||||
|
"isArchived",
|
||||||
|
"isFavorite",
|
||||||
|
"isImage",
|
||||||
|
"isTrashed",
|
||||||
|
"isVideo",
|
||||||
|
"livePhotoVideoId",
|
||||||
|
"localDateTime",
|
||||||
|
"ownerId",
|
||||||
|
"projectionType",
|
||||||
|
"ratio",
|
||||||
|
"stack",
|
||||||
|
"thumbhash"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"TimeBucketResponseDto": {
|
"TimeBucketResponseDto": {
|
||||||
|
"properties": {
|
||||||
|
"bucketAssets": {
|
||||||
|
"$ref": "#/components/schemas/TimeBucketAssetResponseDto"
|
||||||
|
},
|
||||||
|
"hasNextPage": {
|
||||||
|
"type": "boolean"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"bucketAssets",
|
||||||
|
"hasNextPage"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
|
"TimeBucketsResponseDto": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"count": {
|
"count": {
|
||||||
"type": "integer"
|
"type": "integer"
|
||||||
@ -13420,12 +13581,24 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"TimeBucketSize": {
|
"TimelineStackResponseDto": {
|
||||||
"enum": [
|
"properties": {
|
||||||
"DAY",
|
"assetCount": {
|
||||||
"MONTH"
|
"type": "number"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"primaryAssetId": {
|
||||||
|
"type": "string"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"assetCount",
|
||||||
|
"id",
|
||||||
|
"primaryAssetId"
|
||||||
],
|
],
|
||||||
"type": "string"
|
"type": "object"
|
||||||
},
|
},
|
||||||
"ToneMapping": {
|
"ToneMapping": {
|
||||||
"enum": [
|
"enum": [
|
||||||
|
@ -1380,7 +1380,32 @@ export type TagBulkAssetsResponseDto = {
|
|||||||
export type TagUpdateDto = {
|
export type TagUpdateDto = {
|
||||||
color?: string | null;
|
color?: string | null;
|
||||||
};
|
};
|
||||||
|
export type TimelineStackResponseDto = {
|
||||||
|
assetCount: number;
|
||||||
|
id: string;
|
||||||
|
primaryAssetId: string;
|
||||||
|
};
|
||||||
|
export type TimeBucketAssetResponseDto = {
|
||||||
|
duration: (string | number)[];
|
||||||
|
id: string[];
|
||||||
|
isArchived: number[];
|
||||||
|
isFavorite: number[];
|
||||||
|
isImage: number[];
|
||||||
|
isTrashed: number[];
|
||||||
|
isVideo: number[];
|
||||||
|
livePhotoVideoId: (string | number)[];
|
||||||
|
localDateTime: string[];
|
||||||
|
ownerId: string[];
|
||||||
|
projectionType: (string | number)[];
|
||||||
|
ratio: number[];
|
||||||
|
stack: TimelineStackResponseDto[];
|
||||||
|
thumbhash: (string | number)[];
|
||||||
|
};
|
||||||
export type TimeBucketResponseDto = {
|
export type TimeBucketResponseDto = {
|
||||||
|
bucketAssets: TimeBucketAssetResponseDto;
|
||||||
|
hasNextPage: boolean;
|
||||||
|
};
|
||||||
|
export type TimeBucketsResponseDto = {
|
||||||
count: number;
|
count: number;
|
||||||
timeBucket: string;
|
timeBucket: string;
|
||||||
};
|
};
|
||||||
@ -3201,15 +3226,16 @@ export function tagAssets({ id, bulkIdsDto }: {
|
|||||||
body: bulkIdsDto
|
body: bulkIdsDto
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, timeBucket, userId, withPartners, withStacked }: {
|
export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key, order, page, pageSize, personId, tagId, timeBucket, userId, withPartners, withStacked }: {
|
||||||
albumId?: string;
|
albumId?: string;
|
||||||
isArchived?: boolean;
|
isArchived?: boolean;
|
||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
isTrashed?: boolean;
|
isTrashed?: boolean;
|
||||||
key?: string;
|
key?: string;
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
|
page?: number;
|
||||||
|
pageSize?: number;
|
||||||
personId?: string;
|
personId?: string;
|
||||||
size: TimeBucketSize;
|
|
||||||
tagId?: string;
|
tagId?: string;
|
||||||
timeBucket: string;
|
timeBucket: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
@ -3218,7 +3244,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
|
|||||||
}, opts?: Oazapfts.RequestOpts) {
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
data: AssetResponseDto[];
|
data: TimeBucketResponseDto;
|
||||||
}>(`/timeline/bucket${QS.query(QS.explode({
|
}>(`/timeline/bucket${QS.query(QS.explode({
|
||||||
albumId,
|
albumId,
|
||||||
isArchived,
|
isArchived,
|
||||||
@ -3226,8 +3252,9 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
|
|||||||
isTrashed,
|
isTrashed,
|
||||||
key,
|
key,
|
||||||
order,
|
order,
|
||||||
|
page,
|
||||||
|
pageSize,
|
||||||
personId,
|
personId,
|
||||||
size,
|
|
||||||
tagId,
|
tagId,
|
||||||
timeBucket,
|
timeBucket,
|
||||||
userId,
|
userId,
|
||||||
@ -3237,7 +3264,7 @@ export function getTimeBucket({ albumId, isArchived, isFavorite, isTrashed, key,
|
|||||||
...opts
|
...opts
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, size, tagId, userId, withPartners, withStacked }: {
|
export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key, order, personId, tagId, userId, withPartners, withStacked }: {
|
||||||
albumId?: string;
|
albumId?: string;
|
||||||
isArchived?: boolean;
|
isArchived?: boolean;
|
||||||
isFavorite?: boolean;
|
isFavorite?: boolean;
|
||||||
@ -3245,7 +3272,6 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
|
|||||||
key?: string;
|
key?: string;
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
personId?: string;
|
personId?: string;
|
||||||
size: TimeBucketSize;
|
|
||||||
tagId?: string;
|
tagId?: string;
|
||||||
userId?: string;
|
userId?: string;
|
||||||
withPartners?: boolean;
|
withPartners?: boolean;
|
||||||
@ -3253,7 +3279,7 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
|
|||||||
}, opts?: Oazapfts.RequestOpts) {
|
}, opts?: Oazapfts.RequestOpts) {
|
||||||
return oazapfts.ok(oazapfts.fetchJson<{
|
return oazapfts.ok(oazapfts.fetchJson<{
|
||||||
status: 200;
|
status: 200;
|
||||||
data: TimeBucketResponseDto[];
|
data: TimeBucketsResponseDto[];
|
||||||
}>(`/timeline/buckets${QS.query(QS.explode({
|
}>(`/timeline/buckets${QS.query(QS.explode({
|
||||||
albumId,
|
albumId,
|
||||||
isArchived,
|
isArchived,
|
||||||
@ -3262,7 +3288,6 @@ export function getTimeBuckets({ albumId, isArchived, isFavorite, isTrashed, key
|
|||||||
key,
|
key,
|
||||||
order,
|
order,
|
||||||
personId,
|
personId,
|
||||||
size,
|
|
||||||
tagId,
|
tagId,
|
||||||
userId,
|
userId,
|
||||||
withPartners,
|
withPartners,
|
||||||
@ -3736,7 +3761,3 @@ export enum LogLevel {
|
|||||||
Error = "error",
|
Error = "error",
|
||||||
Fatal = "fatal"
|
Fatal = "fatal"
|
||||||
}
|
}
|
||||||
export enum TimeBucketSize {
|
|
||||||
Day = "DAY",
|
|
||||||
Month = "MONTH"
|
|
||||||
}
|
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
import { Controller, Get, Query } from '@nestjs/common';
|
import { Controller, Get, Query } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto';
|
import { TimeBucketAssetDto, TimeBucketDto } from 'src/dtos/time-bucket.dto';
|
||||||
import { Permission } from 'src/enum';
|
import { Permission } from 'src/enum';
|
||||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||||
import { TimelineService } from 'src/services/timeline.service';
|
import { TimelineService } from 'src/services/timeline.service';
|
||||||
@ -14,13 +13,13 @@ export class TimelineController {
|
|||||||
|
|
||||||
@Get('buckets')
|
@Get('buckets')
|
||||||
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
|
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
|
||||||
getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
|
getTimeBuckets(@Auth() auth: AuthDto, @Query() dto: TimeBucketDto) {
|
||||||
return this.service.getTimeBuckets(auth, dto);
|
return this.service.getTimeBuckets(auth, dto);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Get('bucket')
|
@Get('bucket')
|
||||||
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
|
@Authenticated({ permission: Permission.ASSET_READ, sharedLink: true })
|
||||||
getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto): Promise<AssetResponseDto[]> {
|
getTimeBucket(@Auth() auth: AuthDto, @Query() dto: TimeBucketAssetDto) {
|
||||||
return this.service.getTimeBucket(auth, dto) as Promise<AssetResponseDto[]>;
|
return this.service.getTimeBucket(auth, dto);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import {
|
|||||||
import { TagResponseDto, mapTag } from 'src/dtos/tag.dto';
|
import { TagResponseDto, mapTag } from 'src/dtos/tag.dto';
|
||||||
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
import { UserResponseDto, mapUser } from 'src/dtos/user.dto';
|
||||||
import { AssetStatus, AssetType } from 'src/enum';
|
import { AssetStatus, AssetType } from 'src/enum';
|
||||||
|
import { hexOrBufferToBase64 } from 'src/utils/bytes';
|
||||||
import { mimeTypes } from 'src/utils/mime-types';
|
import { mimeTypes } from 'src/utils/mime-types';
|
||||||
|
|
||||||
export class SanitizedAssetResponseDto {
|
export class SanitizedAssetResponseDto {
|
||||||
@ -140,15 +141,6 @@ const mapStack = (entity: { stack?: Stack | null }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// if an asset is jsonified in the DB before being returned, its buffer fields will be hex-encoded strings
|
|
||||||
export const hexOrBufferToBase64 = (encoded: string | Buffer) => {
|
|
||||||
if (typeof encoded === 'string') {
|
|
||||||
return Buffer.from(encoded.slice(2), 'hex').toString('base64');
|
|
||||||
}
|
|
||||||
|
|
||||||
return encoded.toString('base64');
|
|
||||||
};
|
|
||||||
|
|
||||||
export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): AssetResponseDto {
|
export function mapAsset(entity: MapAsset, options: AssetMapOptions = {}): AssetResponseDto {
|
||||||
const { stripMetadata = false, withStack = false } = options;
|
const { stripMetadata = false, withStack = false } = options;
|
||||||
|
|
||||||
|
@ -1,15 +1,11 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { IsEnum, IsNotEmpty, IsString } from 'class-validator';
|
|
||||||
|
import { IsEnum, IsInt, IsString, Min } from 'class-validator';
|
||||||
import { AssetOrder } from 'src/enum';
|
import { AssetOrder } from 'src/enum';
|
||||||
import { TimeBucketSize } from 'src/repositories/asset.repository';
|
import { TimeBucketAssets, TimelineStack } from 'src/services/timeline.service.types';
|
||||||
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
import { Optional, ValidateBoolean, ValidateUUID } from 'src/validation';
|
||||||
|
|
||||||
export class TimeBucketDto {
|
export class TimeBucketDto {
|
||||||
@IsNotEmpty()
|
|
||||||
@IsEnum(TimeBucketSize)
|
|
||||||
@ApiProperty({ enum: TimeBucketSize, enumName: 'TimeBucketSize' })
|
|
||||||
size!: TimeBucketSize;
|
|
||||||
|
|
||||||
@ValidateUUID({ optional: true })
|
@ValidateUUID({ optional: true })
|
||||||
userId?: string;
|
userId?: string;
|
||||||
|
|
||||||
@ -46,12 +42,132 @@ export class TimeBucketDto {
|
|||||||
export class TimeBucketAssetDto extends TimeBucketDto {
|
export class TimeBucketAssetDto extends TimeBucketDto {
|
||||||
@IsString()
|
@IsString()
|
||||||
timeBucket!: string;
|
timeBucket!: string;
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@Min(1)
|
||||||
|
@Optional()
|
||||||
|
page?: number;
|
||||||
|
|
||||||
|
@IsInt()
|
||||||
|
@Optional()
|
||||||
|
pageSize?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class TimeBucketResponseDto {
|
export class TimelineStackResponseDto implements TimelineStack {
|
||||||
|
@ApiProperty()
|
||||||
|
id!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
primaryAssetId!: string;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
assetCount!: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TimeBucketAssetResponseDto implements TimeBucketAssets {
|
||||||
|
@ApiProperty({ type: [String] })
|
||||||
|
id: string[] = [];
|
||||||
|
|
||||||
|
@ApiProperty({ type: [String] })
|
||||||
|
ownerId: string[] = [];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
ratio: number[] = [];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
isFavorite: number[] = [];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
isArchived: number[] = [];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
isTrashed: number[] = [];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
isImage: number[] = [];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
isVideo: number[] = [];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
thumbhash: (string | number)[] = [];
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
localDateTime: Date[] = [];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
duration: (string | number)[] = [];
|
||||||
|
|
||||||
|
@ApiProperty({ type: [TimelineStackResponseDto] })
|
||||||
|
stack: (TimelineStackResponseDto | number)[] = [];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
projectionType: (string | number)[] = [];
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
type: 'array',
|
||||||
|
items: {
|
||||||
|
oneOf: [
|
||||||
|
{
|
||||||
|
type: 'string',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'number',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
livePhotoVideoId: (string | number)[] = [];
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TimeBucketsResponseDto {
|
||||||
@ApiProperty({ type: 'string' })
|
@ApiProperty({ type: 'string' })
|
||||||
timeBucket!: string;
|
timeBucket!: string;
|
||||||
|
|
||||||
@ApiProperty({ type: 'integer' })
|
@ApiProperty({ type: 'integer' })
|
||||||
count!: number;
|
count!: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class TimeBucketResponseDto {
|
||||||
|
@ApiProperty({ type: TimeBucketAssetResponseDto })
|
||||||
|
bucketAssets!: TimeBucketAssetResponseDto;
|
||||||
|
|
||||||
|
@ApiProperty()
|
||||||
|
hasNextPage!: boolean;
|
||||||
|
}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
anyUuid,
|
anyUuid,
|
||||||
asUuid,
|
asUuid,
|
||||||
hasPeople,
|
hasPeople,
|
||||||
|
hasPeopleNoJoin,
|
||||||
removeUndefinedKeys,
|
removeUndefinedKeys,
|
||||||
searchAssetBuilder,
|
searchAssetBuilder,
|
||||||
truncatedDate,
|
truncatedDate,
|
||||||
@ -24,6 +25,7 @@ import {
|
|||||||
withOwner,
|
withOwner,
|
||||||
withSmartSearch,
|
withSmartSearch,
|
||||||
withTagId,
|
withTagId,
|
||||||
|
withTagIdNoWhere,
|
||||||
withTags,
|
withTags,
|
||||||
} from 'src/utils/database';
|
} from 'src/utils/database';
|
||||||
import { globToSqlPattern } from 'src/utils/misc';
|
import { globToSqlPattern } from 'src/utils/misc';
|
||||||
@ -80,7 +82,6 @@ export interface AssetBuilderOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface TimeBucketOptions extends AssetBuilderOptions {
|
export interface TimeBucketOptions extends AssetBuilderOptions {
|
||||||
size: TimeBucketSize;
|
|
||||||
order?: AssetOrder;
|
order?: AssetOrder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -637,7 +638,7 @@ export class AssetRepository {
|
|||||||
.with('assets', (qb) =>
|
.with('assets', (qb) =>
|
||||||
qb
|
qb
|
||||||
.selectFrom('assets')
|
.selectFrom('assets')
|
||||||
.select(truncatedDate<Date>(options.size).as('timeBucket'))
|
.select(truncatedDate<Date>(TimeBucketSize.MONTH).as('timeBucket'))
|
||||||
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
|
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
|
||||||
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||||
.where('assets.isVisible', '=', true)
|
.where('assets.isVisible', '=', true)
|
||||||
@ -679,18 +680,39 @@ export class AssetRepository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH, withStacked: true }] })
|
@GenerateSql({
|
||||||
async getTimeBucket(timeBucket: string, options: TimeBucketOptions) {
|
params: [DummyValue.TIME_BUCKET, { size: TimeBucketSize.MONTH, withStacked: true }, { skip: -1, take: 1000 }],
|
||||||
return this.db
|
})
|
||||||
|
async getTimeBucket(timeBucket: string, options: TimeBucketOptions, pagination: PaginationOptions) {
|
||||||
|
const paginate = pagination.skip! >= 1 && pagination.take >= 1;
|
||||||
|
const query = this.db
|
||||||
.selectFrom('assets')
|
.selectFrom('assets')
|
||||||
.selectAll('assets')
|
.select([
|
||||||
.$call(withExif)
|
'assets.id as id',
|
||||||
|
'assets.ownerId',
|
||||||
|
'assets.status',
|
||||||
|
'deletedAt',
|
||||||
|
'type',
|
||||||
|
'duration',
|
||||||
|
'isFavorite',
|
||||||
|
'isArchived',
|
||||||
|
'thumbhash',
|
||||||
|
'localDateTime',
|
||||||
|
'livePhotoVideoId',
|
||||||
|
])
|
||||||
|
.leftJoin('exif', 'assets.id', 'exif.assetId')
|
||||||
|
.select(['exif.exifImageHeight as height', 'exifImageWidth as width', 'exif.orientation', 'exif.projectionType'])
|
||||||
.$if(!!options.albumId, (qb) =>
|
.$if(!!options.albumId, (qb) =>
|
||||||
qb
|
qb
|
||||||
.innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
|
.innerJoin('albums_assets_assets', 'albums_assets_assets.assetsId', 'assets.id')
|
||||||
.where('albums_assets_assets.albumsId', '=', options.albumId!),
|
.where('albums_assets_assets.albumsId', '=', options.albumId!),
|
||||||
)
|
)
|
||||||
.$if(!!options.personId, (qb) => hasPeople(qb, [options.personId!]))
|
.$if(!!options.personId, (qb) =>
|
||||||
|
qb.innerJoin(
|
||||||
|
() => hasPeopleNoJoin([options.personId!]),
|
||||||
|
(join) => join.onRef('has_people.assetId', '=', 'assets.id'),
|
||||||
|
),
|
||||||
|
)
|
||||||
.$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
|
.$if(!!options.userIds, (qb) => qb.where('assets.ownerId', '=', anyUuid(options.userIds!)))
|
||||||
.$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
|
.$if(options.isArchived !== undefined, (qb) => qb.where('assets.isArchived', '=', options.isArchived!))
|
||||||
.$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
|
.$if(options.isFavorite !== undefined, (qb) => qb.where('assets.isFavorite', '=', options.isFavorite!))
|
||||||
@ -720,12 +742,15 @@ export class AssetRepository {
|
|||||||
qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
|
qb.where('assets.duplicateId', options.isDuplicate ? 'is not' : 'is', null),
|
||||||
)
|
)
|
||||||
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
|
.$if(!!options.isTrashed, (qb) => qb.where('assets.status', '!=', AssetStatus.DELETED))
|
||||||
.$if(!!options.tagId, (qb) => withTagId(qb, options.tagId!))
|
.$if(!!options.tagId, (qb) => qb.where((eb) => withTagIdNoWhere(options.tagId!, eb.ref('assets.id'))))
|
||||||
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
.where('assets.deletedAt', options.isTrashed ? 'is not' : 'is', null)
|
||||||
.where('assets.isVisible', '=', true)
|
.where('assets.isVisible', '=', true)
|
||||||
.where(truncatedDate(options.size), '=', timeBucket.replace(/^[+-]/, ''))
|
.where(truncatedDate(TimeBucketSize.MONTH), '=', timeBucket.replace(/^[+-]/, ''))
|
||||||
.orderBy('assets.localDateTime', options.order ?? 'desc')
|
.orderBy('assets.localDateTime', options.order ?? 'desc')
|
||||||
.execute();
|
.$if(paginate, (qb) => qb.offset(pagination.skip!))
|
||||||
|
.$if(paginate, (qb) => qb.limit(pagination.take + 1));
|
||||||
|
|
||||||
|
return await query.execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
@GenerateSql({ params: [DummyValue.UUID] })
|
@GenerateSql({ params: [DummyValue.UUID] })
|
||||||
|
@ -4,7 +4,7 @@ import { DateTime } from 'luxon';
|
|||||||
import { Writable } from 'node:stream';
|
import { Writable } from 'node:stream';
|
||||||
import { AUDIT_LOG_MAX_DURATION } from 'src/constants';
|
import { AUDIT_LOG_MAX_DURATION } from 'src/constants';
|
||||||
import { SessionSyncCheckpoints } from 'src/db';
|
import { SessionSyncCheckpoints } from 'src/db';
|
||||||
import { AssetResponseDto, hexOrBufferToBase64, mapAsset } from 'src/dtos/asset-response.dto';
|
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import {
|
import {
|
||||||
AssetDeltaSyncDto,
|
AssetDeltaSyncDto,
|
||||||
@ -18,6 +18,7 @@ import { DatabaseAction, EntityType, Permission, SyncEntityType, SyncRequestType
|
|||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { SyncAck } from 'src/types';
|
import { SyncAck } from 'src/types';
|
||||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||||
|
import { hexOrBufferToBase64 } from 'src/utils/bytes';
|
||||||
import { setIsEqual } from 'src/utils/set';
|
import { setIsEqual } from 'src/utils/set';
|
||||||
import { fromAck, serialize } from 'src/utils/sync';
|
import { fromAck, serialize } from 'src/utils/sync';
|
||||||
|
|
||||||
|
@ -1,9 +1,7 @@
|
|||||||
import { BadRequestException } from '@nestjs/common';
|
import { BadRequestException } from '@nestjs/common';
|
||||||
import { TimeBucketSize } from 'src/repositories/asset.repository';
|
|
||||||
import { TimelineService } from 'src/services/timeline.service';
|
import { TimelineService } from 'src/services/timeline.service';
|
||||||
import { assetStub } from 'test/fixtures/asset.stub';
|
import { assetStub } from 'test/fixtures/asset.stub';
|
||||||
import { authStub } from 'test/fixtures/auth.stub';
|
import { authStub } from 'test/fixtures/auth.stub';
|
||||||
import { factory } from 'test/small.factory';
|
|
||||||
import { newTestService, ServiceMocks } from 'test/utils';
|
import { newTestService, ServiceMocks } from 'test/utils';
|
||||||
|
|
||||||
describe(TimelineService.name, () => {
|
describe(TimelineService.name, () => {
|
||||||
@ -18,13 +16,10 @@ describe(TimelineService.name, () => {
|
|||||||
it("should return buckets if userId and albumId aren't set", async () => {
|
it("should return buckets if userId and albumId aren't set", async () => {
|
||||||
mocks.asset.getTimeBuckets.mockResolvedValue([{ timeBucket: 'bucket', count: 1 }]);
|
mocks.asset.getTimeBuckets.mockResolvedValue([{ timeBucket: 'bucket', count: 1 }]);
|
||||||
|
|
||||||
await expect(
|
await expect(sut.getTimeBuckets(authStub.admin, {})).resolves.toEqual(
|
||||||
sut.getTimeBuckets(authStub.admin, {
|
expect.arrayContaining([{ timeBucket: 'bucket', count: 1 }]),
|
||||||
size: TimeBucketSize.DAY,
|
);
|
||||||
}),
|
|
||||||
).resolves.toEqual(expect.arrayContaining([{ timeBucket: 'bucket', count: 1 }]));
|
|
||||||
expect(mocks.asset.getTimeBuckets).toHaveBeenCalledWith({
|
expect(mocks.asset.getTimeBuckets).toHaveBeenCalledWith({
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
userIds: [authStub.admin.user.id],
|
userIds: [authStub.admin.user.id],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -35,16 +30,24 @@ describe(TimelineService.name, () => {
|
|||||||
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
|
mocks.access.album.checkOwnerAccess.mockResolvedValue(new Set(['album-id']));
|
||||||
mocks.asset.getTimeBucket.mockResolvedValue([assetStub.image]);
|
mocks.asset.getTimeBucket.mockResolvedValue([assetStub.image]);
|
||||||
|
|
||||||
await expect(
|
await expect(sut.getTimeBucket(authStub.admin, { timeBucket: 'bucket', albumId: 'album-id' })).resolves.toEqual(
|
||||||
sut.getTimeBucket(authStub.admin, { size: TimeBucketSize.DAY, timeBucket: 'bucket', albumId: 'album-id' }),
|
expect.objectContaining({
|
||||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
bucketAssets: expect.objectContaining({ id: expect.arrayContaining(['asset-id']) }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
|
||||||
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-id']));
|
expect(mocks.access.album.checkOwnerAccess).toHaveBeenCalledWith(authStub.admin.user.id, new Set(['album-id']));
|
||||||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', {
|
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith(
|
||||||
size: TimeBucketSize.DAY,
|
'bucket',
|
||||||
timeBucket: 'bucket',
|
{
|
||||||
albumId: 'album-id',
|
timeBucket: 'bucket',
|
||||||
});
|
albumId: 'album-id',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skip: 1,
|
||||||
|
take: -1,
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the assets for a archive time bucket if user has archive.read', async () => {
|
it('should return the assets for a archive time bucket if user has archive.read', async () => {
|
||||||
@ -52,20 +55,26 @@ describe(TimelineService.name, () => {
|
|||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
isArchived: true,
|
isArchived: true,
|
||||||
userId: authStub.admin.user.id,
|
userId: authStub.admin.user.id,
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
bucketAssets: expect.objectContaining({ id: expect.arrayContaining(['asset-id']) }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith(
|
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith(
|
||||||
'bucket',
|
'bucket',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
isArchived: true,
|
isArchived: true,
|
||||||
userIds: [authStub.admin.user.id],
|
userIds: [authStub.admin.user.id],
|
||||||
}),
|
}),
|
||||||
|
{
|
||||||
|
skip: 1,
|
||||||
|
take: -1,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -75,20 +84,29 @@ describe(TimelineService.name, () => {
|
|||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
isArchived: false,
|
isArchived: false,
|
||||||
userId: authStub.admin.user.id,
|
userId: authStub.admin.user.id,
|
||||||
withPartners: true,
|
withPartners: true,
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
).resolves.toEqual(
|
||||||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', {
|
expect.objectContaining({
|
||||||
size: TimeBucketSize.DAY,
|
bucketAssets: expect.objectContaining({ id: expect.arrayContaining(['asset-id']) }),
|
||||||
timeBucket: 'bucket',
|
}),
|
||||||
isArchived: false,
|
);
|
||||||
withPartners: true,
|
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith(
|
||||||
userIds: [authStub.admin.user.id],
|
'bucket',
|
||||||
});
|
{
|
||||||
|
timeBucket: 'bucket',
|
||||||
|
isArchived: false,
|
||||||
|
withPartners: true,
|
||||||
|
userIds: [authStub.admin.user.id],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
skip: 1,
|
||||||
|
take: -1,
|
||||||
|
},
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should check permissions to read tag', async () => {
|
it('should check permissions to read tag', async () => {
|
||||||
@ -97,41 +115,27 @@ describe(TimelineService.name, () => {
|
|||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
userId: authStub.admin.user.id,
|
userId: authStub.admin.user.id,
|
||||||
tagId: 'tag-123',
|
tagId: 'tag-123',
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
).resolves.toEqual(
|
||||||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', {
|
expect.objectContaining({
|
||||||
size: TimeBucketSize.DAY,
|
bucketAssets: expect.objectContaining({ id: expect.arrayContaining(['asset-id']) }),
|
||||||
tagId: 'tag-123',
|
}),
|
||||||
timeBucket: 'bucket',
|
);
|
||||||
userIds: [authStub.admin.user.id],
|
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith(
|
||||||
});
|
'bucket',
|
||||||
});
|
{
|
||||||
|
tagId: 'tag-123',
|
||||||
it('should strip metadata if showExif is disabled', async () => {
|
timeBucket: 'bucket',
|
||||||
mocks.access.album.checkSharedLinkAccess.mockResolvedValue(new Set(['album-id']));
|
userIds: [authStub.admin.user.id],
|
||||||
mocks.asset.getTimeBucket.mockResolvedValue([assetStub.image]);
|
},
|
||||||
|
{
|
||||||
const auth = factory.auth({ sharedLink: { showExif: false } });
|
skip: 1,
|
||||||
|
take: -1,
|
||||||
const buckets = await sut.getTimeBucket(auth, {
|
},
|
||||||
size: TimeBucketSize.DAY,
|
);
|
||||||
timeBucket: 'bucket',
|
|
||||||
isArchived: true,
|
|
||||||
albumId: 'album-id',
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(buckets).toEqual([expect.objectContaining({ id: 'asset-id' })]);
|
|
||||||
expect(buckets[0]).not.toHaveProperty('exif');
|
|
||||||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith('bucket', {
|
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
|
||||||
isArchived: true,
|
|
||||||
albumId: 'album-id',
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return the assets for a library time bucket if user has library.read', async () => {
|
it('should return the assets for a library time bucket if user has library.read', async () => {
|
||||||
@ -139,25 +143,30 @@ describe(TimelineService.name, () => {
|
|||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
userId: authStub.admin.user.id,
|
userId: authStub.admin.user.id,
|
||||||
}),
|
}),
|
||||||
).resolves.toEqual(expect.arrayContaining([expect.objectContaining({ id: 'asset-id' })]));
|
).resolves.toEqual(
|
||||||
|
expect.objectContaining({
|
||||||
|
bucketAssets: expect.objectContaining({ id: expect.arrayContaining(['asset-id']) }),
|
||||||
|
}),
|
||||||
|
);
|
||||||
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith(
|
expect(mocks.asset.getTimeBucket).toHaveBeenCalledWith(
|
||||||
'bucket',
|
'bucket',
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
userIds: [authStub.admin.user.id],
|
userIds: [authStub.admin.user.id],
|
||||||
}),
|
}),
|
||||||
|
{
|
||||||
|
skip: 1,
|
||||||
|
take: -1,
|
||||||
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should throw an error if withParners is true and isArchived true or undefined', async () => {
|
it('should throw an error if withParners is true and isArchived true or undefined', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
isArchived: true,
|
isArchived: true,
|
||||||
withPartners: true,
|
withPartners: true,
|
||||||
@ -167,7 +176,6 @@ describe(TimelineService.name, () => {
|
|||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
isArchived: undefined,
|
isArchived: undefined,
|
||||||
withPartners: true,
|
withPartners: true,
|
||||||
@ -179,7 +187,6 @@ describe(TimelineService.name, () => {
|
|||||||
it('should throw an error if withParners is true and isFavorite is either true or false', async () => {
|
it('should throw an error if withParners is true and isFavorite is either true or false', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
withPartners: true,
|
withPartners: true,
|
||||||
@ -189,7 +196,6 @@ describe(TimelineService.name, () => {
|
|||||||
|
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
isFavorite: false,
|
isFavorite: false,
|
||||||
withPartners: true,
|
withPartners: true,
|
||||||
@ -201,7 +207,6 @@ describe(TimelineService.name, () => {
|
|||||||
it('should throw an error if withParners is true and isTrash is true', async () => {
|
it('should throw an error if withParners is true and isTrash is true', async () => {
|
||||||
await expect(
|
await expect(
|
||||||
sut.getTimeBucket(authStub.admin, {
|
sut.getTimeBucket(authStub.admin, {
|
||||||
size: TimeBucketSize.DAY,
|
|
||||||
timeBucket: 'bucket',
|
timeBucket: 'bucket',
|
||||||
isTrashed: true,
|
isTrashed: true,
|
||||||
withPartners: true,
|
withPartners: true,
|
||||||
|
@ -1,30 +1,105 @@
|
|||||||
import { BadRequestException, Injectable } from '@nestjs/common';
|
import { BadRequestException, Injectable } from '@nestjs/common';
|
||||||
import { AssetResponseDto, SanitizedAssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
import { round } from 'lodash';
|
||||||
|
import { Stack } from 'src/database';
|
||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { TimeBucketAssetDto, TimeBucketDto, TimeBucketResponseDto } from 'src/dtos/time-bucket.dto';
|
import {
|
||||||
import { Permission } from 'src/enum';
|
TimeBucketAssetDto,
|
||||||
|
TimeBucketDto,
|
||||||
|
TimeBucketResponseDto,
|
||||||
|
TimeBucketsResponseDto,
|
||||||
|
} from 'src/dtos/time-bucket.dto';
|
||||||
|
import { AssetType, Permission } from 'src/enum';
|
||||||
import { TimeBucketOptions } from 'src/repositories/asset.repository';
|
import { TimeBucketOptions } from 'src/repositories/asset.repository';
|
||||||
import { BaseService } from 'src/services/base.service';
|
import { BaseService } from 'src/services/base.service';
|
||||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
import { TimeBucketAssets } from 'src/services/timeline.service.types';
|
||||||
|
import { getMyPartnerIds, isFlipped } from 'src/utils/asset.util';
|
||||||
|
import { hexOrBufferToBase64 } from 'src/utils/bytes';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class TimelineService extends BaseService {
|
export class TimelineService extends BaseService {
|
||||||
async getTimeBuckets(auth: AuthDto, dto: TimeBucketDto): Promise<TimeBucketResponseDto[]> {
|
async getTimeBuckets(auth: AuthDto, dto: TimeBucketDto): Promise<TimeBucketsResponseDto[]> {
|
||||||
await this.timeBucketChecks(auth, dto);
|
await this.timeBucketChecks(auth, dto);
|
||||||
const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto);
|
const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto);
|
||||||
return this.assetRepository.getTimeBuckets(timeBucketOptions);
|
return this.assetRepository.getTimeBuckets(timeBucketOptions);
|
||||||
}
|
}
|
||||||
|
|
||||||
async getTimeBucket(
|
async getTimeBucket(auth: AuthDto, dto: TimeBucketAssetDto): Promise<TimeBucketResponseDto> {
|
||||||
auth: AuthDto,
|
|
||||||
dto: TimeBucketAssetDto,
|
|
||||||
): Promise<AssetResponseDto[] | SanitizedAssetResponseDto[]> {
|
|
||||||
await this.timeBucketChecks(auth, dto);
|
await this.timeBucketChecks(auth, dto);
|
||||||
const timeBucketOptions = await this.buildTimeBucketOptions(auth, dto);
|
const timeBucketOptions = await this.buildTimeBucketOptions(auth, { ...dto });
|
||||||
const assets = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions);
|
|
||||||
return !auth.sharedLink || auth.sharedLink?.showExif
|
const page = dto.page || 1;
|
||||||
? assets.map((asset) => mapAsset(asset, { withStack: true, auth }))
|
const size = dto.pageSize || -1;
|
||||||
: assets.map((asset) => mapAsset(asset, { stripMetadata: true, auth }));
|
if (dto.pageSize === 0) {
|
||||||
|
throw new BadRequestException('pageSize must not be 0');
|
||||||
|
}
|
||||||
|
const paginate = page >= 1 && size >= 1;
|
||||||
|
const items = await this.assetRepository.getTimeBucket(dto.timeBucket, timeBucketOptions, {
|
||||||
|
skip: page,
|
||||||
|
take: size,
|
||||||
|
});
|
||||||
|
|
||||||
|
const hasNextPage = paginate && items.length > size;
|
||||||
|
if (paginate) {
|
||||||
|
items.splice(size);
|
||||||
|
}
|
||||||
|
|
||||||
|
const bucketAssets: TimeBucketAssets = {
|
||||||
|
id: [],
|
||||||
|
ownerId: [],
|
||||||
|
ratio: [],
|
||||||
|
isFavorite: [],
|
||||||
|
isArchived: [],
|
||||||
|
isTrashed: [],
|
||||||
|
isVideo: [],
|
||||||
|
isImage: [],
|
||||||
|
thumbhash: [],
|
||||||
|
localDateTime: [],
|
||||||
|
stack: [],
|
||||||
|
duration: [],
|
||||||
|
projectionType: [],
|
||||||
|
livePhotoVideoId: [],
|
||||||
|
};
|
||||||
|
for (const item of items) {
|
||||||
|
let width = item.width!;
|
||||||
|
let height = item.height!;
|
||||||
|
if (isFlipped(item.orientation)) {
|
||||||
|
const w = item.width!;
|
||||||
|
const h = item.height!;
|
||||||
|
height = w;
|
||||||
|
width = h;
|
||||||
|
}
|
||||||
|
bucketAssets.id.push(item.id);
|
||||||
|
bucketAssets.ownerId.push(item.ownerId);
|
||||||
|
bucketAssets.ratio.push(round(width / height, 2));
|
||||||
|
bucketAssets.isArchived.push(item.isArchived ? 1 : 0);
|
||||||
|
bucketAssets.isFavorite.push(item.isFavorite ? 1 : 0);
|
||||||
|
bucketAssets.isTrashed.push(item.deletedAt === null ? 0 : 1);
|
||||||
|
bucketAssets.thumbhash.push(item.thumbhash ? hexOrBufferToBase64(item.thumbhash) : 0);
|
||||||
|
bucketAssets.localDateTime.push(item.localDateTime);
|
||||||
|
bucketAssets.stack.push(this.mapStack(item.stack) || 0);
|
||||||
|
bucketAssets.duration.push(item.duration || 0);
|
||||||
|
bucketAssets.projectionType.push(item.projectionType || 0);
|
||||||
|
bucketAssets.livePhotoVideoId.push(item.livePhotoVideoId || 0);
|
||||||
|
bucketAssets.isImage.push(item.type === AssetType.IMAGE ? 1 : 0);
|
||||||
|
bucketAssets.isVideo.push(item.type === AssetType.VIDEO ? 1 : 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
bucketAssets,
|
||||||
|
hasNextPage,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
mapStack(entity?: Stack | null) {
|
||||||
|
if (!entity) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
id: entity.id!,
|
||||||
|
primaryAssetId: entity.primaryAssetId!,
|
||||||
|
assetCount: entity.assetCount as number,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async buildTimeBucketOptions(auth: AuthDto, dto: TimeBucketDto): Promise<TimeBucketOptions> {
|
private async buildTimeBucketOptions(auth: AuthDto, dto: TimeBucketDto): Promise<TimeBucketOptions> {
|
||||||
|
22
server/src/services/timeline.service.types.ts
Normal file
22
server/src/services/timeline.service.types.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
export type TimelineStack = {
|
||||||
|
id: string;
|
||||||
|
primaryAssetId: string;
|
||||||
|
assetCount: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type TimeBucketAssets = {
|
||||||
|
id: string[];
|
||||||
|
ownerId: string[];
|
||||||
|
ratio: number[];
|
||||||
|
isFavorite: number[];
|
||||||
|
isArchived: number[];
|
||||||
|
isTrashed: number[];
|
||||||
|
isVideo: number[];
|
||||||
|
isImage: number[];
|
||||||
|
thumbhash: (string | number)[];
|
||||||
|
localDateTime: Date[];
|
||||||
|
stack: (TimelineStack | number)[];
|
||||||
|
duration: (string | number)[];
|
||||||
|
projectionType: (string | number)[];
|
||||||
|
livePhotoVideoId: (string | number)[];
|
||||||
|
};
|
@ -197,3 +197,16 @@ export const asRequest = (request: AuthRequest, file: Express.Multer.File) => {
|
|||||||
file: mapToUploadFile(file as ImmichFile),
|
file: mapToUploadFile(file as ImmichFile),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
function isRotated90CW(orientation: number) {
|
||||||
|
return orientation === 5 || orientation === 6 || orientation === 90;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isRotated270CW(orientation: number) {
|
||||||
|
return orientation === 7 || orientation === 8 || orientation === -90;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function isFlipped(orientation?: string | null) {
|
||||||
|
const value = Number(orientation);
|
||||||
|
return value && (isRotated270CW(value) || isRotated90CW(value));
|
||||||
|
}
|
||||||
|
@ -22,3 +22,12 @@ export function asHumanReadable(bytes: number, precision = 1): string {
|
|||||||
|
|
||||||
return `${remainder.toFixed(magnitude == 0 ? 0 : precision)} ${units[magnitude]}`;
|
return `${remainder.toFixed(magnitude == 0 ? 0 : precision)} ${units[magnitude]}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// if an asset is jsonified in the DB before being returned, its buffer fields will be hex-encoded strings
|
||||||
|
export const hexOrBufferToBase64 = (encoded: string | Buffer) => {
|
||||||
|
if (typeof encoded === 'string') {
|
||||||
|
return Buffer.from(encoded.slice(2), 'hex').toString('base64');
|
||||||
|
}
|
||||||
|
|
||||||
|
return encoded.toString('base64');
|
||||||
|
};
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
DeduplicateJoinsPlugin,
|
DeduplicateJoinsPlugin,
|
||||||
Expression,
|
Expression,
|
||||||
|
expressionBuilder,
|
||||||
ExpressionBuilder,
|
ExpressionBuilder,
|
||||||
ExpressionWrapper,
|
ExpressionWrapper,
|
||||||
Kysely,
|
Kysely,
|
||||||
@ -180,18 +181,19 @@ export function withFacesAndPeople(eb: ExpressionBuilder<DB, 'assets'>, withDele
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function hasPeople<O>(qb: SelectQueryBuilder<DB, 'assets', O>, personIds: string[]) {
|
export function hasPeople<O>(qb: SelectQueryBuilder<DB, 'assets', O>, personIds: string[]) {
|
||||||
return qb.innerJoin(
|
return qb.innerJoin(hasPeopleNoJoin(personIds), (join) => join.onRef('has_people.assetId', '=', 'assets.id'));
|
||||||
(eb) =>
|
}
|
||||||
eb
|
|
||||||
.selectFrom('asset_faces')
|
export function hasPeopleNoJoin(personIds: string[]) {
|
||||||
.select('assetId')
|
const eb = expressionBuilder<DB, never>();
|
||||||
.where('personId', '=', anyUuid(personIds!))
|
return eb
|
||||||
.where('deletedAt', 'is', null)
|
.selectFrom('asset_faces')
|
||||||
.groupBy('assetId')
|
.select('assetId')
|
||||||
.having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length)
|
.where('personId', '=', anyUuid(personIds!))
|
||||||
.as('has_people'),
|
.where('deletedAt', 'is', null)
|
||||||
(join) => join.onRef('has_people.assetId', '=', 'assets.id'),
|
.groupBy('assetId')
|
||||||
);
|
.having((eb) => eb.fn.count('personId').distinct(), '=', personIds.length)
|
||||||
|
.as('has_people');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasTags<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagIds: string[]) {
|
export function hasTags<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagIds: string[]) {
|
||||||
@ -236,16 +238,20 @@ export function truncatedDate<O>(size: TimeBucketSize) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function withTagId<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagId: string) {
|
export function withTagId<O>(qb: SelectQueryBuilder<DB, 'assets', O>, tagId: string) {
|
||||||
return qb.where((eb) =>
|
return qb.where((eb) => withTagIdNoWhere(tagId, eb.ref('assets.id')));
|
||||||
eb.exists(
|
}
|
||||||
eb
|
|
||||||
.selectFrom('tags_closure')
|
export function withTagIdNoWhere(tagId: string, assetId: Expression<string>) {
|
||||||
.innerJoin('tag_asset', 'tag_asset.tagsId', 'tags_closure.id_descendant')
|
const eb = expressionBuilder<DB, never>();
|
||||||
.whereRef('tag_asset.assetsId', '=', 'assets.id')
|
return eb.exists(
|
||||||
.where('tags_closure.id_ancestor', '=', tagId),
|
eb
|
||||||
),
|
.selectFrom('tags_closure')
|
||||||
|
.innerJoin('tag_asset', 'tag_asset.tagsId', 'tags_closure.id_descendant')
|
||||||
|
.whereRef('tag_asset.assetsId', '=', assetId)
|
||||||
|
.where('tags_closure.id_ancestor', '=', tagId),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const joinDeduplicationPlugin = new DeduplicateJoinsPlugin();
|
const joinDeduplicationPlugin = new DeduplicateJoinsPlugin();
|
||||||
/** TODO: This should only be used for search-related queries, not as a general purpose query builder */
|
/** TODO: This should only be used for search-related queries, not as a general purpose query builder */
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@ import { LoggingRepository } from 'src/repositories/logging.repository';
|
|||||||
import { bootstrapTelemetry } from 'src/repositories/telemetry.repository';
|
import { bootstrapTelemetry } from 'src/repositories/telemetry.repository';
|
||||||
import { ApiService } from 'src/services/api.service';
|
import { ApiService } from 'src/services/api.service';
|
||||||
import { isStartUpError, useSwagger } from 'src/utils/misc';
|
import { isStartUpError, useSwagger } from 'src/utils/misc';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
process.title = 'immich-api';
|
process.title = 'immich-api';
|
||||||
|
|
||||||
|
4
server/test/fixtures/asset.stub.ts
vendored
4
server/test/fixtures/asset.stub.ts
vendored
@ -257,6 +257,10 @@ export const assetStub = {
|
|||||||
duplicateId: null,
|
duplicateId: null,
|
||||||
isOffline: false,
|
isOffline: false,
|
||||||
stack: null,
|
stack: null,
|
||||||
|
orientation: '',
|
||||||
|
projectionType: null,
|
||||||
|
height: 3840,
|
||||||
|
width: 2160,
|
||||||
}),
|
}),
|
||||||
|
|
||||||
trashed: Object.freeze({
|
trashed: Object.freeze({
|
||||||
|
6
typescript-open-api/typescript-sdk/package-lock.json
generated
Normal file
6
typescript-open-api/typescript-sdk/package-lock.json
generated
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "typescript-sdk",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user