mirror of
https://github.com/immich-app/immich.git
synced 2025-03-11 02:23:09 -05:00
feat: better mobile sync
This commit is contained in:
parent
62e0658e5a
commit
e095c96fa6
35 changed files with 3127 additions and 16 deletions
83
e2e/src/api/specs/sync.e2e-spec.ts
Normal file
83
e2e/src/api/specs/sync.e2e-spec.ts
Normal file
|
@ -0,0 +1,83 @@
|
|||
import { LoginResponseDto, login, signUpAdmin } from '@immich/sdk';
|
||||
import { loginDto, signupDto, uuidDto } from 'src/fixtures';
|
||||
import { errorDto } from 'src/responses';
|
||||
import { app, utils } from 'src/utils';
|
||||
import request from 'supertest';
|
||||
import { beforeAll, describe, expect, it } from 'vitest';
|
||||
|
||||
describe('/sync', () => {
|
||||
let admin: LoginResponseDto;
|
||||
|
||||
beforeAll(async () => {
|
||||
await utils.resetDatabase();
|
||||
await signUpAdmin({ signUpDto: signupDto.admin });
|
||||
admin = await login({ loginCredentialDto: loginDto.admin });
|
||||
});
|
||||
|
||||
describe('GET /sync/acknowledge', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(app).post('/sync/acknowledge');
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
|
||||
it('should work', async () => {
|
||||
const { status } = await request(app)
|
||||
.post('/sync/acknowledge')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({});
|
||||
expect(status).toBe(204);
|
||||
});
|
||||
|
||||
it('should work with an album sync date', async () => {
|
||||
const { status } = await request(app)
|
||||
.post('/sync/acknowledge')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({
|
||||
album: {
|
||||
id: uuidDto.dummy,
|
||||
timestamp: '2024-10-23T21:01:07.732Z',
|
||||
},
|
||||
});
|
||||
expect(status).toBe(204);
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /sync/stream', () => {
|
||||
it('should require authentication', async () => {
|
||||
const { status, body } = await request(app).post('/sync/stream');
|
||||
expect(status).toBe(401);
|
||||
expect(body).toEqual(errorDto.unauthorized);
|
||||
});
|
||||
|
||||
it('should require at least type', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/sync/stream')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ types: [] });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(errorDto.badRequest());
|
||||
});
|
||||
|
||||
it('should require valid types', async () => {
|
||||
const { status, body } = await request(app)
|
||||
.post('/sync/stream')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ types: ['invalid'] });
|
||||
expect(status).toBe(400);
|
||||
expect(body).toEqual(
|
||||
errorDto.badRequest([expect.stringContaining('each value in types must be one of the following values')]),
|
||||
);
|
||||
});
|
||||
|
||||
it('should accept a valid type', async () => {
|
||||
const response = await request(app)
|
||||
.post('/sync/stream')
|
||||
.set('Authorization', `Bearer ${admin.accessToken}`)
|
||||
.send({ types: ['asset'] });
|
||||
expect(response.status).toBe(200);
|
||||
expect(response.get('Content-Type')).toBe('application/jsonlines+json; charset=utf-8');
|
||||
expect(response.body).toEqual('');
|
||||
});
|
||||
});
|
||||
});
|
|
@ -2,6 +2,7 @@ export const uuidDto = {
|
|||
invalid: 'invalid-uuid',
|
||||
// valid uuid v4
|
||||
notFound: '00000000-0000-4000-a000-000000000000',
|
||||
dummy: '00000000-0000-4000-a000-000000000000',
|
||||
};
|
||||
|
||||
const adminLoginDto = {
|
||||
|
|
10
mobile/openapi/README.md
generated
10
mobile/openapi/README.md
generated
|
@ -201,8 +201,10 @@ Class | Method | HTTP request | Description
|
|||
*StacksApi* | [**getStack**](doc//StacksApi.md#getstack) | **GET** /stacks/{id} |
|
||||
*StacksApi* | [**searchStacks**](doc//StacksApi.md#searchstacks) | **GET** /stacks |
|
||||
*StacksApi* | [**updateStack**](doc//StacksApi.md#updatestack) | **PUT** /stacks/{id} |
|
||||
*SyncApi* | [**ackSync**](doc//SyncApi.md#acksync) | **POST** /sync/acknowledge |
|
||||
*SyncApi* | [**getDeltaSync**](doc//SyncApi.md#getdeltasync) | **POST** /sync/delta-sync |
|
||||
*SyncApi* | [**getFullSyncForUser**](doc//SyncApi.md#getfullsyncforuser) | **POST** /sync/full-sync |
|
||||
*SyncApi* | [**getSyncStream**](doc//SyncApi.md#getsyncstream) | **POST** /sync/stream |
|
||||
*SystemConfigApi* | [**getConfig**](doc//SystemConfigApi.md#getconfig) | **GET** /system-config |
|
||||
*SystemConfigApi* | [**getConfigDefaults**](doc//SystemConfigApi.md#getconfigdefaults) | **GET** /system-config/defaults |
|
||||
*SystemConfigApi* | [**getStorageTemplateOptions**](doc//SystemConfigApi.md#getstoragetemplateoptions) | **GET** /system-config/storage-template-options |
|
||||
|
@ -259,6 +261,7 @@ Class | Method | HTTP request | Description
|
|||
- [ActivityStatisticsResponseDto](doc//ActivityStatisticsResponseDto.md)
|
||||
- [AddUsersDto](doc//AddUsersDto.md)
|
||||
- [AdminOnboardingUpdateDto](doc//AdminOnboardingUpdateDto.md)
|
||||
- [AlbumAssetResponseDto](doc//AlbumAssetResponseDto.md)
|
||||
- [AlbumResponseDto](doc//AlbumResponseDto.md)
|
||||
- [AlbumStatisticsResponseDto](doc//AlbumStatisticsResponseDto.md)
|
||||
- [AlbumUserAddDto](doc//AlbumUserAddDto.md)
|
||||
|
@ -413,6 +416,13 @@ Class | Method | HTTP request | Description
|
|||
- [StackCreateDto](doc//StackCreateDto.md)
|
||||
- [StackResponseDto](doc//StackResponseDto.md)
|
||||
- [StackUpdateDto](doc//StackUpdateDto.md)
|
||||
- [SyncAcknowledgeDto](doc//SyncAcknowledgeDto.md)
|
||||
- [SyncAction](doc//SyncAction.md)
|
||||
- [SyncCheckpointDto](doc//SyncCheckpointDto.md)
|
||||
- [SyncStreamDto](doc//SyncStreamDto.md)
|
||||
- [SyncStreamResponseDto](doc//SyncStreamResponseDto.md)
|
||||
- [SyncStreamResponseDtoData](doc//SyncStreamResponseDtoData.md)
|
||||
- [SyncType](doc//SyncType.md)
|
||||
- [SystemConfigDto](doc//SystemConfigDto.md)
|
||||
- [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md)
|
||||
- [SystemConfigFacesDto](doc//SystemConfigFacesDto.md)
|
||||
|
|
8
mobile/openapi/lib/api.dart
generated
8
mobile/openapi/lib/api.dart
generated
|
@ -73,6 +73,7 @@ part 'model/activity_response_dto.dart';
|
|||
part 'model/activity_statistics_response_dto.dart';
|
||||
part 'model/add_users_dto.dart';
|
||||
part 'model/admin_onboarding_update_dto.dart';
|
||||
part 'model/album_asset_response_dto.dart';
|
||||
part 'model/album_response_dto.dart';
|
||||
part 'model/album_statistics_response_dto.dart';
|
||||
part 'model/album_user_add_dto.dart';
|
||||
|
@ -227,6 +228,13 @@ part 'model/source_type.dart';
|
|||
part 'model/stack_create_dto.dart';
|
||||
part 'model/stack_response_dto.dart';
|
||||
part 'model/stack_update_dto.dart';
|
||||
part 'model/sync_acknowledge_dto.dart';
|
||||
part 'model/sync_action.dart';
|
||||
part 'model/sync_checkpoint_dto.dart';
|
||||
part 'model/sync_stream_dto.dart';
|
||||
part 'model/sync_stream_response_dto.dart';
|
||||
part 'model/sync_stream_response_dto_data.dart';
|
||||
part 'model/sync_type.dart';
|
||||
part 'model/system_config_dto.dart';
|
||||
part 'model/system_config_f_fmpeg_dto.dart';
|
||||
part 'model/system_config_faces_dto.dart';
|
||||
|
|
89
mobile/openapi/lib/api/sync_api.dart
generated
89
mobile/openapi/lib/api/sync_api.dart
generated
|
@ -16,6 +16,45 @@ class SyncApi {
|
|||
|
||||
final ApiClient apiClient;
|
||||
|
||||
/// Performs an HTTP 'POST /sync/acknowledge' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SyncAcknowledgeDto] syncAcknowledgeDto (required):
|
||||
Future<Response> ackSyncWithHttpInfo(SyncAcknowledgeDto syncAcknowledgeDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/sync/acknowledge';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = syncAcknowledgeDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SyncAcknowledgeDto] syncAcknowledgeDto (required):
|
||||
Future<void> ackSync(SyncAcknowledgeDto syncAcknowledgeDto,) async {
|
||||
final response = await ackSyncWithHttpInfo(syncAcknowledgeDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /sync/delta-sync' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
|
@ -112,4 +151,54 @@ class SyncApi {
|
|||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Performs an HTTP 'POST /sync/stream' operation and returns the [Response].
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SyncStreamDto] syncStreamDto (required):
|
||||
Future<Response> getSyncStreamWithHttpInfo(SyncStreamDto syncStreamDto,) async {
|
||||
// ignore: prefer_const_declarations
|
||||
final path = r'/sync/stream';
|
||||
|
||||
// ignore: prefer_final_locals
|
||||
Object? postBody = syncStreamDto;
|
||||
|
||||
final queryParams = <QueryParam>[];
|
||||
final headerParams = <String, String>{};
|
||||
final formParams = <String, String>{};
|
||||
|
||||
const contentTypes = <String>['application/json'];
|
||||
|
||||
|
||||
return apiClient.invokeAPI(
|
||||
path,
|
||||
'POST',
|
||||
queryParams,
|
||||
postBody,
|
||||
headerParams,
|
||||
formParams,
|
||||
contentTypes.isEmpty ? null : contentTypes.first,
|
||||
);
|
||||
}
|
||||
|
||||
/// Parameters:
|
||||
///
|
||||
/// * [SyncStreamDto] syncStreamDto (required):
|
||||
Future<List<SyncStreamResponseDto>?> getSyncStream(SyncStreamDto syncStreamDto,) async {
|
||||
final response = await getSyncStreamWithHttpInfo(syncStreamDto,);
|
||||
if (response.statusCode >= HttpStatus.badRequest) {
|
||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||
}
|
||||
// When a remote server returns no body with a status of 204, we shall not decode it.
|
||||
// At the time of writing this, `dart:convert` will throw an "Unexpected end of input"
|
||||
// FormatException when trying to decode an empty string.
|
||||
if (response.body.isNotEmpty && response.statusCode != HttpStatus.noContent) {
|
||||
final responseBody = await _decodeBodyBytes(response);
|
||||
return (await apiClient.deserializeAsync(responseBody, 'List<SyncStreamResponseDto>') as List)
|
||||
.cast<SyncStreamResponseDto>()
|
||||
.toList(growable: false);
|
||||
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
16
mobile/openapi/lib/api_client.dart
generated
16
mobile/openapi/lib/api_client.dart
generated
|
@ -200,6 +200,8 @@ class ApiClient {
|
|||
return AddUsersDto.fromJson(value);
|
||||
case 'AdminOnboardingUpdateDto':
|
||||
return AdminOnboardingUpdateDto.fromJson(value);
|
||||
case 'AlbumAssetResponseDto':
|
||||
return AlbumAssetResponseDto.fromJson(value);
|
||||
case 'AlbumResponseDto':
|
||||
return AlbumResponseDto.fromJson(value);
|
||||
case 'AlbumStatisticsResponseDto':
|
||||
|
@ -508,6 +510,20 @@ class ApiClient {
|
|||
return StackResponseDto.fromJson(value);
|
||||
case 'StackUpdateDto':
|
||||
return StackUpdateDto.fromJson(value);
|
||||
case 'SyncAcknowledgeDto':
|
||||
return SyncAcknowledgeDto.fromJson(value);
|
||||
case 'SyncAction':
|
||||
return SyncActionTypeTransformer().decode(value);
|
||||
case 'SyncCheckpointDto':
|
||||
return SyncCheckpointDto.fromJson(value);
|
||||
case 'SyncStreamDto':
|
||||
return SyncStreamDto.fromJson(value);
|
||||
case 'SyncStreamResponseDto':
|
||||
return SyncStreamResponseDto.fromJson(value);
|
||||
case 'SyncStreamResponseDtoData':
|
||||
return SyncStreamResponseDtoData.fromJson(value);
|
||||
case 'SyncType':
|
||||
return SyncTypeTypeTransformer().decode(value);
|
||||
case 'SystemConfigDto':
|
||||
return SystemConfigDto.fromJson(value);
|
||||
case 'SystemConfigFFmpegDto':
|
||||
|
|
6
mobile/openapi/lib/api_helper.dart
generated
6
mobile/openapi/lib/api_helper.dart
generated
|
@ -130,6 +130,12 @@ String parameterToString(dynamic value) {
|
|||
if (value is SourceType) {
|
||||
return SourceTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is SyncAction) {
|
||||
return SyncActionTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is SyncType) {
|
||||
return SyncTypeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
if (value is TimeBucketSize) {
|
||||
return TimeBucketSizeTypeTransformer().encode(value).toString();
|
||||
}
|
||||
|
|
107
mobile/openapi/lib/model/album_asset_response_dto.dart
generated
Normal file
107
mobile/openapi/lib/model/album_asset_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 AlbumAssetResponseDto {
|
||||
/// Returns a new [AlbumAssetResponseDto] instance.
|
||||
AlbumAssetResponseDto({
|
||||
required this.albumId,
|
||||
required this.assetId,
|
||||
});
|
||||
|
||||
String albumId;
|
||||
|
||||
String assetId;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is AlbumAssetResponseDto &&
|
||||
other.albumId == albumId &&
|
||||
other.assetId == assetId;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(albumId.hashCode) +
|
||||
(assetId.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'AlbumAssetResponseDto[albumId=$albumId, assetId=$assetId]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'albumId'] = this.albumId;
|
||||
json[r'assetId'] = this.assetId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [AlbumAssetResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static AlbumAssetResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "AlbumAssetResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return AlbumAssetResponseDto(
|
||||
albumId: mapValueOfType<String>(json, r'albumId')!,
|
||||
assetId: mapValueOfType<String>(json, r'assetId')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<AlbumAssetResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <AlbumAssetResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = AlbumAssetResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, AlbumAssetResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, AlbumAssetResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = AlbumAssetResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of AlbumAssetResponseDto-objects as value to a dart map
|
||||
static Map<String, List<AlbumAssetResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<AlbumAssetResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = AlbumAssetResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'albumId',
|
||||
'assetId',
|
||||
};
|
||||
}
|
||||
|
329
mobile/openapi/lib/model/sync_acknowledge_dto.dart
generated
Normal file
329
mobile/openapi/lib/model/sync_acknowledge_dto.dart
generated
Normal file
|
@ -0,0 +1,329 @@
|
|||
//
|
||||
// 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 SyncAcknowledgeDto {
|
||||
/// Returns a new [SyncAcknowledgeDto] instance.
|
||||
SyncAcknowledgeDto({
|
||||
this.activity,
|
||||
this.album,
|
||||
this.albumAsset,
|
||||
this.albumUser,
|
||||
this.asset,
|
||||
this.assetAlbum,
|
||||
this.assetPartner,
|
||||
this.memory,
|
||||
this.partner,
|
||||
this.person,
|
||||
this.sharedLink,
|
||||
this.stack,
|
||||
this.tag,
|
||||
this.user,
|
||||
});
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? activity;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? album;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? albumAsset;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? albumUser;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? asset;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? assetAlbum;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? assetPartner;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? memory;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? partner;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? person;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? sharedLink;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? stack;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? tag;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SyncCheckpointDto? user;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncAcknowledgeDto &&
|
||||
other.activity == activity &&
|
||||
other.album == album &&
|
||||
other.albumAsset == albumAsset &&
|
||||
other.albumUser == albumUser &&
|
||||
other.asset == asset &&
|
||||
other.assetAlbum == assetAlbum &&
|
||||
other.assetPartner == assetPartner &&
|
||||
other.memory == memory &&
|
||||
other.partner == partner &&
|
||||
other.person == person &&
|
||||
other.sharedLink == sharedLink &&
|
||||
other.stack == stack &&
|
||||
other.tag == tag &&
|
||||
other.user == user;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(activity == null ? 0 : activity!.hashCode) +
|
||||
(album == null ? 0 : album!.hashCode) +
|
||||
(albumAsset == null ? 0 : albumAsset!.hashCode) +
|
||||
(albumUser == null ? 0 : albumUser!.hashCode) +
|
||||
(asset == null ? 0 : asset!.hashCode) +
|
||||
(assetAlbum == null ? 0 : assetAlbum!.hashCode) +
|
||||
(assetPartner == null ? 0 : assetPartner!.hashCode) +
|
||||
(memory == null ? 0 : memory!.hashCode) +
|
||||
(partner == null ? 0 : partner!.hashCode) +
|
||||
(person == null ? 0 : person!.hashCode) +
|
||||
(sharedLink == null ? 0 : sharedLink!.hashCode) +
|
||||
(stack == null ? 0 : stack!.hashCode) +
|
||||
(tag == null ? 0 : tag!.hashCode) +
|
||||
(user == null ? 0 : user!.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncAcknowledgeDto[activity=$activity, album=$album, albumAsset=$albumAsset, albumUser=$albumUser, asset=$asset, assetAlbum=$assetAlbum, assetPartner=$assetPartner, memory=$memory, partner=$partner, person=$person, sharedLink=$sharedLink, stack=$stack, tag=$tag, user=$user]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
if (this.activity != null) {
|
||||
json[r'activity'] = this.activity;
|
||||
} else {
|
||||
// json[r'activity'] = null;
|
||||
}
|
||||
if (this.album != null) {
|
||||
json[r'album'] = this.album;
|
||||
} else {
|
||||
// json[r'album'] = null;
|
||||
}
|
||||
if (this.albumAsset != null) {
|
||||
json[r'albumAsset'] = this.albumAsset;
|
||||
} else {
|
||||
// json[r'albumAsset'] = null;
|
||||
}
|
||||
if (this.albumUser != null) {
|
||||
json[r'albumUser'] = this.albumUser;
|
||||
} else {
|
||||
// json[r'albumUser'] = null;
|
||||
}
|
||||
if (this.asset != null) {
|
||||
json[r'asset'] = this.asset;
|
||||
} else {
|
||||
// json[r'asset'] = null;
|
||||
}
|
||||
if (this.assetAlbum != null) {
|
||||
json[r'assetAlbum'] = this.assetAlbum;
|
||||
} else {
|
||||
// json[r'assetAlbum'] = null;
|
||||
}
|
||||
if (this.assetPartner != null) {
|
||||
json[r'assetPartner'] = this.assetPartner;
|
||||
} else {
|
||||
// json[r'assetPartner'] = null;
|
||||
}
|
||||
if (this.memory != null) {
|
||||
json[r'memory'] = this.memory;
|
||||
} else {
|
||||
// json[r'memory'] = null;
|
||||
}
|
||||
if (this.partner != null) {
|
||||
json[r'partner'] = this.partner;
|
||||
} else {
|
||||
// json[r'partner'] = null;
|
||||
}
|
||||
if (this.person != null) {
|
||||
json[r'person'] = this.person;
|
||||
} else {
|
||||
// json[r'person'] = null;
|
||||
}
|
||||
if (this.sharedLink != null) {
|
||||
json[r'sharedLink'] = this.sharedLink;
|
||||
} else {
|
||||
// json[r'sharedLink'] = null;
|
||||
}
|
||||
if (this.stack != null) {
|
||||
json[r'stack'] = this.stack;
|
||||
} else {
|
||||
// json[r'stack'] = null;
|
||||
}
|
||||
if (this.tag != null) {
|
||||
json[r'tag'] = this.tag;
|
||||
} else {
|
||||
// json[r'tag'] = null;
|
||||
}
|
||||
if (this.user != null) {
|
||||
json[r'user'] = this.user;
|
||||
} else {
|
||||
// json[r'user'] = null;
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SyncAcknowledgeDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SyncAcknowledgeDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SyncAcknowledgeDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SyncAcknowledgeDto(
|
||||
activity: SyncCheckpointDto.fromJson(json[r'activity']),
|
||||
album: SyncCheckpointDto.fromJson(json[r'album']),
|
||||
albumAsset: SyncCheckpointDto.fromJson(json[r'albumAsset']),
|
||||
albumUser: SyncCheckpointDto.fromJson(json[r'albumUser']),
|
||||
asset: SyncCheckpointDto.fromJson(json[r'asset']),
|
||||
assetAlbum: SyncCheckpointDto.fromJson(json[r'assetAlbum']),
|
||||
assetPartner: SyncCheckpointDto.fromJson(json[r'assetPartner']),
|
||||
memory: SyncCheckpointDto.fromJson(json[r'memory']),
|
||||
partner: SyncCheckpointDto.fromJson(json[r'partner']),
|
||||
person: SyncCheckpointDto.fromJson(json[r'person']),
|
||||
sharedLink: SyncCheckpointDto.fromJson(json[r'sharedLink']),
|
||||
stack: SyncCheckpointDto.fromJson(json[r'stack']),
|
||||
tag: SyncCheckpointDto.fromJson(json[r'tag']),
|
||||
user: SyncCheckpointDto.fromJson(json[r'user']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SyncAcknowledgeDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncAcknowledgeDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncAcknowledgeDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SyncAcknowledgeDto> mapFromJson(dynamic json) {
|
||||
final map = <String, SyncAcknowledgeDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SyncAcknowledgeDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SyncAcknowledgeDto-objects as value to a dart map
|
||||
static Map<String, List<SyncAcknowledgeDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SyncAcknowledgeDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SyncAcknowledgeDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
};
|
||||
}
|
||||
|
85
mobile/openapi/lib/model/sync_action.dart
generated
Normal file
85
mobile/openapi/lib/model/sync_action.dart
generated
Normal file
|
@ -0,0 +1,85 @@
|
|||
//
|
||||
// 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 SyncAction {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const SyncAction._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const upsert = SyncAction._(r'upsert');
|
||||
static const delete = SyncAction._(r'delete');
|
||||
|
||||
/// List of all possible values in this [enum][SyncAction].
|
||||
static const values = <SyncAction>[
|
||||
upsert,
|
||||
delete,
|
||||
];
|
||||
|
||||
static SyncAction? fromJson(dynamic value) => SyncActionTypeTransformer().decode(value);
|
||||
|
||||
static List<SyncAction> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncAction>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncAction.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [SyncAction] to String,
|
||||
/// and [decode] dynamic data back to [SyncAction].
|
||||
class SyncActionTypeTransformer {
|
||||
factory SyncActionTypeTransformer() => _instance ??= const SyncActionTypeTransformer._();
|
||||
|
||||
const SyncActionTypeTransformer._();
|
||||
|
||||
String encode(SyncAction data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a SyncAction.
|
||||
///
|
||||
/// 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.
|
||||
SyncAction? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'upsert': return SyncAction.upsert;
|
||||
case r'delete': return SyncAction.delete;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [SyncActionTypeTransformer] instance.
|
||||
static SyncActionTypeTransformer? _instance;
|
||||
}
|
||||
|
107
mobile/openapi/lib/model/sync_checkpoint_dto.dart
generated
Normal file
107
mobile/openapi/lib/model/sync_checkpoint_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 SyncCheckpointDto {
|
||||
/// Returns a new [SyncCheckpointDto] instance.
|
||||
SyncCheckpointDto({
|
||||
required this.id,
|
||||
required this.timestamp,
|
||||
});
|
||||
|
||||
String id;
|
||||
|
||||
String timestamp;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncCheckpointDto &&
|
||||
other.id == id &&
|
||||
other.timestamp == timestamp;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(id.hashCode) +
|
||||
(timestamp.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncCheckpointDto[id=$id, timestamp=$timestamp]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'id'] = this.id;
|
||||
json[r'timestamp'] = this.timestamp;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SyncCheckpointDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SyncCheckpointDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SyncCheckpointDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SyncCheckpointDto(
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
timestamp: mapValueOfType<String>(json, r'timestamp')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SyncCheckpointDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncCheckpointDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncCheckpointDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SyncCheckpointDto> mapFromJson(dynamic json) {
|
||||
final map = <String, SyncCheckpointDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SyncCheckpointDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SyncCheckpointDto-objects as value to a dart map
|
||||
static Map<String, List<SyncCheckpointDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SyncCheckpointDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SyncCheckpointDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'id',
|
||||
'timestamp',
|
||||
};
|
||||
}
|
||||
|
209
mobile/openapi/lib/model/sync_stream_dto.dart
generated
Normal file
209
mobile/openapi/lib/model/sync_stream_dto.dart
generated
Normal file
|
@ -0,0 +1,209 @@
|
|||
//
|
||||
// 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 SyncStreamDto {
|
||||
/// Returns a new [SyncStreamDto] instance.
|
||||
SyncStreamDto({
|
||||
this.types = const [],
|
||||
});
|
||||
|
||||
List<SyncStreamDtoTypesEnum> types;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncStreamDto &&
|
||||
_deepEquality.equals(other.types, types);
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(types.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncStreamDto[types=$types]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'types'] = this.types;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SyncStreamDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SyncStreamDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SyncStreamDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SyncStreamDto(
|
||||
types: SyncStreamDtoTypesEnum.listFromJson(json[r'types']),
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SyncStreamDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncStreamDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncStreamDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SyncStreamDto> mapFromJson(dynamic json) {
|
||||
final map = <String, SyncStreamDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SyncStreamDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SyncStreamDto-objects as value to a dart map
|
||||
static Map<String, List<SyncStreamDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SyncStreamDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SyncStreamDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'types',
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
class SyncStreamDtoTypesEnum {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const SyncStreamDtoTypesEnum._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const asset = SyncStreamDtoTypesEnum._(r'asset');
|
||||
static const assetPeriodPartner = SyncStreamDtoTypesEnum._(r'asset.partner');
|
||||
static const assetAlbum = SyncStreamDtoTypesEnum._(r'assetAlbum');
|
||||
static const album = SyncStreamDtoTypesEnum._(r'album');
|
||||
static const albumAsset = SyncStreamDtoTypesEnum._(r'albumAsset');
|
||||
static const albumUser = SyncStreamDtoTypesEnum._(r'albumUser');
|
||||
static const activity = SyncStreamDtoTypesEnum._(r'activity');
|
||||
static const memory = SyncStreamDtoTypesEnum._(r'memory');
|
||||
static const partner = SyncStreamDtoTypesEnum._(r'partner');
|
||||
static const person = SyncStreamDtoTypesEnum._(r'person');
|
||||
static const sharedLink = SyncStreamDtoTypesEnum._(r'sharedLink');
|
||||
static const stack = SyncStreamDtoTypesEnum._(r'stack');
|
||||
static const tag = SyncStreamDtoTypesEnum._(r'tag');
|
||||
static const user = SyncStreamDtoTypesEnum._(r'user');
|
||||
|
||||
/// List of all possible values in this [enum][SyncStreamDtoTypesEnum].
|
||||
static const values = <SyncStreamDtoTypesEnum>[
|
||||
asset,
|
||||
assetPeriodPartner,
|
||||
assetAlbum,
|
||||
album,
|
||||
albumAsset,
|
||||
albumUser,
|
||||
activity,
|
||||
memory,
|
||||
partner,
|
||||
person,
|
||||
sharedLink,
|
||||
stack,
|
||||
tag,
|
||||
user,
|
||||
];
|
||||
|
||||
static SyncStreamDtoTypesEnum? fromJson(dynamic value) => SyncStreamDtoTypesEnumTypeTransformer().decode(value);
|
||||
|
||||
static List<SyncStreamDtoTypesEnum> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncStreamDtoTypesEnum>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncStreamDtoTypesEnum.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [SyncStreamDtoTypesEnum] to String,
|
||||
/// and [decode] dynamic data back to [SyncStreamDtoTypesEnum].
|
||||
class SyncStreamDtoTypesEnumTypeTransformer {
|
||||
factory SyncStreamDtoTypesEnumTypeTransformer() => _instance ??= const SyncStreamDtoTypesEnumTypeTransformer._();
|
||||
|
||||
const SyncStreamDtoTypesEnumTypeTransformer._();
|
||||
|
||||
String encode(SyncStreamDtoTypesEnum data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a SyncStreamDtoTypesEnum.
|
||||
///
|
||||
/// 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.
|
||||
SyncStreamDtoTypesEnum? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'asset': return SyncStreamDtoTypesEnum.asset;
|
||||
case r'asset.partner': return SyncStreamDtoTypesEnum.assetPeriodPartner;
|
||||
case r'assetAlbum': return SyncStreamDtoTypesEnum.assetAlbum;
|
||||
case r'album': return SyncStreamDtoTypesEnum.album;
|
||||
case r'albumAsset': return SyncStreamDtoTypesEnum.albumAsset;
|
||||
case r'albumUser': return SyncStreamDtoTypesEnum.albumUser;
|
||||
case r'activity': return SyncStreamDtoTypesEnum.activity;
|
||||
case r'memory': return SyncStreamDtoTypesEnum.memory;
|
||||
case r'partner': return SyncStreamDtoTypesEnum.partner;
|
||||
case r'person': return SyncStreamDtoTypesEnum.person;
|
||||
case r'sharedLink': return SyncStreamDtoTypesEnum.sharedLink;
|
||||
case r'stack': return SyncStreamDtoTypesEnum.stack;
|
||||
case r'tag': return SyncStreamDtoTypesEnum.tag;
|
||||
case r'user': return SyncStreamDtoTypesEnum.user;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [SyncStreamDtoTypesEnumTypeTransformer] instance.
|
||||
static SyncStreamDtoTypesEnumTypeTransformer? _instance;
|
||||
}
|
||||
|
||||
|
115
mobile/openapi/lib/model/sync_stream_response_dto.dart
generated
Normal file
115
mobile/openapi/lib/model/sync_stream_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 SyncStreamResponseDto {
|
||||
/// Returns a new [SyncStreamResponseDto] instance.
|
||||
SyncStreamResponseDto({
|
||||
required this.action,
|
||||
required this.data,
|
||||
required this.type,
|
||||
});
|
||||
|
||||
SyncAction action;
|
||||
|
||||
SyncStreamResponseDtoData data;
|
||||
|
||||
SyncType type;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncStreamResponseDto &&
|
||||
other.action == action &&
|
||||
other.data == data &&
|
||||
other.type == type;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(action.hashCode) +
|
||||
(data.hashCode) +
|
||||
(type.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncStreamResponseDto[action=$action, data=$data, type=$type]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'action'] = this.action;
|
||||
json[r'data'] = this.data;
|
||||
json[r'type'] = this.type;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SyncStreamResponseDto] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SyncStreamResponseDto? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SyncStreamResponseDto");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SyncStreamResponseDto(
|
||||
action: SyncAction.fromJson(json[r'action'])!,
|
||||
data: SyncStreamResponseDtoData.fromJson(json[r'data'])!,
|
||||
type: SyncType.fromJson(json[r'type'])!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SyncStreamResponseDto> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncStreamResponseDto>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncStreamResponseDto.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SyncStreamResponseDto> mapFromJson(dynamic json) {
|
||||
final map = <String, SyncStreamResponseDto>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SyncStreamResponseDto.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SyncStreamResponseDto-objects as value to a dart map
|
||||
static Map<String, List<SyncStreamResponseDto>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SyncStreamResponseDto>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SyncStreamResponseDto.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'action',
|
||||
'data',
|
||||
'type',
|
||||
};
|
||||
}
|
||||
|
830
mobile/openapi/lib/model/sync_stream_response_dto_data.dart
generated
Normal file
830
mobile/openapi/lib/model/sync_stream_response_dto_data.dart
generated
Normal file
|
@ -0,0 +1,830 @@
|
|||
//
|
||||
// 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 SyncStreamResponseDtoData {
|
||||
/// Returns a new [SyncStreamResponseDtoData] instance.
|
||||
SyncStreamResponseDtoData({
|
||||
required this.checksum,
|
||||
required this.deviceAssetId,
|
||||
required this.deviceId,
|
||||
this.duplicateId,
|
||||
required this.duration,
|
||||
this.exifInfo,
|
||||
required this.fileCreatedAt,
|
||||
required this.fileModifiedAt,
|
||||
required this.hasMetadata,
|
||||
required this.id,
|
||||
required this.isArchived,
|
||||
required this.isFavorite,
|
||||
required this.isOffline,
|
||||
required this.isTrashed,
|
||||
this.libraryId,
|
||||
this.livePhotoVideoId,
|
||||
required this.localDateTime,
|
||||
required this.originalFileName,
|
||||
this.originalMimeType,
|
||||
required this.originalPath,
|
||||
required this.owner,
|
||||
required this.ownerId,
|
||||
this.people = const [],
|
||||
this.resized,
|
||||
this.smartInfo,
|
||||
this.stack,
|
||||
this.tags = const [],
|
||||
required this.thumbhash,
|
||||
required this.type,
|
||||
this.unassignedFaces = const [],
|
||||
required this.updatedAt,
|
||||
required this.albumName,
|
||||
required this.albumThumbnailAssetId,
|
||||
this.albumUsers = const [],
|
||||
required this.assetCount,
|
||||
this.assets = const [],
|
||||
required this.createdAt,
|
||||
required this.description,
|
||||
this.endDate,
|
||||
required this.hasSharedLink,
|
||||
required this.isActivityEnabled,
|
||||
this.lastModifiedAssetTimestamp,
|
||||
this.order,
|
||||
required this.shared,
|
||||
this.startDate,
|
||||
required this.albumId,
|
||||
required this.assetId,
|
||||
this.comment,
|
||||
required this.user,
|
||||
required this.data,
|
||||
this.deletedAt,
|
||||
required this.isSaved,
|
||||
required this.memoryAt,
|
||||
this.seenAt,
|
||||
required this.avatarColor,
|
||||
required this.email,
|
||||
this.inTimeline,
|
||||
required this.name,
|
||||
required this.profileChangedAt,
|
||||
required this.profileImagePath,
|
||||
required this.birthDate,
|
||||
required this.isHidden,
|
||||
required this.thumbnailPath,
|
||||
this.album,
|
||||
required this.allowDownload,
|
||||
required this.allowUpload,
|
||||
required this.expiresAt,
|
||||
required this.key,
|
||||
required this.password,
|
||||
required this.showMetadata,
|
||||
this.token,
|
||||
required this.userId,
|
||||
required this.primaryAssetId,
|
||||
});
|
||||
|
||||
/// base64 encoded sha1 hash
|
||||
String checksum;
|
||||
|
||||
String deviceAssetId;
|
||||
|
||||
String deviceId;
|
||||
|
||||
String? duplicateId;
|
||||
|
||||
String duration;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
ExifResponseDto? exifInfo;
|
||||
|
||||
DateTime fileCreatedAt;
|
||||
|
||||
DateTime fileModifiedAt;
|
||||
|
||||
bool hasMetadata;
|
||||
|
||||
String id;
|
||||
|
||||
bool isArchived;
|
||||
|
||||
bool isFavorite;
|
||||
|
||||
bool isOffline;
|
||||
|
||||
bool isTrashed;
|
||||
|
||||
/// This property was deprecated in v1.106.0
|
||||
String? libraryId;
|
||||
|
||||
String? livePhotoVideoId;
|
||||
|
||||
DateTime localDateTime;
|
||||
|
||||
String originalFileName;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
String? originalMimeType;
|
||||
|
||||
String originalPath;
|
||||
|
||||
UserResponseDto owner;
|
||||
|
||||
String ownerId;
|
||||
|
||||
List<PersonWithFacesResponseDto> people;
|
||||
|
||||
/// This property was deprecated in v1.113.0
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? resized;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
SmartInfoResponseDto? smartInfo;
|
||||
|
||||
AssetStackResponseDto? stack;
|
||||
|
||||
List<TagResponseDto> tags;
|
||||
|
||||
String? thumbhash;
|
||||
|
||||
SharedLinkType type;
|
||||
|
||||
List<AssetFaceWithoutPersonResponseDto> unassignedFaces;
|
||||
|
||||
/// This property was added in v1.107.0
|
||||
DateTime updatedAt;
|
||||
|
||||
String albumName;
|
||||
|
||||
String? albumThumbnailAssetId;
|
||||
|
||||
List<AlbumUserResponseDto> albumUsers;
|
||||
|
||||
int assetCount;
|
||||
|
||||
List<AssetResponseDto> assets;
|
||||
|
||||
DateTime createdAt;
|
||||
|
||||
String? description;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
DateTime? endDate;
|
||||
|
||||
bool hasSharedLink;
|
||||
|
||||
bool isActivityEnabled;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
DateTime? lastModifiedAssetTimestamp;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
AssetOrder? order;
|
||||
|
||||
bool shared;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
DateTime? startDate;
|
||||
|
||||
String albumId;
|
||||
|
||||
String? assetId;
|
||||
|
||||
String? comment;
|
||||
|
||||
UserResponseDto user;
|
||||
|
||||
OnThisDayDto data;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
DateTime? deletedAt;
|
||||
|
||||
bool isSaved;
|
||||
|
||||
DateTime memoryAt;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
DateTime? seenAt;
|
||||
|
||||
UserAvatarColor avatarColor;
|
||||
|
||||
String email;
|
||||
|
||||
///
|
||||
/// Please note: This property should have been non-nullable! Since the specification file
|
||||
/// does not include a default value (using the "default:" property), however, the generated
|
||||
/// source code must fall back to having a nullable type.
|
||||
/// Consider adding a "default:" property in the specification file to hide this note.
|
||||
///
|
||||
bool? inTimeline;
|
||||
|
||||
String name;
|
||||
|
||||
DateTime profileChangedAt;
|
||||
|
||||
String profileImagePath;
|
||||
|
||||
DateTime? birthDate;
|
||||
|
||||
bool isHidden;
|
||||
|
||||
String thumbnailPath;
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
AlbumResponseDto? album;
|
||||
|
||||
bool allowDownload;
|
||||
|
||||
bool allowUpload;
|
||||
|
||||
DateTime? expiresAt;
|
||||
|
||||
String key;
|
||||
|
||||
String? password;
|
||||
|
||||
bool showMetadata;
|
||||
|
||||
String? token;
|
||||
|
||||
String userId;
|
||||
|
||||
String primaryAssetId;
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) => identical(this, other) || other is SyncStreamResponseDtoData &&
|
||||
other.checksum == checksum &&
|
||||
other.deviceAssetId == deviceAssetId &&
|
||||
other.deviceId == deviceId &&
|
||||
other.duplicateId == duplicateId &&
|
||||
other.duration == duration &&
|
||||
other.exifInfo == exifInfo &&
|
||||
other.fileCreatedAt == fileCreatedAt &&
|
||||
other.fileModifiedAt == fileModifiedAt &&
|
||||
other.hasMetadata == hasMetadata &&
|
||||
other.id == id &&
|
||||
other.isArchived == isArchived &&
|
||||
other.isFavorite == isFavorite &&
|
||||
other.isOffline == isOffline &&
|
||||
other.isTrashed == isTrashed &&
|
||||
other.libraryId == libraryId &&
|
||||
other.livePhotoVideoId == livePhotoVideoId &&
|
||||
other.localDateTime == localDateTime &&
|
||||
other.originalFileName == originalFileName &&
|
||||
other.originalMimeType == originalMimeType &&
|
||||
other.originalPath == originalPath &&
|
||||
other.owner == owner &&
|
||||
other.ownerId == ownerId &&
|
||||
_deepEquality.equals(other.people, people) &&
|
||||
other.resized == resized &&
|
||||
other.smartInfo == smartInfo &&
|
||||
other.stack == stack &&
|
||||
_deepEquality.equals(other.tags, tags) &&
|
||||
other.thumbhash == thumbhash &&
|
||||
other.type == type &&
|
||||
_deepEquality.equals(other.unassignedFaces, unassignedFaces) &&
|
||||
other.updatedAt == updatedAt &&
|
||||
other.albumName == albumName &&
|
||||
other.albumThumbnailAssetId == albumThumbnailAssetId &&
|
||||
_deepEquality.equals(other.albumUsers, albumUsers) &&
|
||||
other.assetCount == assetCount &&
|
||||
_deepEquality.equals(other.assets, assets) &&
|
||||
other.createdAt == createdAt &&
|
||||
other.description == description &&
|
||||
other.endDate == endDate &&
|
||||
other.hasSharedLink == hasSharedLink &&
|
||||
other.isActivityEnabled == isActivityEnabled &&
|
||||
other.lastModifiedAssetTimestamp == lastModifiedAssetTimestamp &&
|
||||
other.order == order &&
|
||||
other.shared == shared &&
|
||||
other.startDate == startDate &&
|
||||
other.albumId == albumId &&
|
||||
other.assetId == assetId &&
|
||||
other.comment == comment &&
|
||||
other.user == user &&
|
||||
other.data == data &&
|
||||
other.deletedAt == deletedAt &&
|
||||
other.isSaved == isSaved &&
|
||||
other.memoryAt == memoryAt &&
|
||||
other.seenAt == seenAt &&
|
||||
other.avatarColor == avatarColor &&
|
||||
other.email == email &&
|
||||
other.inTimeline == inTimeline &&
|
||||
other.name == name &&
|
||||
other.profileChangedAt == profileChangedAt &&
|
||||
other.profileImagePath == profileImagePath &&
|
||||
other.birthDate == birthDate &&
|
||||
other.isHidden == isHidden &&
|
||||
other.thumbnailPath == thumbnailPath &&
|
||||
other.album == album &&
|
||||
other.allowDownload == allowDownload &&
|
||||
other.allowUpload == allowUpload &&
|
||||
other.expiresAt == expiresAt &&
|
||||
other.key == key &&
|
||||
other.password == password &&
|
||||
other.showMetadata == showMetadata &&
|
||||
other.token == token &&
|
||||
other.userId == userId &&
|
||||
other.primaryAssetId == primaryAssetId;
|
||||
|
||||
@override
|
||||
int get hashCode =>
|
||||
// ignore: unnecessary_parenthesis
|
||||
(checksum.hashCode) +
|
||||
(deviceAssetId.hashCode) +
|
||||
(deviceId.hashCode) +
|
||||
(duplicateId == null ? 0 : duplicateId!.hashCode) +
|
||||
(duration.hashCode) +
|
||||
(exifInfo == null ? 0 : exifInfo!.hashCode) +
|
||||
(fileCreatedAt.hashCode) +
|
||||
(fileModifiedAt.hashCode) +
|
||||
(hasMetadata.hashCode) +
|
||||
(id.hashCode) +
|
||||
(isArchived.hashCode) +
|
||||
(isFavorite.hashCode) +
|
||||
(isOffline.hashCode) +
|
||||
(isTrashed.hashCode) +
|
||||
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||
(localDateTime.hashCode) +
|
||||
(originalFileName.hashCode) +
|
||||
(originalMimeType == null ? 0 : originalMimeType!.hashCode) +
|
||||
(originalPath.hashCode) +
|
||||
(owner.hashCode) +
|
||||
(ownerId.hashCode) +
|
||||
(people.hashCode) +
|
||||
(resized == null ? 0 : resized!.hashCode) +
|
||||
(smartInfo == null ? 0 : smartInfo!.hashCode) +
|
||||
(stack == null ? 0 : stack!.hashCode) +
|
||||
(tags.hashCode) +
|
||||
(thumbhash == null ? 0 : thumbhash!.hashCode) +
|
||||
(type.hashCode) +
|
||||
(unassignedFaces.hashCode) +
|
||||
(updatedAt.hashCode) +
|
||||
(albumName.hashCode) +
|
||||
(albumThumbnailAssetId == null ? 0 : albumThumbnailAssetId!.hashCode) +
|
||||
(albumUsers.hashCode) +
|
||||
(assetCount.hashCode) +
|
||||
(assets.hashCode) +
|
||||
(createdAt.hashCode) +
|
||||
(description == null ? 0 : description!.hashCode) +
|
||||
(endDate == null ? 0 : endDate!.hashCode) +
|
||||
(hasSharedLink.hashCode) +
|
||||
(isActivityEnabled.hashCode) +
|
||||
(lastModifiedAssetTimestamp == null ? 0 : lastModifiedAssetTimestamp!.hashCode) +
|
||||
(order == null ? 0 : order!.hashCode) +
|
||||
(shared.hashCode) +
|
||||
(startDate == null ? 0 : startDate!.hashCode) +
|
||||
(albumId.hashCode) +
|
||||
(assetId == null ? 0 : assetId!.hashCode) +
|
||||
(comment == null ? 0 : comment!.hashCode) +
|
||||
(user.hashCode) +
|
||||
(data.hashCode) +
|
||||
(deletedAt == null ? 0 : deletedAt!.hashCode) +
|
||||
(isSaved.hashCode) +
|
||||
(memoryAt.hashCode) +
|
||||
(seenAt == null ? 0 : seenAt!.hashCode) +
|
||||
(avatarColor.hashCode) +
|
||||
(email.hashCode) +
|
||||
(inTimeline == null ? 0 : inTimeline!.hashCode) +
|
||||
(name.hashCode) +
|
||||
(profileChangedAt.hashCode) +
|
||||
(profileImagePath.hashCode) +
|
||||
(birthDate == null ? 0 : birthDate!.hashCode) +
|
||||
(isHidden.hashCode) +
|
||||
(thumbnailPath.hashCode) +
|
||||
(album == null ? 0 : album!.hashCode) +
|
||||
(allowDownload.hashCode) +
|
||||
(allowUpload.hashCode) +
|
||||
(expiresAt == null ? 0 : expiresAt!.hashCode) +
|
||||
(key.hashCode) +
|
||||
(password == null ? 0 : password!.hashCode) +
|
||||
(showMetadata.hashCode) +
|
||||
(token == null ? 0 : token!.hashCode) +
|
||||
(userId.hashCode) +
|
||||
(primaryAssetId.hashCode);
|
||||
|
||||
@override
|
||||
String toString() => 'SyncStreamResponseDtoData[checksum=$checksum, deviceAssetId=$deviceAssetId, deviceId=$deviceId, duplicateId=$duplicateId, duration=$duration, exifInfo=$exifInfo, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, hasMetadata=$hasMetadata, id=$id, isArchived=$isArchived, isFavorite=$isFavorite, isOffline=$isOffline, isTrashed=$isTrashed, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, originalMimeType=$originalMimeType, originalPath=$originalPath, owner=$owner, ownerId=$ownerId, people=$people, resized=$resized, smartInfo=$smartInfo, stack=$stack, tags=$tags, thumbhash=$thumbhash, type=$type, unassignedFaces=$unassignedFaces, updatedAt=$updatedAt, albumName=$albumName, albumThumbnailAssetId=$albumThumbnailAssetId, albumUsers=$albumUsers, assetCount=$assetCount, assets=$assets, createdAt=$createdAt, description=$description, endDate=$endDate, hasSharedLink=$hasSharedLink, isActivityEnabled=$isActivityEnabled, lastModifiedAssetTimestamp=$lastModifiedAssetTimestamp, order=$order, shared=$shared, startDate=$startDate, albumId=$albumId, assetId=$assetId, comment=$comment, user=$user, data=$data, deletedAt=$deletedAt, isSaved=$isSaved, memoryAt=$memoryAt, seenAt=$seenAt, avatarColor=$avatarColor, email=$email, inTimeline=$inTimeline, name=$name, profileChangedAt=$profileChangedAt, profileImagePath=$profileImagePath, birthDate=$birthDate, isHidden=$isHidden, thumbnailPath=$thumbnailPath, album=$album, allowDownload=$allowDownload, allowUpload=$allowUpload, expiresAt=$expiresAt, key=$key, password=$password, showMetadata=$showMetadata, token=$token, userId=$userId, primaryAssetId=$primaryAssetId]';
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
final json = <String, dynamic>{};
|
||||
json[r'checksum'] = this.checksum;
|
||||
json[r'deviceAssetId'] = this.deviceAssetId;
|
||||
json[r'deviceId'] = this.deviceId;
|
||||
if (this.duplicateId != null) {
|
||||
json[r'duplicateId'] = this.duplicateId;
|
||||
} else {
|
||||
// json[r'duplicateId'] = null;
|
||||
}
|
||||
json[r'duration'] = this.duration;
|
||||
if (this.exifInfo != null) {
|
||||
json[r'exifInfo'] = this.exifInfo;
|
||||
} else {
|
||||
// json[r'exifInfo'] = null;
|
||||
}
|
||||
json[r'fileCreatedAt'] = this.fileCreatedAt.toUtc().toIso8601String();
|
||||
json[r'fileModifiedAt'] = this.fileModifiedAt.toUtc().toIso8601String();
|
||||
json[r'hasMetadata'] = this.hasMetadata;
|
||||
json[r'id'] = this.id;
|
||||
json[r'isArchived'] = this.isArchived;
|
||||
json[r'isFavorite'] = this.isFavorite;
|
||||
json[r'isOffline'] = this.isOffline;
|
||||
json[r'isTrashed'] = this.isTrashed;
|
||||
if (this.libraryId != null) {
|
||||
json[r'libraryId'] = this.libraryId;
|
||||
} else {
|
||||
// json[r'libraryId'] = null;
|
||||
}
|
||||
if (this.livePhotoVideoId != null) {
|
||||
json[r'livePhotoVideoId'] = this.livePhotoVideoId;
|
||||
} else {
|
||||
// json[r'livePhotoVideoId'] = null;
|
||||
}
|
||||
json[r'localDateTime'] = this.localDateTime.toUtc().toIso8601String();
|
||||
json[r'originalFileName'] = this.originalFileName;
|
||||
if (this.originalMimeType != null) {
|
||||
json[r'originalMimeType'] = this.originalMimeType;
|
||||
} else {
|
||||
// json[r'originalMimeType'] = null;
|
||||
}
|
||||
json[r'originalPath'] = this.originalPath;
|
||||
json[r'owner'] = this.owner;
|
||||
json[r'ownerId'] = this.ownerId;
|
||||
json[r'people'] = this.people;
|
||||
if (this.resized != null) {
|
||||
json[r'resized'] = this.resized;
|
||||
} else {
|
||||
// json[r'resized'] = null;
|
||||
}
|
||||
if (this.smartInfo != null) {
|
||||
json[r'smartInfo'] = this.smartInfo;
|
||||
} else {
|
||||
// json[r'smartInfo'] = null;
|
||||
}
|
||||
if (this.stack != null) {
|
||||
json[r'stack'] = this.stack;
|
||||
} else {
|
||||
// json[r'stack'] = null;
|
||||
}
|
||||
json[r'tags'] = this.tags;
|
||||
if (this.thumbhash != null) {
|
||||
json[r'thumbhash'] = this.thumbhash;
|
||||
} else {
|
||||
// json[r'thumbhash'] = null;
|
||||
}
|
||||
json[r'type'] = this.type;
|
||||
json[r'unassignedFaces'] = this.unassignedFaces;
|
||||
json[r'updatedAt'] = this.updatedAt.toUtc().toIso8601String();
|
||||
json[r'albumName'] = this.albumName;
|
||||
if (this.albumThumbnailAssetId != null) {
|
||||
json[r'albumThumbnailAssetId'] = this.albumThumbnailAssetId;
|
||||
} else {
|
||||
// json[r'albumThumbnailAssetId'] = null;
|
||||
}
|
||||
json[r'albumUsers'] = this.albumUsers;
|
||||
json[r'assetCount'] = this.assetCount;
|
||||
json[r'assets'] = this.assets;
|
||||
json[r'createdAt'] = this.createdAt.toUtc().toIso8601String();
|
||||
if (this.description != null) {
|
||||
json[r'description'] = this.description;
|
||||
} else {
|
||||
// json[r'description'] = null;
|
||||
}
|
||||
if (this.endDate != null) {
|
||||
json[r'endDate'] = this.endDate!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'endDate'] = null;
|
||||
}
|
||||
json[r'hasSharedLink'] = this.hasSharedLink;
|
||||
json[r'isActivityEnabled'] = this.isActivityEnabled;
|
||||
if (this.lastModifiedAssetTimestamp != null) {
|
||||
json[r'lastModifiedAssetTimestamp'] = this.lastModifiedAssetTimestamp!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'lastModifiedAssetTimestamp'] = null;
|
||||
}
|
||||
if (this.order != null) {
|
||||
json[r'order'] = this.order;
|
||||
} else {
|
||||
// json[r'order'] = null;
|
||||
}
|
||||
json[r'shared'] = this.shared;
|
||||
if (this.startDate != null) {
|
||||
json[r'startDate'] = this.startDate!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'startDate'] = null;
|
||||
}
|
||||
json[r'albumId'] = this.albumId;
|
||||
if (this.assetId != null) {
|
||||
json[r'assetId'] = this.assetId;
|
||||
} else {
|
||||
// json[r'assetId'] = null;
|
||||
}
|
||||
if (this.comment != null) {
|
||||
json[r'comment'] = this.comment;
|
||||
} else {
|
||||
// json[r'comment'] = null;
|
||||
}
|
||||
json[r'user'] = this.user;
|
||||
json[r'data'] = this.data;
|
||||
if (this.deletedAt != null) {
|
||||
json[r'deletedAt'] = this.deletedAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'deletedAt'] = null;
|
||||
}
|
||||
json[r'isSaved'] = this.isSaved;
|
||||
json[r'memoryAt'] = this.memoryAt.toUtc().toIso8601String();
|
||||
if (this.seenAt != null) {
|
||||
json[r'seenAt'] = this.seenAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'seenAt'] = null;
|
||||
}
|
||||
json[r'avatarColor'] = this.avatarColor;
|
||||
json[r'email'] = this.email;
|
||||
if (this.inTimeline != null) {
|
||||
json[r'inTimeline'] = this.inTimeline;
|
||||
} else {
|
||||
// json[r'inTimeline'] = null;
|
||||
}
|
||||
json[r'name'] = this.name;
|
||||
json[r'profileChangedAt'] = this.profileChangedAt.toUtc().toIso8601String();
|
||||
json[r'profileImagePath'] = this.profileImagePath;
|
||||
if (this.birthDate != null) {
|
||||
json[r'birthDate'] = _dateFormatter.format(this.birthDate!.toUtc());
|
||||
} else {
|
||||
// json[r'birthDate'] = null;
|
||||
}
|
||||
json[r'isHidden'] = this.isHidden;
|
||||
json[r'thumbnailPath'] = this.thumbnailPath;
|
||||
if (this.album != null) {
|
||||
json[r'album'] = this.album;
|
||||
} else {
|
||||
// json[r'album'] = null;
|
||||
}
|
||||
json[r'allowDownload'] = this.allowDownload;
|
||||
json[r'allowUpload'] = this.allowUpload;
|
||||
if (this.expiresAt != null) {
|
||||
json[r'expiresAt'] = this.expiresAt!.toUtc().toIso8601String();
|
||||
} else {
|
||||
// json[r'expiresAt'] = null;
|
||||
}
|
||||
json[r'key'] = this.key;
|
||||
if (this.password != null) {
|
||||
json[r'password'] = this.password;
|
||||
} else {
|
||||
// json[r'password'] = null;
|
||||
}
|
||||
json[r'showMetadata'] = this.showMetadata;
|
||||
if (this.token != null) {
|
||||
json[r'token'] = this.token;
|
||||
} else {
|
||||
// json[r'token'] = null;
|
||||
}
|
||||
json[r'userId'] = this.userId;
|
||||
json[r'primaryAssetId'] = this.primaryAssetId;
|
||||
return json;
|
||||
}
|
||||
|
||||
/// Returns a new [SyncStreamResponseDtoData] instance and imports its values from
|
||||
/// [value] if it's a [Map], null otherwise.
|
||||
// ignore: prefer_constructors_over_static_methods
|
||||
static SyncStreamResponseDtoData? fromJson(dynamic value) {
|
||||
upgradeDto(value, "SyncStreamResponseDtoData");
|
||||
if (value is Map) {
|
||||
final json = value.cast<String, dynamic>();
|
||||
|
||||
return SyncStreamResponseDtoData(
|
||||
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||
deviceAssetId: mapValueOfType<String>(json, r'deviceAssetId')!,
|
||||
deviceId: mapValueOfType<String>(json, r'deviceId')!,
|
||||
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
||||
duration: mapValueOfType<String>(json, r'duration')!,
|
||||
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
|
||||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,
|
||||
hasMetadata: mapValueOfType<bool>(json, r'hasMetadata')!,
|
||||
id: mapValueOfType<String>(json, r'id')!,
|
||||
isArchived: mapValueOfType<bool>(json, r'isArchived')!,
|
||||
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||
isOffline: mapValueOfType<bool>(json, r'isOffline')!,
|
||||
isTrashed: mapValueOfType<bool>(json, r'isTrashed')!,
|
||||
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||
localDateTime: mapDateTime(json, r'localDateTime', r'')!,
|
||||
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
|
||||
originalMimeType: mapValueOfType<String>(json, r'originalMimeType'),
|
||||
originalPath: mapValueOfType<String>(json, r'originalPath')!,
|
||||
owner: UserResponseDto.fromJson(json[r'owner'])!,
|
||||
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||
people: PersonWithFacesResponseDto.listFromJson(json[r'people']),
|
||||
resized: mapValueOfType<bool>(json, r'resized'),
|
||||
smartInfo: SmartInfoResponseDto.fromJson(json[r'smartInfo']),
|
||||
stack: AssetStackResponseDto.fromJson(json[r'stack']),
|
||||
tags: TagResponseDto.listFromJson(json[r'tags']),
|
||||
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
|
||||
type: SharedLinkType.fromJson(json[r'type'])!,
|
||||
unassignedFaces: AssetFaceWithoutPersonResponseDto.listFromJson(json[r'unassignedFaces']),
|
||||
updatedAt: mapDateTime(json, r'updatedAt', r'')!,
|
||||
albumName: mapValueOfType<String>(json, r'albumName')!,
|
||||
albumThumbnailAssetId: mapValueOfType<String>(json, r'albumThumbnailAssetId'),
|
||||
albumUsers: AlbumUserResponseDto.listFromJson(json[r'albumUsers']),
|
||||
assetCount: mapValueOfType<int>(json, r'assetCount')!,
|
||||
assets: AssetResponseDto.listFromJson(json[r'assets']),
|
||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||
description: mapValueOfType<String>(json, r'description'),
|
||||
endDate: mapDateTime(json, r'endDate', r''),
|
||||
hasSharedLink: mapValueOfType<bool>(json, r'hasSharedLink')!,
|
||||
isActivityEnabled: mapValueOfType<bool>(json, r'isActivityEnabled')!,
|
||||
lastModifiedAssetTimestamp: mapDateTime(json, r'lastModifiedAssetTimestamp', r''),
|
||||
order: AssetOrder.fromJson(json[r'order']),
|
||||
shared: mapValueOfType<bool>(json, r'shared')!,
|
||||
startDate: mapDateTime(json, r'startDate', r''),
|
||||
albumId: mapValueOfType<String>(json, r'albumId')!,
|
||||
assetId: mapValueOfType<String>(json, r'assetId'),
|
||||
comment: mapValueOfType<String>(json, r'comment'),
|
||||
user: UserResponseDto.fromJson(json[r'user'])!,
|
||||
data: OnThisDayDto.fromJson(json[r'data'])!,
|
||||
deletedAt: mapDateTime(json, r'deletedAt', r''),
|
||||
isSaved: mapValueOfType<bool>(json, r'isSaved')!,
|
||||
memoryAt: mapDateTime(json, r'memoryAt', r'')!,
|
||||
seenAt: mapDateTime(json, r'seenAt', r''),
|
||||
avatarColor: UserAvatarColor.fromJson(json[r'avatarColor'])!,
|
||||
email: mapValueOfType<String>(json, r'email')!,
|
||||
inTimeline: mapValueOfType<bool>(json, r'inTimeline'),
|
||||
name: mapValueOfType<String>(json, r'name')!,
|
||||
profileChangedAt: mapDateTime(json, r'profileChangedAt', r'')!,
|
||||
profileImagePath: mapValueOfType<String>(json, r'profileImagePath')!,
|
||||
birthDate: mapDateTime(json, r'birthDate', r''),
|
||||
isHidden: mapValueOfType<bool>(json, r'isHidden')!,
|
||||
thumbnailPath: mapValueOfType<String>(json, r'thumbnailPath')!,
|
||||
album: AlbumResponseDto.fromJson(json[r'album']),
|
||||
allowDownload: mapValueOfType<bool>(json, r'allowDownload')!,
|
||||
allowUpload: mapValueOfType<bool>(json, r'allowUpload')!,
|
||||
expiresAt: mapDateTime(json, r'expiresAt', r''),
|
||||
key: mapValueOfType<String>(json, r'key')!,
|
||||
password: mapValueOfType<String>(json, r'password'),
|
||||
showMetadata: mapValueOfType<bool>(json, r'showMetadata')!,
|
||||
token: mapValueOfType<String>(json, r'token'),
|
||||
userId: mapValueOfType<String>(json, r'userId')!,
|
||||
primaryAssetId: mapValueOfType<String>(json, r'primaryAssetId')!,
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static List<SyncStreamResponseDtoData> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncStreamResponseDtoData>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncStreamResponseDtoData.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
|
||||
static Map<String, SyncStreamResponseDtoData> mapFromJson(dynamic json) {
|
||||
final map = <String, SyncStreamResponseDtoData>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||
for (final entry in json.entries) {
|
||||
final value = SyncStreamResponseDtoData.fromJson(entry.value);
|
||||
if (value != null) {
|
||||
map[entry.key] = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
// maps a json object with a list of SyncStreamResponseDtoData-objects as value to a dart map
|
||||
static Map<String, List<SyncStreamResponseDtoData>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||
final map = <String, List<SyncStreamResponseDtoData>>{};
|
||||
if (json is Map && json.isNotEmpty) {
|
||||
// ignore: parameter_assignments
|
||||
json = json.cast<String, dynamic>();
|
||||
for (final entry in json.entries) {
|
||||
map[entry.key] = SyncStreamResponseDtoData.listFromJson(entry.value, growable: growable,);
|
||||
}
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
/// The list of required keys that must be present in a JSON.
|
||||
static const requiredKeys = <String>{
|
||||
'checksum',
|
||||
'deviceAssetId',
|
||||
'deviceId',
|
||||
'duration',
|
||||
'fileCreatedAt',
|
||||
'fileModifiedAt',
|
||||
'hasMetadata',
|
||||
'id',
|
||||
'isArchived',
|
||||
'isFavorite',
|
||||
'isOffline',
|
||||
'isTrashed',
|
||||
'localDateTime',
|
||||
'originalFileName',
|
||||
'originalPath',
|
||||
'owner',
|
||||
'ownerId',
|
||||
'thumbhash',
|
||||
'type',
|
||||
'updatedAt',
|
||||
'albumName',
|
||||
'albumThumbnailAssetId',
|
||||
'albumUsers',
|
||||
'assetCount',
|
||||
'assets',
|
||||
'createdAt',
|
||||
'description',
|
||||
'hasSharedLink',
|
||||
'isActivityEnabled',
|
||||
'shared',
|
||||
'albumId',
|
||||
'assetId',
|
||||
'user',
|
||||
'data',
|
||||
'isSaved',
|
||||
'memoryAt',
|
||||
'avatarColor',
|
||||
'email',
|
||||
'name',
|
||||
'profileChangedAt',
|
||||
'profileImagePath',
|
||||
'birthDate',
|
||||
'isHidden',
|
||||
'thumbnailPath',
|
||||
'allowDownload',
|
||||
'allowUpload',
|
||||
'expiresAt',
|
||||
'key',
|
||||
'password',
|
||||
'showMetadata',
|
||||
'userId',
|
||||
'primaryAssetId',
|
||||
};
|
||||
}
|
||||
|
121
mobile/openapi/lib/model/sync_type.dart
generated
Normal file
121
mobile/openapi/lib/model/sync_type.dart
generated
Normal file
|
@ -0,0 +1,121 @@
|
|||
//
|
||||
// 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 SyncType {
|
||||
/// Instantiate a new enum with the provided [value].
|
||||
const SyncType._(this.value);
|
||||
|
||||
/// The underlying value of this enum member.
|
||||
final String value;
|
||||
|
||||
@override
|
||||
String toString() => value;
|
||||
|
||||
String toJson() => value;
|
||||
|
||||
static const asset = SyncType._(r'asset');
|
||||
static const assetPeriodPartner = SyncType._(r'asset.partner');
|
||||
static const assetAlbum = SyncType._(r'assetAlbum');
|
||||
static const album = SyncType._(r'album');
|
||||
static const albumAsset = SyncType._(r'albumAsset');
|
||||
static const albumUser = SyncType._(r'albumUser');
|
||||
static const activity = SyncType._(r'activity');
|
||||
static const memory = SyncType._(r'memory');
|
||||
static const partner = SyncType._(r'partner');
|
||||
static const person = SyncType._(r'person');
|
||||
static const sharedLink = SyncType._(r'sharedLink');
|
||||
static const stack = SyncType._(r'stack');
|
||||
static const tag = SyncType._(r'tag');
|
||||
static const user = SyncType._(r'user');
|
||||
|
||||
/// List of all possible values in this [enum][SyncType].
|
||||
static const values = <SyncType>[
|
||||
asset,
|
||||
assetPeriodPartner,
|
||||
assetAlbum,
|
||||
album,
|
||||
albumAsset,
|
||||
albumUser,
|
||||
activity,
|
||||
memory,
|
||||
partner,
|
||||
person,
|
||||
sharedLink,
|
||||
stack,
|
||||
tag,
|
||||
user,
|
||||
];
|
||||
|
||||
static SyncType? fromJson(dynamic value) => SyncTypeTypeTransformer().decode(value);
|
||||
|
||||
static List<SyncType> listFromJson(dynamic json, {bool growable = false,}) {
|
||||
final result = <SyncType>[];
|
||||
if (json is List && json.isNotEmpty) {
|
||||
for (final row in json) {
|
||||
final value = SyncType.fromJson(row);
|
||||
if (value != null) {
|
||||
result.add(value);
|
||||
}
|
||||
}
|
||||
}
|
||||
return result.toList(growable: growable);
|
||||
}
|
||||
}
|
||||
|
||||
/// Transformation class that can [encode] an instance of [SyncType] to String,
|
||||
/// and [decode] dynamic data back to [SyncType].
|
||||
class SyncTypeTypeTransformer {
|
||||
factory SyncTypeTypeTransformer() => _instance ??= const SyncTypeTypeTransformer._();
|
||||
|
||||
const SyncTypeTypeTransformer._();
|
||||
|
||||
String encode(SyncType data) => data.value;
|
||||
|
||||
/// Decodes a [dynamic value][data] to a SyncType.
|
||||
///
|
||||
/// 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.
|
||||
SyncType? decode(dynamic data, {bool allowNull = true}) {
|
||||
if (data != null) {
|
||||
switch (data) {
|
||||
case r'asset': return SyncType.asset;
|
||||
case r'asset.partner': return SyncType.assetPeriodPartner;
|
||||
case r'assetAlbum': return SyncType.assetAlbum;
|
||||
case r'album': return SyncType.album;
|
||||
case r'albumAsset': return SyncType.albumAsset;
|
||||
case r'albumUser': return SyncType.albumUser;
|
||||
case r'activity': return SyncType.activity;
|
||||
case r'memory': return SyncType.memory;
|
||||
case r'partner': return SyncType.partner;
|
||||
case r'person': return SyncType.person;
|
||||
case r'sharedLink': return SyncType.sharedLink;
|
||||
case r'stack': return SyncType.stack;
|
||||
case r'tag': return SyncType.tag;
|
||||
case r'user': return SyncType.user;
|
||||
default:
|
||||
if (!allowNull) {
|
||||
throw ArgumentError('Unknown enum value to decode: $data');
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Singleton [SyncTypeTypeTransformer] instance.
|
||||
static SyncTypeTypeTransformer? _instance;
|
||||
}
|
||||
|
|
@ -5778,6 +5778,41 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/sync/acknowledge": {
|
||||
"post": {
|
||||
"operationId": "ackSync",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SyncAcknowledgeDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"204": {
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Sync"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/sync/delta-sync": {
|
||||
"post": {
|
||||
"operationId": "getDeltaSync",
|
||||
|
@ -5865,6 +5900,51 @@
|
|||
]
|
||||
}
|
||||
},
|
||||
"/sync/stream": {
|
||||
"post": {
|
||||
"operationId": "getSyncStream",
|
||||
"parameters": [],
|
||||
"requestBody": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/SyncStreamDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": true
|
||||
},
|
||||
"responses": {
|
||||
"200": {
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/SyncStreamResponseDto"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
}
|
||||
},
|
||||
"description": ""
|
||||
}
|
||||
},
|
||||
"security": [
|
||||
{
|
||||
"bearer": []
|
||||
},
|
||||
{
|
||||
"cookie": []
|
||||
},
|
||||
{
|
||||
"api_key": []
|
||||
}
|
||||
],
|
||||
"tags": [
|
||||
"Sync"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/system-config": {
|
||||
"get": {
|
||||
"operationId": "getConfig",
|
||||
|
@ -7581,6 +7661,23 @@
|
|||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AlbumAssetResponseDto": {
|
||||
"properties": {
|
||||
"albumId": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"assetId": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"albumId",
|
||||
"assetId"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"AlbumResponseDto": {
|
||||
"properties": {
|
||||
"albumName": {
|
||||
|
@ -11456,6 +11553,175 @@
|
|||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SyncAcknowledgeDto": {
|
||||
"properties": {
|
||||
"activity": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"album": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"albumAsset": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"albumUser": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"asset": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"assetAlbum": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"assetPartner": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"memory": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"partner": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"person": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"sharedLink": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"stack": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"tag": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
},
|
||||
"user": {
|
||||
"$ref": "#/components/schemas/SyncCheckpointDto"
|
||||
}
|
||||
},
|
||||
"type": "object"
|
||||
},
|
||||
"SyncAction": {
|
||||
"enum": [
|
||||
"upsert",
|
||||
"delete"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SyncCheckpointDto": {
|
||||
"properties": {
|
||||
"id": {
|
||||
"format": "uuid",
|
||||
"type": "string"
|
||||
},
|
||||
"timestamp": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"timestamp"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SyncStreamDto": {
|
||||
"properties": {
|
||||
"types": {
|
||||
"items": {
|
||||
"enum": [
|
||||
"asset",
|
||||
"asset.partner",
|
||||
"assetAlbum",
|
||||
"album",
|
||||
"albumAsset",
|
||||
"albumUser",
|
||||
"activity",
|
||||
"memory",
|
||||
"partner",
|
||||
"person",
|
||||
"sharedLink",
|
||||
"stack",
|
||||
"tag",
|
||||
"user"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"type": "array"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"types"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SyncStreamResponseDto": {
|
||||
"properties": {
|
||||
"action": {
|
||||
"$ref": "#/components/schemas/SyncAction"
|
||||
},
|
||||
"data": {
|
||||
"anyOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/AssetResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AlbumResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/AlbumAssetResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/ActivityResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/MemoryResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/PartnerResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/PersonResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/SharedLinkResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/StackResponseDto"
|
||||
},
|
||||
{
|
||||
"$ref": "#/components/schemas/UserResponseDto"
|
||||
}
|
||||
]
|
||||
},
|
||||
"type": {
|
||||
"$ref": "#/components/schemas/SyncType"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"action",
|
||||
"data",
|
||||
"type"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"SyncType": {
|
||||
"enum": [
|
||||
"asset",
|
||||
"asset.partner",
|
||||
"assetAlbum",
|
||||
"album",
|
||||
"albumAsset",
|
||||
"albumUser",
|
||||
"activity",
|
||||
"memory",
|
||||
"partner",
|
||||
"person",
|
||||
"sharedLink",
|
||||
"stack",
|
||||
"tag",
|
||||
"user"
|
||||
],
|
||||
"type": "string"
|
||||
},
|
||||
"SystemConfigDto": {
|
||||
"properties": {
|
||||
"ffmpeg": {
|
||||
|
|
|
@ -1069,6 +1069,26 @@ export type StackCreateDto = {
|
|||
export type StackUpdateDto = {
|
||||
primaryAssetId?: string;
|
||||
};
|
||||
export type SyncCheckpointDto = {
|
||||
id: string;
|
||||
timestamp: string;
|
||||
};
|
||||
export type SyncAcknowledgeDto = {
|
||||
activity?: SyncCheckpointDto;
|
||||
album?: SyncCheckpointDto;
|
||||
albumAsset?: SyncCheckpointDto;
|
||||
albumUser?: SyncCheckpointDto;
|
||||
asset?: SyncCheckpointDto;
|
||||
assetAlbum?: SyncCheckpointDto;
|
||||
assetPartner?: SyncCheckpointDto;
|
||||
memory?: SyncCheckpointDto;
|
||||
partner?: SyncCheckpointDto;
|
||||
person?: SyncCheckpointDto;
|
||||
sharedLink?: SyncCheckpointDto;
|
||||
stack?: SyncCheckpointDto;
|
||||
tag?: SyncCheckpointDto;
|
||||
user?: SyncCheckpointDto;
|
||||
};
|
||||
export type AssetDeltaSyncDto = {
|
||||
updatedAfter: string;
|
||||
userIds: string[];
|
||||
|
@ -1084,6 +1104,18 @@ export type AssetFullSyncDto = {
|
|||
updatedUntil: string;
|
||||
userId?: string;
|
||||
};
|
||||
export type SyncStreamDto = {
|
||||
types: ("asset" | "asset.partner" | "assetAlbum" | "album" | "albumAsset" | "albumUser" | "activity" | "memory" | "partner" | "person" | "sharedLink" | "stack" | "tag" | "user")[];
|
||||
};
|
||||
export type AlbumAssetResponseDto = {
|
||||
albumId: string;
|
||||
assetId: string;
|
||||
};
|
||||
export type SyncStreamResponseDto = {
|
||||
action: SyncAction;
|
||||
data: AssetResponseDto | AlbumResponseDto | AlbumAssetResponseDto | ActivityResponseDto | MemoryResponseDto | PartnerResponseDto | PersonResponseDto | SharedLinkResponseDto | StackResponseDto | UserResponseDto;
|
||||
"type": SyncType;
|
||||
};
|
||||
export type SystemConfigFFmpegDto = {
|
||||
accel: TranscodeHWAccel;
|
||||
accelDecode: boolean;
|
||||
|
@ -2852,6 +2884,15 @@ export function updateStack({ id, stackUpdateDto }: {
|
|||
body: stackUpdateDto
|
||||
})));
|
||||
}
|
||||
export function ackSync({ syncAcknowledgeDto }: {
|
||||
syncAcknowledgeDto: SyncAcknowledgeDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchText("/sync/acknowledge", oazapfts.json({
|
||||
...opts,
|
||||
method: "POST",
|
||||
body: syncAcknowledgeDto
|
||||
})));
|
||||
}
|
||||
export function getDeltaSync({ assetDeltaSyncDto }: {
|
||||
assetDeltaSyncDto: AssetDeltaSyncDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
|
@ -2876,6 +2917,18 @@ export function getFullSyncForUser({ assetFullSyncDto }: {
|
|||
body: assetFullSyncDto
|
||||
})));
|
||||
}
|
||||
export function getSyncStream({ syncStreamDto }: {
|
||||
syncStreamDto: SyncStreamDto;
|
||||
}, opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
data: SyncStreamResponseDto[];
|
||||
}>("/sync/stream", oazapfts.json({
|
||||
...opts,
|
||||
method: "POST",
|
||||
body: syncStreamDto
|
||||
})));
|
||||
}
|
||||
export function getConfig(opts?: Oazapfts.RequestOpts) {
|
||||
return oazapfts.ok(oazapfts.fetchJson<{
|
||||
status: 200;
|
||||
|
@ -3491,6 +3544,26 @@ export enum Error2 {
|
|||
NoPermission = "no_permission",
|
||||
NotFound = "not_found"
|
||||
}
|
||||
export enum SyncAction {
|
||||
Upsert = "upsert",
|
||||
Delete = "delete"
|
||||
}
|
||||
export enum SyncType {
|
||||
Asset = "asset",
|
||||
AssetPartner = "asset.partner",
|
||||
AssetAlbum = "assetAlbum",
|
||||
Album = "album",
|
||||
AlbumAsset = "albumAsset",
|
||||
AlbumUser = "albumUser",
|
||||
Activity = "activity",
|
||||
Memory = "memory",
|
||||
Partner = "partner",
|
||||
Person = "person",
|
||||
SharedLink = "sharedLink",
|
||||
Stack = "stack",
|
||||
Tag = "tag",
|
||||
User = "user"
|
||||
}
|
||||
export enum TranscodeHWAccel {
|
||||
Nvenc = "nvenc",
|
||||
Qsv = "qsv",
|
||||
|
|
|
@ -26,7 +26,7 @@ import { teardownTelemetry } from 'src/repositories/telemetry.repository';
|
|||
import { services } from 'src/services';
|
||||
import { DatabaseService } from 'src/services/database.service';
|
||||
|
||||
const common = [...services, ...repositories];
|
||||
const common = [...services, ...repositories, GlobalExceptionFilter];
|
||||
|
||||
const middleware = [
|
||||
FileUploadInterceptor,
|
||||
|
|
|
@ -1,27 +1,60 @@
|
|||
import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Body, Controller, Header, HttpCode, HttpStatus, Post, Res } from '@nestjs/common';
|
||||
import { ApiResponse, ApiTags } from '@nestjs/swagger';
|
||||
import { Response } from 'express';
|
||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetDeltaSyncDto, AssetDeltaSyncResponseDto, AssetFullSyncDto } from 'src/dtos/sync.dto';
|
||||
import {
|
||||
AssetDeltaSyncDto,
|
||||
AssetDeltaSyncResponseDto,
|
||||
AssetFullSyncDto,
|
||||
SyncAcknowledgeDto,
|
||||
SyncStreamDto,
|
||||
SyncStreamResponseDto,
|
||||
} from 'src/dtos/sync.dto';
|
||||
import { Auth, Authenticated } from 'src/middleware/auth.guard';
|
||||
import { GlobalExceptionFilter } from 'src/middleware/global-exception.filter';
|
||||
import { SyncService } from 'src/services/sync.service';
|
||||
|
||||
@ApiTags('Sync')
|
||||
@Controller('sync')
|
||||
export class SyncController {
|
||||
constructor(private service: SyncService) {}
|
||||
constructor(
|
||||
private syncService: SyncService,
|
||||
private errorService: GlobalExceptionFilter,
|
||||
) {}
|
||||
|
||||
@Post('acknowledge')
|
||||
@HttpCode(HttpStatus.NO_CONTENT)
|
||||
@Authenticated()
|
||||
ackSync(@Auth() auth: AuthDto, @Body() dto: SyncAcknowledgeDto) {
|
||||
return this.syncService.acknowledge(auth, dto);
|
||||
}
|
||||
|
||||
@Post('stream')
|
||||
@Header('Content-Type', 'application/jsonlines+json')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@ApiResponse({ status: 200, type: SyncStreamResponseDto, isArray: true })
|
||||
@Authenticated()
|
||||
getSyncStream(@Auth() auth: AuthDto, @Res() res: Response, @Body() dto: SyncStreamDto) {
|
||||
try {
|
||||
void this.syncService.stream(auth, res, dto);
|
||||
} catch (error: Error | any) {
|
||||
res.setHeader('Content-Type', 'application/json');
|
||||
this.errorService.handleError(res, error);
|
||||
}
|
||||
}
|
||||
|
||||
@Post('full-sync')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Authenticated()
|
||||
getFullSyncForUser(@Auth() auth: AuthDto, @Body() dto: AssetFullSyncDto): Promise<AssetResponseDto[]> {
|
||||
return this.service.getFullSync(auth, dto);
|
||||
return this.syncService.getFullSync(auth, dto);
|
||||
}
|
||||
|
||||
@Post('delta-sync')
|
||||
@HttpCode(HttpStatus.OK)
|
||||
@Authenticated()
|
||||
getDeltaSync(@Auth() auth: AuthDto, @Body() dto: AssetDeltaSyncDto): Promise<AssetDeltaSyncResponseDto> {
|
||||
return this.service.getDeltaSync(auth, dto);
|
||||
return this.syncService.getDeltaSync(auth, dto);
|
||||
}
|
||||
}
|
||||
|
|
9
server/src/dtos/album-asset.dto.ts
Normal file
9
server/src/dtos/album-asset.dto.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
|
||||
export class AlbumAssetResponseDto {
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
albumId!: string;
|
||||
|
||||
@ApiProperty({ format: 'uuid' })
|
||||
assetId!: string;
|
||||
}
|
|
@ -1,7 +1,140 @@
|
|||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { IsInt, IsPositive } from 'class-validator';
|
||||
import { ApiProperty, getSchemaPath } from '@nestjs/swagger';
|
||||
import { Type } from 'class-transformer';
|
||||
import { ArrayNotEmpty, IsDateString, IsEnum, IsInt, IsPositive, ValidateNested } from 'class-validator';
|
||||
import { ActivityResponseDto } from 'src/dtos/activity.dto';
|
||||
import { AlbumAssetResponseDto } from 'src/dtos/album-asset.dto';
|
||||
import { AlbumResponseDto } from 'src/dtos/album.dto';
|
||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||
import { ValidateDate, ValidateUUID } from 'src/validation';
|
||||
import { MemoryResponseDto } from 'src/dtos/memory.dto';
|
||||
import { PartnerResponseDto } from 'src/dtos/partner.dto';
|
||||
import { PersonResponseDto } from 'src/dtos/person.dto';
|
||||
import { SharedLinkResponseDto } from 'src/dtos/shared-link.dto';
|
||||
import { StackResponseDto } from 'src/dtos/stack.dto';
|
||||
import { UserResponseDto } from 'src/dtos/user.dto';
|
||||
import { SyncState } from 'src/entities/session-sync-state.entity';
|
||||
import { SyncAction, SyncEntity } from 'src/enum';
|
||||
import { Optional, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
class SyncCheckpointDto {
|
||||
@ValidateUUID()
|
||||
id!: string;
|
||||
|
||||
@IsDateString()
|
||||
timestamp!: string;
|
||||
}
|
||||
|
||||
export class SyncAcknowledgeDto implements SyncState {
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
activity?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
album?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
albumUser?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
albumAsset?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
asset?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
assetAlbum?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
assetPartner?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
memory?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
partner?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
person?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
sharedLink?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
stack?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
tag?: SyncCheckpointDto;
|
||||
|
||||
@Optional()
|
||||
@ValidateNested()
|
||||
@Type(() => SyncCheckpointDto)
|
||||
user?: SyncCheckpointDto;
|
||||
}
|
||||
|
||||
export class SyncStreamResponseDto {
|
||||
@ApiProperty({ enum: SyncEntity, enumName: 'SyncType' })
|
||||
type!: SyncEntity;
|
||||
|
||||
@ApiProperty({ enum: SyncAction, enumName: 'SyncAction' })
|
||||
action!: SyncAction;
|
||||
|
||||
@ApiProperty({
|
||||
anyOf: [
|
||||
{ $ref: getSchemaPath(AssetResponseDto) },
|
||||
{ $ref: getSchemaPath(AlbumResponseDto) },
|
||||
{ $ref: getSchemaPath(AlbumAssetResponseDto) },
|
||||
{ $ref: getSchemaPath(ActivityResponseDto) },
|
||||
{ $ref: getSchemaPath(MemoryResponseDto) },
|
||||
{ $ref: getSchemaPath(PartnerResponseDto) },
|
||||
{ $ref: getSchemaPath(PersonResponseDto) },
|
||||
{ $ref: getSchemaPath(SharedLinkResponseDto) },
|
||||
{ $ref: getSchemaPath(StackResponseDto) },
|
||||
{ $ref: getSchemaPath(UserResponseDto) },
|
||||
],
|
||||
})
|
||||
data!:
|
||||
| ActivityResponseDto
|
||||
| AssetResponseDto
|
||||
| AlbumResponseDto
|
||||
| AlbumAssetResponseDto
|
||||
| MemoryResponseDto
|
||||
| PartnerResponseDto
|
||||
| PersonResponseDto
|
||||
| SharedLinkResponseDto
|
||||
| StackResponseDto
|
||||
| UserResponseDto;
|
||||
}
|
||||
|
||||
export class SyncStreamDto {
|
||||
@IsEnum(SyncEntity, { each: true })
|
||||
@ApiProperty({ enum: SyncEntity, isArray: true })
|
||||
@ArrayNotEmpty()
|
||||
types!: SyncEntity[];
|
||||
}
|
||||
|
||||
export class AssetFullSyncDto {
|
||||
@ValidateUUID({ optional: true })
|
||||
|
|
|
@ -16,6 +16,7 @@ import { MoveEntity } from 'src/entities/move.entity';
|
|||
import { NaturalEarthCountriesEntity } from 'src/entities/natural-earth-countries.entity';
|
||||
import { PartnerEntity } from 'src/entities/partner.entity';
|
||||
import { PersonEntity } from 'src/entities/person.entity';
|
||||
import { SessionSyncStateEntity } from 'src/entities/session-sync-state.entity';
|
||||
import { SessionEntity } from 'src/entities/session.entity';
|
||||
import { SharedLinkEntity } from 'src/entities/shared-link.entity';
|
||||
import { SmartInfoEntity } from 'src/entities/smart-info.entity';
|
||||
|
@ -54,6 +55,7 @@ export const entities = [
|
|||
UserEntity,
|
||||
UserMetadataEntity,
|
||||
SessionEntity,
|
||||
SessionSyncStateEntity,
|
||||
LibraryEntity,
|
||||
VersionHistoryEntity,
|
||||
];
|
||||
|
|
52
server/src/entities/session-sync-state.entity.ts
Normal file
52
server/src/entities/session-sync-state.entity.ts
Normal file
|
@ -0,0 +1,52 @@
|
|||
import { SessionEntity } from 'src/entities/session.entity';
|
||||
import { Column, CreateDateColumn, Entity, JoinColumn, OneToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm';
|
||||
|
||||
export type SyncCheckpoint = {
|
||||
id: string;
|
||||
timestamp: string;
|
||||
};
|
||||
|
||||
export type SyncState = {
|
||||
activity?: SyncCheckpoint;
|
||||
|
||||
album?: SyncCheckpoint;
|
||||
albumUser?: SyncCheckpoint;
|
||||
albumAsset?: SyncCheckpoint;
|
||||
|
||||
asset?: SyncCheckpoint;
|
||||
assetAlbum?: SyncCheckpoint;
|
||||
assetPartner?: SyncCheckpoint;
|
||||
|
||||
memory?: SyncCheckpoint;
|
||||
|
||||
partner?: SyncCheckpoint;
|
||||
|
||||
person?: SyncCheckpoint;
|
||||
|
||||
sharedLink?: SyncCheckpoint;
|
||||
|
||||
stack?: SyncCheckpoint;
|
||||
|
||||
tag?: SyncCheckpoint;
|
||||
|
||||
user?: SyncCheckpoint;
|
||||
};
|
||||
|
||||
@Entity('session_sync_states')
|
||||
export class SessionSyncStateEntity {
|
||||
@OneToOne(() => SessionEntity, { onDelete: 'CASCADE', nullable: true })
|
||||
@JoinColumn()
|
||||
session?: SessionEntity;
|
||||
|
||||
@PrimaryColumn()
|
||||
sessionId!: string;
|
||||
|
||||
@CreateDateColumn({ type: 'timestamptz' })
|
||||
createdAt!: Date;
|
||||
|
||||
@UpdateDateColumn({ type: 'timestamptz' })
|
||||
updatedAt!: Date;
|
||||
|
||||
@Column({ type: 'jsonb', nullable: true })
|
||||
state?: SyncState;
|
||||
}
|
|
@ -53,6 +53,28 @@ export enum DatabaseAction {
|
|||
DELETE = 'DELETE',
|
||||
}
|
||||
|
||||
export enum SyncEntity {
|
||||
ASSET = 'asset',
|
||||
ASSET_PARTNER = 'asset.partner',
|
||||
ASSET_ALBUM = 'assetAlbum',
|
||||
ALBUM = 'album',
|
||||
ALBUM_ASSET = 'albumAsset',
|
||||
ALBUM_USER = 'albumUser',
|
||||
ACTIVITY = 'activity',
|
||||
MEMORY = 'memory',
|
||||
PARTNER = 'partner',
|
||||
PERSON = 'person',
|
||||
SHARED_LINK = 'sharedLink',
|
||||
STACK = 'stack',
|
||||
TAG = 'tag',
|
||||
USER = 'user',
|
||||
}
|
||||
|
||||
export enum SyncAction {
|
||||
UPSERT = 'upsert',
|
||||
DELETE = 'delete',
|
||||
}
|
||||
|
||||
export enum EntityType {
|
||||
ASSET = 'ASSET',
|
||||
ALBUM = 'ALBUM',
|
||||
|
|
43
server/src/interfaces/sync.interface.ts
Normal file
43
server/src/interfaces/sync.interface.ts
Normal file
|
@ -0,0 +1,43 @@
|
|||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { SessionSyncStateEntity, SyncCheckpoint } from 'src/entities/session-sync-state.entity';
|
||||
import { Paginated, PaginationOptions } from 'src/utils/pagination';
|
||||
|
||||
export const ISyncRepository = 'ISyncRepository';
|
||||
|
||||
export type SyncOptions = PaginationOptions & {
|
||||
userId: string;
|
||||
checkpoint?: SyncCheckpoint;
|
||||
};
|
||||
|
||||
export type AssetPartnerSyncOptions = SyncOptions & { partnerIds: string[] };
|
||||
|
||||
export type EntityPK = { id: string };
|
||||
export type DeletedEntity<T = EntityPK> = T & {
|
||||
deletedAt: Date;
|
||||
};
|
||||
export type AlbumAssetPK = {
|
||||
albumId: string;
|
||||
assetId: string;
|
||||
};
|
||||
|
||||
export type AlbumAssetEntity = AlbumAssetPK & {
|
||||
createdAt: Date;
|
||||
};
|
||||
|
||||
export interface ISyncRepository {
|
||||
get(sessionId: string): Promise<SessionSyncStateEntity | null>;
|
||||
upsert(state: Partial<SessionSyncStateEntity>): Promise<void>;
|
||||
|
||||
getAssets(options: SyncOptions): Paginated<AssetEntity>;
|
||||
getDeletedAssets(options: SyncOptions): Paginated<DeletedEntity>;
|
||||
|
||||
getAssetsPartner(options: AssetPartnerSyncOptions): Paginated<AssetEntity>;
|
||||
getDeletedAssetsPartner(options: AssetPartnerSyncOptions): Paginated<DeletedEntity>;
|
||||
|
||||
getAlbums(options: SyncOptions): Paginated<AlbumEntity>;
|
||||
getDeletedAlbums(options: SyncOptions): Paginated<DeletedEntity>;
|
||||
|
||||
getAlbumAssets(options: SyncOptions): Paginated<AlbumAssetEntity>;
|
||||
getDeletedAlbumAssets(options: SyncOptions): Paginated<DeletedEntity<AlbumAssetPK>>;
|
||||
}
|
|
@ -1,9 +1,10 @@
|
|||
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Inject } from '@nestjs/common';
|
||||
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Inject, Injectable } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { ClsService } from 'nestjs-cls';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
import { logGlobalError } from 'src/utils/logger';
|
||||
|
||||
@Injectable()
|
||||
@Catch()
|
||||
export class GlobalExceptionFilter implements ExceptionFilter<Error> {
|
||||
constructor(
|
||||
|
@ -15,10 +16,13 @@ export class GlobalExceptionFilter implements ExceptionFilter<Error> {
|
|||
|
||||
catch(error: Error, host: ArgumentsHost) {
|
||||
const ctx = host.switchToHttp();
|
||||
const response = ctx.getResponse<Response>();
|
||||
this.handleError(ctx.getResponse(), error);
|
||||
}
|
||||
|
||||
handleError(res: Response, error: Error) {
|
||||
const { status, body } = this.fromError(error);
|
||||
if (!response.headersSent) {
|
||||
response.status(status).json({ ...body, statusCode: status, correlationId: this.cls.getId() });
|
||||
if (!res.headersSent) {
|
||||
res.status(status).json({ ...body, statusCode: status, correlationId: this.cls.getId() });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
16
server/src/migrations/1729792220961-AddSessionStateTable.ts
Normal file
16
server/src/migrations/1729792220961-AddSessionStateTable.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class AddSessionStateTable1729792220961 implements MigrationInterface {
|
||||
name = 'AddSessionStateTable1729792220961'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "session_sync_states" ("sessionId" uuid NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "state" jsonb, CONSTRAINT "PK_4821e7414daba4413b8b33546d1" PRIMARY KEY ("sessionId"))`);
|
||||
await queryRunner.query(`ALTER TABLE "session_sync_states" ADD CONSTRAINT "FK_4821e7414daba4413b8b33546d1" FOREIGN KEY ("sessionId") REFERENCES "sessions"("id") ON DELETE CASCADE ON UPDATE NO ACTION`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`ALTER TABLE "session_sync_states" DROP CONSTRAINT "FK_4821e7414daba4413b8b33546d1"`);
|
||||
await queryRunner.query(`DROP TABLE "session_sync_states"`);
|
||||
}
|
||||
|
||||
}
|
|
@ -28,6 +28,7 @@ import { ISessionRepository } from 'src/interfaces/session.interface';
|
|||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISyncRepository } from 'src/interfaces/sync.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
|
||||
|
@ -65,6 +66,7 @@ import { SessionRepository } from 'src/repositories/session.repository';
|
|||
import { SharedLinkRepository } from 'src/repositories/shared-link.repository';
|
||||
import { StackRepository } from 'src/repositories/stack.repository';
|
||||
import { StorageRepository } from 'src/repositories/storage.repository';
|
||||
import { SyncRepository } from 'src/repositories/sync.repository';
|
||||
import { SystemMetadataRepository } from 'src/repositories/system-metadata.repository';
|
||||
import { TagRepository } from 'src/repositories/tag.repository';
|
||||
import { TelemetryRepository } from 'src/repositories/telemetry.repository';
|
||||
|
@ -104,6 +106,7 @@ export const repositories = [
|
|||
{ provide: ISharedLinkRepository, useClass: SharedLinkRepository },
|
||||
{ provide: IStackRepository, useClass: StackRepository },
|
||||
{ provide: IStorageRepository, useClass: StorageRepository },
|
||||
{ provide: ISyncRepository, useClass: SyncRepository },
|
||||
{ provide: ISystemMetadataRepository, useClass: SystemMetadataRepository },
|
||||
{ provide: ITagRepository, useClass: TagRepository },
|
||||
{ provide: ITelemetryRepository, useClass: TelemetryRepository },
|
||||
|
|
114
server/src/repositories/sync.repository.ts
Normal file
114
server/src/repositories/sync.repository.ts
Normal file
|
@ -0,0 +1,114 @@
|
|||
import { Injectable } from '@nestjs/common';
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { SessionSyncStateEntity, SyncCheckpoint } from 'src/entities/session-sync-state.entity';
|
||||
import {
|
||||
AlbumAssetEntity,
|
||||
AlbumAssetPK,
|
||||
AssetPartnerSyncOptions,
|
||||
DeletedEntity,
|
||||
EntityPK,
|
||||
ISyncRepository,
|
||||
SyncOptions,
|
||||
} from 'src/interfaces/sync.interface';
|
||||
import { paginate, Paginated } from 'src/utils/pagination';
|
||||
import { DataSource, FindOptionsWhere, In, MoreThan, MoreThanOrEqual, Repository } from 'typeorm';
|
||||
|
||||
const withCheckpoint = <T>(where: FindOptionsWhere<T>, key: keyof T, checkpoint?: SyncCheckpoint) => {
|
||||
if (!checkpoint) {
|
||||
return [where];
|
||||
}
|
||||
|
||||
const { id: checkpointId, timestamp } = checkpoint;
|
||||
const checkpointDate = new Date(timestamp);
|
||||
return [
|
||||
{
|
||||
...where,
|
||||
[key]: MoreThanOrEqual(new Date(checkpointDate)),
|
||||
id: MoreThan(checkpointId),
|
||||
},
|
||||
{
|
||||
...where,
|
||||
[key]: MoreThan(checkpointDate),
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class SyncRepository implements ISyncRepository {
|
||||
constructor(
|
||||
private dataSource: DataSource,
|
||||
@InjectRepository(AssetEntity) private assetRepository: Repository<AssetEntity>,
|
||||
@InjectRepository(AlbumEntity) private albumRepository: Repository<AlbumEntity>,
|
||||
@InjectRepository(SessionSyncStateEntity) private repository: Repository<SessionSyncStateEntity>,
|
||||
) {}
|
||||
|
||||
get(sessionId: string): Promise<SessionSyncStateEntity | null> {
|
||||
return this.repository.findOneBy({ sessionId });
|
||||
}
|
||||
|
||||
async upsert(state: Partial<SessionSyncStateEntity>): Promise<void> {
|
||||
await this.repository.upsert(state, { conflictPaths: ['sessionId'] });
|
||||
}
|
||||
|
||||
getAssets({ checkpoint, userId, ...options }: AssetPartnerSyncOptions): Paginated<AssetEntity> {
|
||||
return paginate(this.assetRepository, options, {
|
||||
where: withCheckpoint<AssetEntity>(
|
||||
{
|
||||
ownerId: userId,
|
||||
isVisible: true,
|
||||
},
|
||||
'updatedAt',
|
||||
checkpoint,
|
||||
),
|
||||
relations: {
|
||||
exifInfo: true,
|
||||
},
|
||||
order: {
|
||||
updatedAt: 'ASC',
|
||||
id: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getDeletedAssets(): Paginated<DeletedEntity<EntityPK>> {
|
||||
return Promise.resolve({ items: [], hasNextPage: false });
|
||||
}
|
||||
|
||||
getAssetsPartner({ checkpoint, partnerIds, ...options }: AssetPartnerSyncOptions): Paginated<AssetEntity> {
|
||||
return paginate(this.assetRepository, options, {
|
||||
where: withCheckpoint<AssetEntity>({ ownerId: In(partnerIds) }, 'updatedAt', checkpoint),
|
||||
order: {
|
||||
updatedAt: 'ASC',
|
||||
id: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getDeletedAssetsPartner(): Paginated<DeletedEntity<EntityPK>> {
|
||||
return Promise.resolve({ items: [], hasNextPage: false });
|
||||
}
|
||||
|
||||
getAlbums({ checkpoint, userId, ...options }: SyncOptions): Paginated<AlbumEntity> {
|
||||
return paginate(this.albumRepository, options, {
|
||||
where: withCheckpoint<AlbumEntity>({ ownerId: userId }, 'updatedAt', checkpoint),
|
||||
order: {
|
||||
updatedAt: 'ASC',
|
||||
id: 'ASC',
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
getDeletedAlbums(): Paginated<DeletedEntity<EntityPK>> {
|
||||
return Promise.resolve({ items: [], hasNextPage: false });
|
||||
}
|
||||
|
||||
getAlbumAssets(): Paginated<AlbumAssetEntity> {
|
||||
return Promise.resolve({ items: [], hasNextPage: false });
|
||||
}
|
||||
|
||||
getDeletedAlbumAssets(): Paginated<DeletedEntity<AlbumAssetPK>> {
|
||||
return Promise.resolve({ items: [], hasNextPage: false });
|
||||
}
|
||||
}
|
|
@ -31,6 +31,7 @@ import { ISessionRepository } from 'src/interfaces/session.interface';
|
|||
import { ISharedLinkRepository } from 'src/interfaces/shared-link.interface';
|
||||
import { IStackRepository } from 'src/interfaces/stack.interface';
|
||||
import { IStorageRepository } from 'src/interfaces/storage.interface';
|
||||
import { ISyncRepository } from 'src/interfaces/sync.interface';
|
||||
import { ISystemMetadataRepository } from 'src/interfaces/system-metadata.interface';
|
||||
import { ITagRepository } from 'src/interfaces/tag.interface';
|
||||
import { ITelemetryRepository } from 'src/interfaces/telemetry.interface';
|
||||
|
@ -75,6 +76,7 @@ export class BaseService {
|
|||
@Inject(ISharedLinkRepository) protected sharedLinkRepository: ISharedLinkRepository,
|
||||
@Inject(IStackRepository) protected stackRepository: IStackRepository,
|
||||
@Inject(IStorageRepository) protected storageRepository: IStorageRepository,
|
||||
@Inject(ISyncRepository) protected syncRepository: ISyncRepository,
|
||||
@Inject(ISystemMetadataRepository) protected systemMetadataRepository: ISystemMetadataRepository,
|
||||
@Inject(ITagRepository) protected tagRepository: ITagRepository,
|
||||
@Inject(ITelemetryRepository) protected telemetryRepository: ITelemetryRepository,
|
||||
|
|
|
@ -1,16 +1,184 @@
|
|||
import { ForbiddenException } from '@nestjs/common';
|
||||
import { DateTime } from 'luxon';
|
||||
import { Writable } from 'node:stream';
|
||||
import { AUDIT_LOG_MAX_DURATION } from 'src/constants';
|
||||
import { AlbumAssetResponseDto } from 'src/dtos/album-asset.dto';
|
||||
import { AlbumResponseDto, mapAlbumWithoutAssets } from 'src/dtos/album.dto';
|
||||
import { AssetResponseDto, mapAsset } from 'src/dtos/asset-response.dto';
|
||||
import { AuthDto } from 'src/dtos/auth.dto';
|
||||
import { AssetDeltaSyncDto, AssetDeltaSyncResponseDto, AssetFullSyncDto } from 'src/dtos/sync.dto';
|
||||
import { DatabaseAction, EntityType, Permission } from 'src/enum';
|
||||
import {
|
||||
AssetDeltaSyncDto,
|
||||
AssetDeltaSyncResponseDto,
|
||||
AssetFullSyncDto,
|
||||
SyncAcknowledgeDto,
|
||||
SyncStreamDto,
|
||||
} from 'src/dtos/sync.dto';
|
||||
import { AlbumEntity } from 'src/entities/album.entity';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { SyncCheckpoint } from 'src/entities/session-sync-state.entity';
|
||||
import { DatabaseAction, EntityType, Permission, SyncAction, SyncEntity as SyncEntityType } from 'src/enum';
|
||||
import { AlbumAssetEntity, DeletedEntity, SyncOptions } from 'src/interfaces/sync.interface';
|
||||
import { BaseService } from 'src/services/base.service';
|
||||
import { getMyPartnerIds } from 'src/utils/asset.util';
|
||||
import { Paginated, usePagination } from 'src/utils/pagination';
|
||||
import { setIsEqual } from 'src/utils/set';
|
||||
|
||||
const FULL_SYNC = { needsFullSync: true, deleted: [], upserted: [] };
|
||||
const SYNC_PAGE_SIZE = 5000;
|
||||
|
||||
const asJsonLine = (item: unknown) => JSON.stringify(item) + '\n';
|
||||
|
||||
type Loader<T> = (options: SyncOptions) => Paginated<T>;
|
||||
type Mapper<T, R> = (item: T) => R;
|
||||
type StreamerArgs<T, R> = {
|
||||
type: SyncEntityType;
|
||||
action: SyncAction;
|
||||
lastAck?: string;
|
||||
load: Loader<T>;
|
||||
map?: Mapper<T, R>;
|
||||
ack: Mapper<T, SyncCheckpoint>;
|
||||
};
|
||||
|
||||
class Streamer<T = any, R = any> {
|
||||
constructor(private args: StreamerArgs<T, R>) {}
|
||||
|
||||
getEntityType() {
|
||||
return this.args.type;
|
||||
}
|
||||
|
||||
async write({ stream, userId, checkpoint }: { stream: Writable; userId: string; checkpoint?: SyncCheckpoint }) {
|
||||
const { type, action, load, map, ack } = this.args;
|
||||
const pagination = usePagination(SYNC_PAGE_SIZE, (options) => load({ ...options, userId, checkpoint }));
|
||||
for await (const items of pagination) {
|
||||
for (const item of items) {
|
||||
stream.write(asJsonLine({ type, action, data: map?.(item) || (item as unknown as R), ack: ack(item) }));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class SyncService extends BaseService {
|
||||
async acknowledge(auth: AuthDto, dto: SyncAcknowledgeDto) {
|
||||
const { id: sessionId } = this.assertSession(auth);
|
||||
await this.syncRepository.upsert({
|
||||
...dto,
|
||||
sessionId,
|
||||
});
|
||||
}
|
||||
|
||||
async stream(auth: AuthDto, stream: Writable, dto: SyncStreamDto) {
|
||||
const { id: sessionId, userId } = this.assertSession(auth);
|
||||
const syncState = await this.syncRepository.get(sessionId);
|
||||
const state = syncState?.state;
|
||||
const checkpoints: Record<SyncEntityType, SyncCheckpoint | undefined> = {
|
||||
[SyncEntityType.ACTIVITY]: state?.activity,
|
||||
[SyncEntityType.ASSET]: state?.asset,
|
||||
[SyncEntityType.ASSET_ALBUM]: state?.assetAlbum,
|
||||
[SyncEntityType.ASSET_PARTNER]: state?.assetPartner,
|
||||
[SyncEntityType.ALBUM]: state?.album,
|
||||
[SyncEntityType.ALBUM_ASSET]: state?.albumAsset,
|
||||
[SyncEntityType.ALBUM_USER]: state?.albumUser,
|
||||
[SyncEntityType.MEMORY]: state?.memory,
|
||||
[SyncEntityType.PARTNER]: state?.partner,
|
||||
[SyncEntityType.PERSON]: state?.partner,
|
||||
[SyncEntityType.SHARED_LINK]: state?.sharedLink,
|
||||
[SyncEntityType.STACK]: state?.stack,
|
||||
[SyncEntityType.TAG]: state?.tag,
|
||||
[SyncEntityType.USER]: state?.user,
|
||||
};
|
||||
const streamers: Streamer[] = [];
|
||||
|
||||
for (const type of dto.types) {
|
||||
switch (type) {
|
||||
case SyncEntityType.ASSET: {
|
||||
streamers.push(
|
||||
new Streamer<AssetEntity, AssetResponseDto>({
|
||||
type: SyncEntityType.ASSET,
|
||||
action: SyncAction.UPSERT,
|
||||
load: (options) => this.syncRepository.getAssets(options),
|
||||
map: (item) => mapAsset(item, { auth, stripMetadata: false }),
|
||||
ack: (item) => ({ id: item.id, timestamp: item.updatedAt.toISOString() }),
|
||||
}),
|
||||
new Streamer<DeletedEntity, DeletedEntity>({
|
||||
type: SyncEntityType.ASSET,
|
||||
action: SyncAction.DELETE,
|
||||
load: (options) => this.syncRepository.getDeletedAssets(options),
|
||||
map: (entity) => entity,
|
||||
ack: (item) => ({ id: item.id, timestamp: item.deletedAt.toISOString() }),
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncEntityType.ASSET_PARTNER: {
|
||||
const partnerIds = await getMyPartnerIds({ userId, repository: this.partnerRepository });
|
||||
streamers.push(
|
||||
new Streamer<AssetEntity, AssetResponseDto>({
|
||||
type: SyncEntityType.ASSET_PARTNER,
|
||||
action: SyncAction.UPSERT,
|
||||
load: (options) => this.syncRepository.getAssetsPartner({ ...options, partnerIds }),
|
||||
map: (item) => mapAsset(item, { auth, stripMetadata: false }),
|
||||
ack: (item) => ({ id: item.id, timestamp: item.updatedAt.toISOString() }),
|
||||
}),
|
||||
new Streamer<DeletedEntity, DeletedEntity>({
|
||||
type: SyncEntityType.ASSET_PARTNER,
|
||||
action: SyncAction.DELETE,
|
||||
load: (options) => this.syncRepository.getDeletedAssetsPartner({ ...options, partnerIds }),
|
||||
ack: (item) => ({ id: item.id, timestamp: item.deletedAt.toISOString() }),
|
||||
}),
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncEntityType.ALBUM: {
|
||||
streamers.push(
|
||||
new Streamer<AlbumEntity, AlbumResponseDto>({
|
||||
type: SyncEntityType.ALBUM,
|
||||
action: SyncAction.UPSERT,
|
||||
load: (options) => this.syncRepository.getAlbums(options),
|
||||
map: (item) => mapAlbumWithoutAssets(item),
|
||||
ack: (item) => ({ id: item.id, timestamp: item.updatedAt.toISOString() }),
|
||||
}),
|
||||
new Streamer<DeletedEntity, DeletedEntity>({
|
||||
type: SyncEntityType.ALBUM,
|
||||
action: SyncAction.DELETE,
|
||||
load: (options) => this.syncRepository.getDeletedAlbums(options),
|
||||
ack: (item) => ({ id: item.id, timestamp: item.deletedAt.toISOString() }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
case SyncEntityType.ALBUM_ASSET: {
|
||||
streamers.push(
|
||||
new Streamer<AlbumAssetEntity, AlbumAssetResponseDto>({
|
||||
type: SyncEntityType.ALBUM_ASSET,
|
||||
action: SyncAction.UPSERT,
|
||||
load: (options) => this.syncRepository.getAlbumAssets(options),
|
||||
ack: (item) => ({ id: item.assetId, timestamp: item.createdAt.toISOString() }),
|
||||
}),
|
||||
new Streamer<DeletedEntity, DeletedEntity>({
|
||||
type: SyncEntityType.ALBUM_ASSET,
|
||||
action: SyncAction.DELETE,
|
||||
load: (options) => this.syncRepository.getDeletedAlbums(options),
|
||||
ack: (item) => ({ id: item.id, timestamp: item.deletedAt.toISOString() }),
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
default: {
|
||||
this.logger.warn(`Unsupported sync type: ${type}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const streamer of streamers) {
|
||||
await streamer.write({ stream, userId, checkpoint: checkpoints[streamer.getEntityType()] });
|
||||
}
|
||||
|
||||
stream.end();
|
||||
}
|
||||
|
||||
async getFullSync(auth: AuthDto, dto: AssetFullSyncDto): Promise<AssetResponseDto[]> {
|
||||
// mobile implementation is faster if this is a single id
|
||||
const userId = dto.userId || auth.user.id;
|
||||
|
@ -71,4 +239,12 @@ export class SyncService extends BaseService {
|
|||
};
|
||||
return result;
|
||||
}
|
||||
|
||||
private assertSession(auth: AuthDto) {
|
||||
if (!auth.session?.id) {
|
||||
throw new ForbiddenException('This endpoint requires session-based authentication');
|
||||
}
|
||||
|
||||
return auth.session;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -12,6 +12,7 @@ import { writeFileSync } from 'node:fs';
|
|||
import path from 'node:path';
|
||||
import { SystemConfig } from 'src/config';
|
||||
import { CLIP_MODEL_INFO, serverVersion } from 'src/constants';
|
||||
import { AlbumAssetResponseDto } from 'src/dtos/album-asset.dto';
|
||||
import { ImmichCookie, ImmichHeader, MetadataKey } from 'src/enum';
|
||||
import { ILoggerRepository } from 'src/interfaces/logger.interface';
|
||||
|
||||
|
@ -219,6 +220,7 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean })
|
|||
|
||||
const options: SwaggerDocumentOptions = {
|
||||
operationIdFactory: (controllerKey: string, methodKey: string) => methodKey,
|
||||
extraModels: [AlbumAssetResponseDto],
|
||||
};
|
||||
|
||||
const specification = SwaggerModule.createDocument(app, config, options);
|
||||
|
|
13
server/test/repositories/sync.repository.mock.ts
Normal file
13
server/test/repositories/sync.repository.mock.ts
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { ISyncRepository } from 'src/interfaces/sync.interface';
|
||||
import { Mocked, vitest } from 'vitest';
|
||||
|
||||
export const newSyncRepositoryMock = (): Mocked<ISyncRepository> => {
|
||||
return {
|
||||
get: vitest.fn(),
|
||||
upsert: vitest.fn(),
|
||||
|
||||
getAssets: vitest.fn(),
|
||||
getAlbums: vitest.fn(),
|
||||
getAlbumAssets: vitest.fn(),
|
||||
};
|
||||
};
|
|
@ -31,6 +31,7 @@ import { newSessionRepositoryMock } from 'test/repositories/session.repository.m
|
|||
import { newSharedLinkRepositoryMock } from 'test/repositories/shared-link.repository.mock';
|
||||
import { newStackRepositoryMock } from 'test/repositories/stack.repository.mock';
|
||||
import { newStorageRepositoryMock } from 'test/repositories/storage.repository.mock';
|
||||
import { newSyncRepositoryMock } from 'test/repositories/sync.repository.mock';
|
||||
import { newSystemMetadataRepositoryMock } from 'test/repositories/system-metadata.repository.mock';
|
||||
import { newTagRepositoryMock } from 'test/repositories/tag.repository.mock';
|
||||
import { newTelemetryRepositoryMock } from 'test/repositories/telemetry.repository.mock';
|
||||
|
@ -84,6 +85,7 @@ export const newTestService = <T extends BaseService>(
|
|||
const sharedLinkMock = newSharedLinkRepositoryMock();
|
||||
const stackMock = newStackRepositoryMock();
|
||||
const storageMock = newStorageRepositoryMock();
|
||||
const syncMock = newSyncRepositoryMock();
|
||||
const systemMock = newSystemMetadataRepositoryMock();
|
||||
const tagMock = newTagRepositoryMock();
|
||||
const telemetryMock = newTelemetryRepositoryMock();
|
||||
|
@ -123,6 +125,7 @@ export const newTestService = <T extends BaseService>(
|
|||
sharedLinkMock,
|
||||
stackMock,
|
||||
storageMock,
|
||||
syncMock,
|
||||
systemMock,
|
||||
tagMock,
|
||||
telemetryMock,
|
||||
|
@ -164,6 +167,7 @@ export const newTestService = <T extends BaseService>(
|
|||
sharedLinkMock,
|
||||
stackMock,
|
||||
storageMock,
|
||||
syncMock,
|
||||
systemMock,
|
||||
tagMock,
|
||||
telemetryMock,
|
||||
|
|
|
@ -29,6 +29,34 @@
|
|||
|
||||
$: if ($user) {
|
||||
openWebsocketConnection();
|
||||
|
||||
void fetch('/api/sync/stream', {
|
||||
method: 'POST',
|
||||
body: JSON.stringify({ types: ['asset'] }),
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
}).then(async (response) => {
|
||||
if (response.body) {
|
||||
const reader = response.body.getReader();
|
||||
const decoder = new TextDecoder();
|
||||
|
||||
let done = false;
|
||||
while (!done) {
|
||||
const chunk = await reader.read();
|
||||
done = chunk.done;
|
||||
const data = chunk.value;
|
||||
|
||||
if (data) {
|
||||
const parts = decoder.decode(data).split('\n');
|
||||
for (const part of parts) {
|
||||
if (!part.trim()) {
|
||||
continue;
|
||||
}
|
||||
console.log(JSON.parse(part));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
} else {
|
||||
closeWebsocketConnection();
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue