diff --git a/e2e/src/api/specs/sync.e2e-spec.ts b/e2e/src/api/specs/sync.e2e-spec.ts new file mode 100644 index 00000000000000..28661151fb9e3a --- /dev/null +++ b/e2e/src/api/specs/sync.e2e-spec.ts @@ -0,0 +1,85 @@ +import { LoginResponseDto, login, signUpAdmin } from '@immich/sdk'; +import { loginDto, signupDto } 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 require a valid timestamp', async () => { + const { status, body } = await request(app) + .post('/sync/acknowledge') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ + type: 'AssetOwner', + timestamp: '2024-10-31 25:25:25', + }); + expect(status).toBe(400); + expect(body).toEqual(errorDto.badRequest(['timestamp must be a valid ISO 8601 date string'])); + }); + + it('should work', async () => { + const { status } = await request(app) + .post('/sync/acknowledge') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ + type: 'AssetOwner', + 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 reject invalid 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 require at least one 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 accept a valid type', async () => { + const response = await request(app) + .post('/sync/stream') + .set('Authorization', `Bearer ${admin.accessToken}`) + .send({ types: ['AssetOwnerV1'] }); + expect(response.status).toBe(200); + expect(response.get('Content-Type')).toBe('application/jsonlines+json; charset=utf-8'); + expect(response.body).toEqual(''); + }); + }); +}); diff --git a/mobile/openapi/README.md b/mobile/openapi/README.md index 4cdb08ce9988a3..317138fe314282 100644 --- a/mobile/openapi/README.md +++ b/mobile/openapi/README.md @@ -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 | @@ -414,6 +416,21 @@ Class | Method | HTTP request | Description - [StackCreateDto](doc//StackCreateDto.md) - [StackResponseDto](doc//StackResponseDto.md) - [StackUpdateDto](doc//StackUpdateDto.md) + - [SyncAcknowledgeDto](doc//SyncAcknowledgeDto.md) + - [SyncActivityDeleteDtoV1](doc//SyncActivityDeleteDtoV1.md) + - [SyncAlbumAssetDeleteDtoV1](doc//SyncAlbumAssetDeleteDtoV1.md) + - [SyncAlbumAssetDtoV1](doc//SyncAlbumAssetDtoV1.md) + - [SyncAlbumDeleteV1](doc//SyncAlbumDeleteV1.md) + - [SyncAlbumDtoV1](doc//SyncAlbumDtoV1.md) + - [SyncAssetAlbumDeleteV1](doc//SyncAssetAlbumDeleteV1.md) + - [SyncAssetAlbumDtoV1](doc//SyncAssetAlbumDtoV1.md) + - [SyncAssetOwnerDeleteV1](doc//SyncAssetOwnerDeleteV1.md) + - [SyncAssetOwnerDtoV1](doc//SyncAssetOwnerDtoV1.md) + - [SyncAssetPartnerDeleteV1](doc//SyncAssetPartnerDeleteV1.md) + - [SyncAssetPartnerDtoV1](doc//SyncAssetPartnerDtoV1.md) + - [SyncMemoryDelete](doc//SyncMemoryDelete.md) + - [SyncMemoryDtoV1](doc//SyncMemoryDtoV1.md) + - [SyncStreamDto](doc//SyncStreamDto.md) - [SystemConfigBackupsDto](doc//SystemConfigBackupsDto.md) - [SystemConfigDto](doc//SystemConfigDto.md) - [SystemConfigFFmpegDto](doc//SystemConfigFFmpegDto.md) diff --git a/mobile/openapi/lib/api.dart b/mobile/openapi/lib/api.dart index b4c51c8e997e34..cb8515e138edb8 100644 --- a/mobile/openapi/lib/api.dart +++ b/mobile/openapi/lib/api.dart @@ -228,6 +228,21 @@ 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_activity_delete_dto_v1.dart'; +part 'model/sync_album_asset_delete_dto_v1.dart'; +part 'model/sync_album_asset_dto_v1.dart'; +part 'model/sync_album_delete_v1.dart'; +part 'model/sync_album_dto_v1.dart'; +part 'model/sync_asset_album_delete_v1.dart'; +part 'model/sync_asset_album_dto_v1.dart'; +part 'model/sync_asset_owner_delete_v1.dart'; +part 'model/sync_asset_owner_dto_v1.dart'; +part 'model/sync_asset_partner_delete_v1.dart'; +part 'model/sync_asset_partner_dto_v1.dart'; +part 'model/sync_memory_delete.dart'; +part 'model/sync_memory_dto_v1.dart'; +part 'model/sync_stream_dto.dart'; part 'model/system_config_backups_dto.dart'; part 'model/system_config_dto.dart'; part 'model/system_config_f_fmpeg_dto.dart'; diff --git a/mobile/openapi/lib/api/sync_api.dart b/mobile/openapi/lib/api/sync_api.dart index f94eb88081a156..a77f1130974d75 100644 --- a/mobile/openapi/lib/api/sync_api.dart +++ b/mobile/openapi/lib/api/sync_api.dart @@ -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 ackSyncWithHttpInfo(SyncAcknowledgeDto syncAcknowledgeDto,) async { + // ignore: prefer_const_declarations + final path = r'/sync/acknowledge'; + + // ignore: prefer_final_locals + Object? postBody = syncAcknowledgeDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [SyncAcknowledgeDto] syncAcknowledgeDto (required): + Future 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,43 @@ class SyncApi { } return null; } + + /// Performs an HTTP 'POST /sync/stream' operation and returns the [Response]. + /// Parameters: + /// + /// * [SyncStreamDto] syncStreamDto (required): + Future getSyncStreamWithHttpInfo(SyncStreamDto syncStreamDto,) async { + // ignore: prefer_const_declarations + final path = r'/sync/stream'; + + // ignore: prefer_final_locals + Object? postBody = syncStreamDto; + + final queryParams = []; + final headerParams = {}; + final formParams = {}; + + const contentTypes = ['application/json']; + + + return apiClient.invokeAPI( + path, + 'POST', + queryParams, + postBody, + headerParams, + formParams, + contentTypes.isEmpty ? null : contentTypes.first, + ); + } + + /// Parameters: + /// + /// * [SyncStreamDto] syncStreamDto (required): + Future getSyncStream(SyncStreamDto syncStreamDto,) async { + final response = await getSyncStreamWithHttpInfo(syncStreamDto,); + if (response.statusCode >= HttpStatus.badRequest) { + throw ApiException(response.statusCode, await _decodeBodyBytes(response)); + } + } } diff --git a/mobile/openapi/lib/api_client.dart b/mobile/openapi/lib/api_client.dart index b6ddf86e70dfc8..d32ba2cd3855cf 100644 --- a/mobile/openapi/lib/api_client.dart +++ b/mobile/openapi/lib/api_client.dart @@ -510,6 +510,36 @@ class ApiClient { return StackResponseDto.fromJson(value); case 'StackUpdateDto': return StackUpdateDto.fromJson(value); + case 'SyncAcknowledgeDto': + return SyncAcknowledgeDto.fromJson(value); + case 'SyncActivityDeleteDtoV1': + return SyncActivityDeleteDtoV1.fromJson(value); + case 'SyncAlbumAssetDeleteDtoV1': + return SyncAlbumAssetDeleteDtoV1.fromJson(value); + case 'SyncAlbumAssetDtoV1': + return SyncAlbumAssetDtoV1.fromJson(value); + case 'SyncAlbumDeleteV1': + return SyncAlbumDeleteV1.fromJson(value); + case 'SyncAlbumDtoV1': + return SyncAlbumDtoV1.fromJson(value); + case 'SyncAssetAlbumDeleteV1': + return SyncAssetAlbumDeleteV1.fromJson(value); + case 'SyncAssetAlbumDtoV1': + return SyncAssetAlbumDtoV1.fromJson(value); + case 'SyncAssetOwnerDeleteV1': + return SyncAssetOwnerDeleteV1.fromJson(value); + case 'SyncAssetOwnerDtoV1': + return SyncAssetOwnerDtoV1.fromJson(value); + case 'SyncAssetPartnerDeleteV1': + return SyncAssetPartnerDeleteV1.fromJson(value); + case 'SyncAssetPartnerDtoV1': + return SyncAssetPartnerDtoV1.fromJson(value); + case 'SyncMemoryDelete': + return SyncMemoryDelete.fromJson(value); + case 'SyncMemoryDtoV1': + return SyncMemoryDtoV1.fromJson(value); + case 'SyncStreamDto': + return SyncStreamDto.fromJson(value); case 'SystemConfigBackupsDto': return SystemConfigBackupsDto.fromJson(value); case 'SystemConfigDto': diff --git a/mobile/openapi/lib/model/sync_acknowledge_dto.dart b/mobile/openapi/lib/model/sync_acknowledge_dto.dart new file mode 100644 index 00000000000000..7f2c3e0c293750 --- /dev/null +++ b/mobile/openapi/lib/model/sync_acknowledge_dto.dart @@ -0,0 +1,259 @@ +// +// 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({ + required this.timestamp, + required this.type, + }); + + String timestamp; + + SyncAcknowledgeDtoTypeEnum type; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAcknowledgeDto && + other.timestamp == timestamp && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (timestamp.hashCode) + + (type.hashCode); + + @override + String toString() => 'SyncAcknowledgeDto[timestamp=$timestamp, type=$type]'; + + Map toJson() { + final json = {}; + json[r'timestamp'] = this.timestamp; + json[r'type'] = this.type; + 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(); + + return SyncAcknowledgeDto( + timestamp: mapValueOfType(json, r'timestamp')!, + type: SyncAcknowledgeDtoTypeEnum.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + 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 mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // 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> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + 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 = { + 'timestamp', + 'type', + }; +} + + +class SyncAcknowledgeDtoTypeEnum { + /// Instantiate a new enum with the provided [value]. + const SyncAcknowledgeDtoTypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const activity = SyncAcknowledgeDtoTypeEnum._(r'Activity'); + static const activityDelete = SyncAcknowledgeDtoTypeEnum._(r'ActivityDelete'); + static const assetOwner = SyncAcknowledgeDtoTypeEnum._(r'AssetOwner'); + static const assetOwnerDelete = SyncAcknowledgeDtoTypeEnum._(r'AssetOwnerDelete'); + static const assetPartner = SyncAcknowledgeDtoTypeEnum._(r'AssetPartner'); + static const assetPartnerDelete = SyncAcknowledgeDtoTypeEnum._(r'AssetPartnerDelete'); + static const assetAlbum = SyncAcknowledgeDtoTypeEnum._(r'AssetAlbum'); + static const assetAlbumDelete = SyncAcknowledgeDtoTypeEnum._(r'AssetAlbumDelete'); + static const album = SyncAcknowledgeDtoTypeEnum._(r'Album'); + static const albumDelete = SyncAcknowledgeDtoTypeEnum._(r'AlbumDelete'); + static const memory = SyncAcknowledgeDtoTypeEnum._(r'Memory'); + static const memoryDelete = SyncAcknowledgeDtoTypeEnum._(r'MemoryDelete'); + static const partner = SyncAcknowledgeDtoTypeEnum._(r'Partner'); + static const partnerDelete = SyncAcknowledgeDtoTypeEnum._(r'PartnerDelete'); + static const person = SyncAcknowledgeDtoTypeEnum._(r'Person'); + static const personDelete = SyncAcknowledgeDtoTypeEnum._(r'PersonDelete'); + static const sharedLink = SyncAcknowledgeDtoTypeEnum._(r'SharedLink'); + static const sharedLinkDelete = SyncAcknowledgeDtoTypeEnum._(r'SharedLinkDelete'); + static const stack = SyncAcknowledgeDtoTypeEnum._(r'Stack'); + static const stackDelete = SyncAcknowledgeDtoTypeEnum._(r'StackDelete'); + static const tag = SyncAcknowledgeDtoTypeEnum._(r'Tag'); + static const tagDelete = SyncAcknowledgeDtoTypeEnum._(r'TagDelete'); + static const user = SyncAcknowledgeDtoTypeEnum._(r'User'); + static const userDelete = SyncAcknowledgeDtoTypeEnum._(r'UserDelete'); + static const albumAsset = SyncAcknowledgeDtoTypeEnum._(r'AlbumAsset'); + static const albumAssetDelete = SyncAcknowledgeDtoTypeEnum._(r'AlbumAssetDelete'); + static const albumUser = SyncAcknowledgeDtoTypeEnum._(r'AlbumUser'); + static const albumUserDelete = SyncAcknowledgeDtoTypeEnum._(r'AlbumUserDelete'); + + /// List of all possible values in this [enum][SyncAcknowledgeDtoTypeEnum]. + static const values = [ + activity, + activityDelete, + assetOwner, + assetOwnerDelete, + assetPartner, + assetPartnerDelete, + assetAlbum, + assetAlbumDelete, + album, + albumDelete, + memory, + memoryDelete, + partner, + partnerDelete, + person, + personDelete, + sharedLink, + sharedLinkDelete, + stack, + stackDelete, + tag, + tagDelete, + user, + userDelete, + albumAsset, + albumAssetDelete, + albumUser, + albumUserDelete, + ]; + + static SyncAcknowledgeDtoTypeEnum? fromJson(dynamic value) => SyncAcknowledgeDtoTypeEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAcknowledgeDtoTypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncAcknowledgeDtoTypeEnum] to String, +/// and [decode] dynamic data back to [SyncAcknowledgeDtoTypeEnum]. +class SyncAcknowledgeDtoTypeEnumTypeTransformer { + factory SyncAcknowledgeDtoTypeEnumTypeTransformer() => _instance ??= const SyncAcknowledgeDtoTypeEnumTypeTransformer._(); + + const SyncAcknowledgeDtoTypeEnumTypeTransformer._(); + + String encode(SyncAcknowledgeDtoTypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncAcknowledgeDtoTypeEnum. + /// + /// 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. + SyncAcknowledgeDtoTypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'Activity': return SyncAcknowledgeDtoTypeEnum.activity; + case r'ActivityDelete': return SyncAcknowledgeDtoTypeEnum.activityDelete; + case r'AssetOwner': return SyncAcknowledgeDtoTypeEnum.assetOwner; + case r'AssetOwnerDelete': return SyncAcknowledgeDtoTypeEnum.assetOwnerDelete; + case r'AssetPartner': return SyncAcknowledgeDtoTypeEnum.assetPartner; + case r'AssetPartnerDelete': return SyncAcknowledgeDtoTypeEnum.assetPartnerDelete; + case r'AssetAlbum': return SyncAcknowledgeDtoTypeEnum.assetAlbum; + case r'AssetAlbumDelete': return SyncAcknowledgeDtoTypeEnum.assetAlbumDelete; + case r'Album': return SyncAcknowledgeDtoTypeEnum.album; + case r'AlbumDelete': return SyncAcknowledgeDtoTypeEnum.albumDelete; + case r'Memory': return SyncAcknowledgeDtoTypeEnum.memory; + case r'MemoryDelete': return SyncAcknowledgeDtoTypeEnum.memoryDelete; + case r'Partner': return SyncAcknowledgeDtoTypeEnum.partner; + case r'PartnerDelete': return SyncAcknowledgeDtoTypeEnum.partnerDelete; + case r'Person': return SyncAcknowledgeDtoTypeEnum.person; + case r'PersonDelete': return SyncAcknowledgeDtoTypeEnum.personDelete; + case r'SharedLink': return SyncAcknowledgeDtoTypeEnum.sharedLink; + case r'SharedLinkDelete': return SyncAcknowledgeDtoTypeEnum.sharedLinkDelete; + case r'Stack': return SyncAcknowledgeDtoTypeEnum.stack; + case r'StackDelete': return SyncAcknowledgeDtoTypeEnum.stackDelete; + case r'Tag': return SyncAcknowledgeDtoTypeEnum.tag; + case r'TagDelete': return SyncAcknowledgeDtoTypeEnum.tagDelete; + case r'User': return SyncAcknowledgeDtoTypeEnum.user; + case r'UserDelete': return SyncAcknowledgeDtoTypeEnum.userDelete; + case r'AlbumAsset': return SyncAcknowledgeDtoTypeEnum.albumAsset; + case r'AlbumAssetDelete': return SyncAcknowledgeDtoTypeEnum.albumAssetDelete; + case r'AlbumUser': return SyncAcknowledgeDtoTypeEnum.albumUser; + case r'AlbumUserDelete': return SyncAcknowledgeDtoTypeEnum.albumUserDelete; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncAcknowledgeDtoTypeEnumTypeTransformer] instance. + static SyncAcknowledgeDtoTypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/sync_activity_delete_dto_v1.dart b/mobile/openapi/lib/model/sync_activity_delete_dto_v1.dart new file mode 100644 index 00000000000000..4da042cb98a64d --- /dev/null +++ b/mobile/openapi/lib/model/sync_activity_delete_dto_v1.dart @@ -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 SyncActivityDeleteDtoV1 { + /// Returns a new [SyncActivityDeleteDtoV1] instance. + SyncActivityDeleteDtoV1({ + required this.deletedAt, + required this.id, + }); + + String deletedAt; + + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncActivityDeleteDtoV1 && + other.deletedAt == deletedAt && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deletedAt.hashCode) + + (id.hashCode); + + @override + String toString() => 'SyncActivityDeleteDtoV1[deletedAt=$deletedAt, id=$id]'; + + Map toJson() { + final json = {}; + json[r'deletedAt'] = this.deletedAt; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [SyncActivityDeleteDtoV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncActivityDeleteDtoV1? fromJson(dynamic value) { + upgradeDto(value, "SyncActivityDeleteDtoV1"); + if (value is Map) { + final json = value.cast(); + + return SyncActivityDeleteDtoV1( + deletedAt: mapValueOfType(json, r'deletedAt')!, + id: mapValueOfType(json, r'id')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncActivityDeleteDtoV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncActivityDeleteDtoV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncActivityDeleteDtoV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncActivityDeleteDtoV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deletedAt', + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_asset_delete_dto_v1.dart b/mobile/openapi/lib/model/sync_album_asset_delete_dto_v1.dart new file mode 100644 index 00000000000000..e4fe2dd07a03e4 --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_asset_delete_dto_v1.dart @@ -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 SyncAlbumAssetDeleteDtoV1 { + /// Returns a new [SyncAlbumAssetDeleteDtoV1] instance. + SyncAlbumAssetDeleteDtoV1({ + required this.albumId, + required this.assetId, + required this.deletedAt, + }); + + String albumId; + + String assetId; + + String deletedAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumAssetDeleteDtoV1 && + other.albumId == albumId && + other.assetId == assetId && + other.deletedAt == deletedAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId.hashCode) + + (assetId.hashCode) + + (deletedAt.hashCode); + + @override + String toString() => 'SyncAlbumAssetDeleteDtoV1[albumId=$albumId, assetId=$assetId, deletedAt=$deletedAt]'; + + Map toJson() { + final json = {}; + json[r'albumId'] = this.albumId; + json[r'assetId'] = this.assetId; + json[r'deletedAt'] = this.deletedAt; + return json; + } + + /// Returns a new [SyncAlbumAssetDeleteDtoV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumAssetDeleteDtoV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumAssetDeleteDtoV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumAssetDeleteDtoV1( + albumId: mapValueOfType(json, r'albumId')!, + assetId: mapValueOfType(json, r'assetId')!, + deletedAt: mapValueOfType(json, r'deletedAt')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumAssetDeleteDtoV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumAssetDeleteDtoV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumAssetDeleteDtoV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumAssetDeleteDtoV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumId', + 'assetId', + 'deletedAt', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_asset_dto_v1.dart b/mobile/openapi/lib/model/sync_album_asset_dto_v1.dart new file mode 100644 index 00000000000000..5d1fdbc7982e63 --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_asset_dto_v1.dart @@ -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 SyncAlbumAssetDtoV1 { + /// Returns a new [SyncAlbumAssetDtoV1] instance. + SyncAlbumAssetDtoV1({ + required this.albumId, + required this.assetId, + required this.createdAt, + }); + + String albumId; + + String assetId; + + String createdAt; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumAssetDtoV1 && + other.albumId == albumId && + other.assetId == assetId && + other.createdAt == createdAt; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (albumId.hashCode) + + (assetId.hashCode) + + (createdAt.hashCode); + + @override + String toString() => 'SyncAlbumAssetDtoV1[albumId=$albumId, assetId=$assetId, createdAt=$createdAt]'; + + Map toJson() { + final json = {}; + json[r'albumId'] = this.albumId; + json[r'assetId'] = this.assetId; + json[r'createdAt'] = this.createdAt; + return json; + } + + /// Returns a new [SyncAlbumAssetDtoV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumAssetDtoV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumAssetDtoV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumAssetDtoV1( + albumId: mapValueOfType(json, r'albumId')!, + assetId: mapValueOfType(json, r'assetId')!, + createdAt: mapValueOfType(json, r'createdAt')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumAssetDtoV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumAssetDtoV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumAssetDtoV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumAssetDtoV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'albumId', + 'assetId', + 'createdAt', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_delete_v1.dart b/mobile/openapi/lib/model/sync_album_delete_v1.dart new file mode 100644 index 00000000000000..cd62c3d34050a6 --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_delete_v1.dart @@ -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 SyncAlbumDeleteV1 { + /// Returns a new [SyncAlbumDeleteV1] instance. + SyncAlbumDeleteV1({ + required this.deletedAt, + required this.id, + }); + + String deletedAt; + + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumDeleteV1 && + other.deletedAt == deletedAt && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deletedAt.hashCode) + + (id.hashCode); + + @override + String toString() => 'SyncAlbumDeleteV1[deletedAt=$deletedAt, id=$id]'; + + Map toJson() { + final json = {}; + json[r'deletedAt'] = this.deletedAt; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [SyncAlbumDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumDeleteV1( + deletedAt: mapValueOfType(json, r'deletedAt')!, + id: mapValueOfType(json, r'id')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deletedAt', + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/sync_album_dto_v1.dart b/mobile/openapi/lib/model/sync_album_dto_v1.dart new file mode 100644 index 00000000000000..eb040d7cd2ef98 --- /dev/null +++ b/mobile/openapi/lib/model/sync_album_dto_v1.dart @@ -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 SyncAlbumDtoV1 { + /// Returns a new [SyncAlbumDtoV1] instance. + SyncAlbumDtoV1({ + required this.description, + required this.id, + required this.name, + }); + + String description; + + String id; + + String name; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAlbumDtoV1 && + other.description == description && + other.id == id && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (id.hashCode) + + (name.hashCode); + + @override + String toString() => 'SyncAlbumDtoV1[description=$description, id=$id, name=$name]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'id'] = this.id; + json[r'name'] = this.name; + return json; + } + + /// Returns a new [SyncAlbumDtoV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAlbumDtoV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAlbumDtoV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAlbumDtoV1( + description: mapValueOfType(json, r'description')!, + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAlbumDtoV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAlbumDtoV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAlbumDtoV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAlbumDtoV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'id', + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_album_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_album_delete_v1.dart new file mode 100644 index 00000000000000..314ac957f83e50 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_album_delete_v1.dart @@ -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 SyncAssetAlbumDeleteV1 { + /// Returns a new [SyncAssetAlbumDeleteV1] instance. + SyncAssetAlbumDeleteV1({ + required this.deletedAt, + required this.id, + }); + + String deletedAt; + + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetAlbumDeleteV1 && + other.deletedAt == deletedAt && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deletedAt.hashCode) + + (id.hashCode); + + @override + String toString() => 'SyncAssetAlbumDeleteV1[deletedAt=$deletedAt, id=$id]'; + + Map toJson() { + final json = {}; + json[r'deletedAt'] = this.deletedAt; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [SyncAssetAlbumDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetAlbumDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetAlbumDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetAlbumDeleteV1( + deletedAt: mapValueOfType(json, r'deletedAt')!, + id: mapValueOfType(json, r'id')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetAlbumDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetAlbumDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetAlbumDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetAlbumDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deletedAt', + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_album_dto_v1.dart b/mobile/openapi/lib/model/sync_asset_album_dto_v1.dart new file mode 100644 index 00000000000000..efeb36aac35694 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_album_dto_v1.dart @@ -0,0 +1,836 @@ +// +// 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 SyncAssetAlbumDtoV1 { + /// Returns a new [SyncAssetAlbumDtoV1] instance. + SyncAssetAlbumDtoV1({ + this.bitsPerSample, + required this.checksum, + this.city, + this.colorspace, + this.country, + required this.createdAt, + this.dateTimeOriginal, + this.deletedAt, + this.duration, + this.exifImageHeight, + this.exifImageWidth, + this.exposureTime, + this.fNumber, + required this.fileCreatedAt, + required this.fileModifiedAt, + this.fileSizeInByte, + this.fps, + required this.id, + required this.isArchived, + required this.isExternal, + required this.isFavorite, + required this.isOffline, + this.iso, + this.latitude, + this.lensModel, + this.libraryId, + this.livePhotoVideoId, + this.longitude, + this.make, + this.model, + this.modifyDate, + this.orientation, + required this.originalFileName, + required this.originalPath, + required this.ownerId, + this.profileDescription, + this.projectionType, + this.rating, + this.state, + required this.status, + this.thumbhash, + required this.type, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? bitsPerSample; + + String checksum; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? city; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? colorspace; + + /// + /// 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? country; + + String createdAt; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? dateTimeOriginal; + + /// + /// 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? deletedAt; + + /// + /// 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? 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. + /// + num? exifImageHeight; + + /// + /// 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. + /// + num? exifImageWidth; + + /// + /// 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? exposureTime; + + /// + /// 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. + /// + num? fNumber; + + String fileCreatedAt; + + String fileModifiedAt; + + /// + /// 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. + /// + num? fileSizeInByte; + + /// + /// 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. + /// + num? fps; + + String id; + + bool isArchived; + + bool isExternal; + + bool isFavorite; + + bool isOffline; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? iso; + + /// + /// 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. + /// + num? latitude; + + /// + /// 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? lensModel; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? libraryId; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? livePhotoVideoId; + + /// + /// 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. + /// + num? longitude; + + /// + /// 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? make; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? model; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? modifyDate; + + /// + /// 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? orientation; + + String originalFileName; + + String originalPath; + + String ownerId; + + /// + /// 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? profileDescription; + + /// + /// 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? projectionType; + + /// + /// 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. + /// + num? rating; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? state; + + SyncAssetAlbumDtoV1StatusEnum status; + + /// + /// 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? thumbhash; + + SyncAssetAlbumDtoV1TypeEnum type; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetAlbumDtoV1 && + other.bitsPerSample == bitsPerSample && + other.checksum == checksum && + other.city == city && + other.colorspace == colorspace && + other.country == country && + other.createdAt == createdAt && + other.dateTimeOriginal == dateTimeOriginal && + other.deletedAt == deletedAt && + other.duration == duration && + other.exifImageHeight == exifImageHeight && + other.exifImageWidth == exifImageWidth && + other.exposureTime == exposureTime && + other.fNumber == fNumber && + other.fileCreatedAt == fileCreatedAt && + other.fileModifiedAt == fileModifiedAt && + other.fileSizeInByte == fileSizeInByte && + other.fps == fps && + other.id == id && + other.isArchived == isArchived && + other.isExternal == isExternal && + other.isFavorite == isFavorite && + other.isOffline == isOffline && + other.iso == iso && + other.latitude == latitude && + other.lensModel == lensModel && + other.libraryId == libraryId && + other.livePhotoVideoId == livePhotoVideoId && + other.longitude == longitude && + other.make == make && + other.model == model && + other.modifyDate == modifyDate && + other.orientation == orientation && + other.originalFileName == originalFileName && + other.originalPath == originalPath && + other.ownerId == ownerId && + other.profileDescription == profileDescription && + other.projectionType == projectionType && + other.rating == rating && + other.state == state && + other.status == status && + other.thumbhash == thumbhash && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (bitsPerSample == null ? 0 : bitsPerSample!.hashCode) + + (checksum.hashCode) + + (city == null ? 0 : city!.hashCode) + + (colorspace == null ? 0 : colorspace!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (createdAt.hashCode) + + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (duration == null ? 0 : duration!.hashCode) + + (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + + (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + + (exposureTime == null ? 0 : exposureTime!.hashCode) + + (fNumber == null ? 0 : fNumber!.hashCode) + + (fileCreatedAt.hashCode) + + (fileModifiedAt.hashCode) + + (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + + (fps == null ? 0 : fps!.hashCode) + + (id.hashCode) + + (isArchived.hashCode) + + (isExternal.hashCode) + + (isFavorite.hashCode) + + (isOffline.hashCode) + + (iso == null ? 0 : iso!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (modifyDate == null ? 0 : modifyDate!.hashCode) + + (orientation == null ? 0 : orientation!.hashCode) + + (originalFileName.hashCode) + + (originalPath.hashCode) + + (ownerId.hashCode) + + (profileDescription == null ? 0 : profileDescription!.hashCode) + + (projectionType == null ? 0 : projectionType!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (status.hashCode) + + (thumbhash == null ? 0 : thumbhash!.hashCode) + + (type.hashCode); + + @override + String toString() => 'SyncAssetAlbumDtoV1[bitsPerSample=$bitsPerSample, checksum=$checksum, city=$city, colorspace=$colorspace, country=$country, createdAt=$createdAt, dateTimeOriginal=$dateTimeOriginal, deletedAt=$deletedAt, duration=$duration, exifImageHeight=$exifImageHeight, exifImageWidth=$exifImageWidth, exposureTime=$exposureTime, fNumber=$fNumber, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, fileSizeInByte=$fileSizeInByte, fps=$fps, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, iso=$iso, latitude=$latitude, lensModel=$lensModel, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, make=$make, model=$model, modifyDate=$modifyDate, orientation=$orientation, originalFileName=$originalFileName, originalPath=$originalPath, ownerId=$ownerId, profileDescription=$profileDescription, projectionType=$projectionType, rating=$rating, state=$state, status=$status, thumbhash=$thumbhash, type=$type]'; + + Map toJson() { + final json = {}; + if (this.bitsPerSample != null) { + json[r'bitsPerSample'] = this.bitsPerSample; + } else { + // json[r'bitsPerSample'] = null; + } + json[r'checksum'] = this.checksum; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.colorspace != null) { + json[r'colorspace'] = this.colorspace; + } else { + // json[r'colorspace'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + json[r'createdAt'] = this.createdAt; + if (this.dateTimeOriginal != null) { + json[r'dateTimeOriginal'] = this.dateTimeOriginal; + } else { + // json[r'dateTimeOriginal'] = null; + } + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt; + } else { + // json[r'deletedAt'] = null; + } + if (this.duration != null) { + json[r'duration'] = this.duration; + } else { + // json[r'duration'] = null; + } + if (this.exifImageHeight != null) { + json[r'exifImageHeight'] = this.exifImageHeight; + } else { + // json[r'exifImageHeight'] = null; + } + if (this.exifImageWidth != null) { + json[r'exifImageWidth'] = this.exifImageWidth; + } else { + // json[r'exifImageWidth'] = null; + } + if (this.exposureTime != null) { + json[r'exposureTime'] = this.exposureTime; + } else { + // json[r'exposureTime'] = null; + } + if (this.fNumber != null) { + json[r'fNumber'] = this.fNumber; + } else { + // json[r'fNumber'] = null; + } + json[r'fileCreatedAt'] = this.fileCreatedAt; + json[r'fileModifiedAt'] = this.fileModifiedAt; + if (this.fileSizeInByte != null) { + json[r'fileSizeInByte'] = this.fileSizeInByte; + } else { + // json[r'fileSizeInByte'] = null; + } + if (this.fps != null) { + json[r'fps'] = this.fps; + } else { + // json[r'fps'] = null; + } + json[r'id'] = this.id; + json[r'isArchived'] = this.isArchived; + json[r'isExternal'] = this.isExternal; + json[r'isFavorite'] = this.isFavorite; + json[r'isOffline'] = this.isOffline; + if (this.iso != null) { + json[r'iso'] = this.iso; + } else { + // json[r'iso'] = null; + } + if (this.latitude != null) { + json[r'latitude'] = this.latitude; + } else { + // json[r'latitude'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + 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; + } + if (this.longitude != null) { + json[r'longitude'] = this.longitude; + } else { + // json[r'longitude'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.modifyDate != null) { + json[r'modifyDate'] = this.modifyDate; + } else { + // json[r'modifyDate'] = null; + } + if (this.orientation != null) { + json[r'orientation'] = this.orientation; + } else { + // json[r'orientation'] = null; + } + json[r'originalFileName'] = this.originalFileName; + json[r'originalPath'] = this.originalPath; + json[r'ownerId'] = this.ownerId; + if (this.profileDescription != null) { + json[r'profileDescription'] = this.profileDescription; + } else { + // json[r'profileDescription'] = null; + } + if (this.projectionType != null) { + json[r'projectionType'] = this.projectionType; + } else { + // json[r'projectionType'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + json[r'status'] = this.status; + if (this.thumbhash != null) { + json[r'thumbhash'] = this.thumbhash; + } else { + // json[r'thumbhash'] = null; + } + json[r'type'] = this.type; + return json; + } + + /// Returns a new [SyncAssetAlbumDtoV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetAlbumDtoV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetAlbumDtoV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetAlbumDtoV1( + bitsPerSample: num.parse('${json[r'bitsPerSample']}'), + checksum: mapValueOfType(json, r'checksum')!, + city: mapValueOfType(json, r'city'), + colorspace: mapValueOfType(json, r'colorspace'), + country: mapValueOfType(json, r'country'), + createdAt: mapValueOfType(json, r'createdAt')!, + dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), + deletedAt: mapValueOfType(json, r'deletedAt'), + duration: mapValueOfType(json, r'duration'), + exifImageHeight: num.parse('${json[r'exifImageHeight']}'), + exifImageWidth: num.parse('${json[r'exifImageWidth']}'), + exposureTime: mapValueOfType(json, r'exposureTime'), + fNumber: num.parse('${json[r'fNumber']}'), + fileCreatedAt: mapValueOfType(json, r'fileCreatedAt')!, + fileModifiedAt: mapValueOfType(json, r'fileModifiedAt')!, + fileSizeInByte: num.parse('${json[r'fileSizeInByte']}'), + fps: num.parse('${json[r'fps']}'), + id: mapValueOfType(json, r'id')!, + isArchived: mapValueOfType(json, r'isArchived')!, + isExternal: mapValueOfType(json, r'isExternal')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + isOffline: mapValueOfType(json, r'isOffline')!, + iso: num.parse('${json[r'iso']}'), + latitude: num.parse('${json[r'latitude']}'), + lensModel: mapValueOfType(json, r'lensModel'), + libraryId: mapValueOfType(json, r'libraryId'), + livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + longitude: num.parse('${json[r'longitude']}'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + modifyDate: mapValueOfType(json, r'modifyDate'), + orientation: mapValueOfType(json, r'orientation'), + originalFileName: mapValueOfType(json, r'originalFileName')!, + originalPath: mapValueOfType(json, r'originalPath')!, + ownerId: mapValueOfType(json, r'ownerId')!, + profileDescription: mapValueOfType(json, r'profileDescription'), + projectionType: mapValueOfType(json, r'projectionType'), + rating: num.parse('${json[r'rating']}'), + state: mapValueOfType(json, r'state'), + status: SyncAssetAlbumDtoV1StatusEnum.fromJson(json[r'status'])!, + thumbhash: mapValueOfType(json, r'thumbhash'), + type: SyncAssetAlbumDtoV1TypeEnum.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetAlbumDtoV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetAlbumDtoV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetAlbumDtoV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetAlbumDtoV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum', + 'createdAt', + 'fileCreatedAt', + 'fileModifiedAt', + 'id', + 'isArchived', + 'isExternal', + 'isFavorite', + 'isOffline', + 'originalFileName', + 'originalPath', + 'ownerId', + 'status', + 'type', + }; +} + + +class SyncAssetAlbumDtoV1StatusEnum { + /// Instantiate a new enum with the provided [value]. + const SyncAssetAlbumDtoV1StatusEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const active = SyncAssetAlbumDtoV1StatusEnum._(r'active'); + static const trashed = SyncAssetAlbumDtoV1StatusEnum._(r'trashed'); + static const deleted = SyncAssetAlbumDtoV1StatusEnum._(r'deleted'); + + /// List of all possible values in this [enum][SyncAssetAlbumDtoV1StatusEnum]. + static const values = [ + active, + trashed, + deleted, + ]; + + static SyncAssetAlbumDtoV1StatusEnum? fromJson(dynamic value) => SyncAssetAlbumDtoV1StatusEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetAlbumDtoV1StatusEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncAssetAlbumDtoV1StatusEnum] to String, +/// and [decode] dynamic data back to [SyncAssetAlbumDtoV1StatusEnum]. +class SyncAssetAlbumDtoV1StatusEnumTypeTransformer { + factory SyncAssetAlbumDtoV1StatusEnumTypeTransformer() => _instance ??= const SyncAssetAlbumDtoV1StatusEnumTypeTransformer._(); + + const SyncAssetAlbumDtoV1StatusEnumTypeTransformer._(); + + String encode(SyncAssetAlbumDtoV1StatusEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncAssetAlbumDtoV1StatusEnum. + /// + /// 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. + SyncAssetAlbumDtoV1StatusEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'active': return SyncAssetAlbumDtoV1StatusEnum.active; + case r'trashed': return SyncAssetAlbumDtoV1StatusEnum.trashed; + case r'deleted': return SyncAssetAlbumDtoV1StatusEnum.deleted; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncAssetAlbumDtoV1StatusEnumTypeTransformer] instance. + static SyncAssetAlbumDtoV1StatusEnumTypeTransformer? _instance; +} + + + +class SyncAssetAlbumDtoV1TypeEnum { + /// Instantiate a new enum with the provided [value]. + const SyncAssetAlbumDtoV1TypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const IMAGE = SyncAssetAlbumDtoV1TypeEnum._(r'IMAGE'); + static const VIDEO = SyncAssetAlbumDtoV1TypeEnum._(r'VIDEO'); + static const AUDIO = SyncAssetAlbumDtoV1TypeEnum._(r'AUDIO'); + static const OTHER = SyncAssetAlbumDtoV1TypeEnum._(r'OTHER'); + + /// List of all possible values in this [enum][SyncAssetAlbumDtoV1TypeEnum]. + static const values = [ + IMAGE, + VIDEO, + AUDIO, + OTHER, + ]; + + static SyncAssetAlbumDtoV1TypeEnum? fromJson(dynamic value) => SyncAssetAlbumDtoV1TypeEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetAlbumDtoV1TypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncAssetAlbumDtoV1TypeEnum] to String, +/// and [decode] dynamic data back to [SyncAssetAlbumDtoV1TypeEnum]. +class SyncAssetAlbumDtoV1TypeEnumTypeTransformer { + factory SyncAssetAlbumDtoV1TypeEnumTypeTransformer() => _instance ??= const SyncAssetAlbumDtoV1TypeEnumTypeTransformer._(); + + const SyncAssetAlbumDtoV1TypeEnumTypeTransformer._(); + + String encode(SyncAssetAlbumDtoV1TypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncAssetAlbumDtoV1TypeEnum. + /// + /// 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. + SyncAssetAlbumDtoV1TypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'IMAGE': return SyncAssetAlbumDtoV1TypeEnum.IMAGE; + case r'VIDEO': return SyncAssetAlbumDtoV1TypeEnum.VIDEO; + case r'AUDIO': return SyncAssetAlbumDtoV1TypeEnum.AUDIO; + case r'OTHER': return SyncAssetAlbumDtoV1TypeEnum.OTHER; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncAssetAlbumDtoV1TypeEnumTypeTransformer] instance. + static SyncAssetAlbumDtoV1TypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/sync_asset_owner_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_owner_delete_v1.dart new file mode 100644 index 00000000000000..69c85c4a8b5abe --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_owner_delete_v1.dart @@ -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 SyncAssetOwnerDeleteV1 { + /// Returns a new [SyncAssetOwnerDeleteV1] instance. + SyncAssetOwnerDeleteV1({ + required this.deletedAt, + required this.id, + }); + + String deletedAt; + + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetOwnerDeleteV1 && + other.deletedAt == deletedAt && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deletedAt.hashCode) + + (id.hashCode); + + @override + String toString() => 'SyncAssetOwnerDeleteV1[deletedAt=$deletedAt, id=$id]'; + + Map toJson() { + final json = {}; + json[r'deletedAt'] = this.deletedAt; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [SyncAssetOwnerDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetOwnerDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetOwnerDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetOwnerDeleteV1( + deletedAt: mapValueOfType(json, r'deletedAt')!, + id: mapValueOfType(json, r'id')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetOwnerDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetOwnerDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetOwnerDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetOwnerDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deletedAt', + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_owner_dto_v1.dart b/mobile/openapi/lib/model/sync_asset_owner_dto_v1.dart new file mode 100644 index 00000000000000..c369006ec10604 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_owner_dto_v1.dart @@ -0,0 +1,836 @@ +// +// 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 SyncAssetOwnerDtoV1 { + /// Returns a new [SyncAssetOwnerDtoV1] instance. + SyncAssetOwnerDtoV1({ + this.bitsPerSample, + required this.checksum, + this.city, + this.colorspace, + this.country, + required this.createdAt, + this.dateTimeOriginal, + this.deletedAt, + this.duration, + this.exifImageHeight, + this.exifImageWidth, + this.exposureTime, + this.fNumber, + required this.fileCreatedAt, + required this.fileModifiedAt, + this.fileSizeInByte, + this.fps, + required this.id, + required this.isArchived, + required this.isExternal, + required this.isFavorite, + required this.isOffline, + this.iso, + this.latitude, + this.lensModel, + this.libraryId, + this.livePhotoVideoId, + this.longitude, + this.make, + this.model, + this.modifyDate, + this.orientation, + required this.originalFileName, + required this.originalPath, + required this.ownerId, + this.profileDescription, + this.projectionType, + this.rating, + this.state, + required this.status, + this.thumbhash, + required this.type, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? bitsPerSample; + + String checksum; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? city; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? colorspace; + + /// + /// 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? country; + + String createdAt; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? dateTimeOriginal; + + /// + /// 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? deletedAt; + + /// + /// 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? 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. + /// + num? exifImageHeight; + + /// + /// 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. + /// + num? exifImageWidth; + + /// + /// 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? exposureTime; + + /// + /// 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. + /// + num? fNumber; + + String fileCreatedAt; + + String fileModifiedAt; + + /// + /// 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. + /// + num? fileSizeInByte; + + /// + /// 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. + /// + num? fps; + + String id; + + bool isArchived; + + bool isExternal; + + bool isFavorite; + + bool isOffline; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? iso; + + /// + /// 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. + /// + num? latitude; + + /// + /// 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? lensModel; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? libraryId; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? livePhotoVideoId; + + /// + /// 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. + /// + num? longitude; + + /// + /// 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? make; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? model; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? modifyDate; + + /// + /// 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? orientation; + + String originalFileName; + + String originalPath; + + String ownerId; + + /// + /// 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? profileDescription; + + /// + /// 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? projectionType; + + /// + /// 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. + /// + num? rating; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? state; + + SyncAssetOwnerDtoV1StatusEnum status; + + /// + /// 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? thumbhash; + + SyncAssetOwnerDtoV1TypeEnum type; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetOwnerDtoV1 && + other.bitsPerSample == bitsPerSample && + other.checksum == checksum && + other.city == city && + other.colorspace == colorspace && + other.country == country && + other.createdAt == createdAt && + other.dateTimeOriginal == dateTimeOriginal && + other.deletedAt == deletedAt && + other.duration == duration && + other.exifImageHeight == exifImageHeight && + other.exifImageWidth == exifImageWidth && + other.exposureTime == exposureTime && + other.fNumber == fNumber && + other.fileCreatedAt == fileCreatedAt && + other.fileModifiedAt == fileModifiedAt && + other.fileSizeInByte == fileSizeInByte && + other.fps == fps && + other.id == id && + other.isArchived == isArchived && + other.isExternal == isExternal && + other.isFavorite == isFavorite && + other.isOffline == isOffline && + other.iso == iso && + other.latitude == latitude && + other.lensModel == lensModel && + other.libraryId == libraryId && + other.livePhotoVideoId == livePhotoVideoId && + other.longitude == longitude && + other.make == make && + other.model == model && + other.modifyDate == modifyDate && + other.orientation == orientation && + other.originalFileName == originalFileName && + other.originalPath == originalPath && + other.ownerId == ownerId && + other.profileDescription == profileDescription && + other.projectionType == projectionType && + other.rating == rating && + other.state == state && + other.status == status && + other.thumbhash == thumbhash && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (bitsPerSample == null ? 0 : bitsPerSample!.hashCode) + + (checksum.hashCode) + + (city == null ? 0 : city!.hashCode) + + (colorspace == null ? 0 : colorspace!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (createdAt.hashCode) + + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (duration == null ? 0 : duration!.hashCode) + + (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + + (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + + (exposureTime == null ? 0 : exposureTime!.hashCode) + + (fNumber == null ? 0 : fNumber!.hashCode) + + (fileCreatedAt.hashCode) + + (fileModifiedAt.hashCode) + + (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + + (fps == null ? 0 : fps!.hashCode) + + (id.hashCode) + + (isArchived.hashCode) + + (isExternal.hashCode) + + (isFavorite.hashCode) + + (isOffline.hashCode) + + (iso == null ? 0 : iso!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (modifyDate == null ? 0 : modifyDate!.hashCode) + + (orientation == null ? 0 : orientation!.hashCode) + + (originalFileName.hashCode) + + (originalPath.hashCode) + + (ownerId.hashCode) + + (profileDescription == null ? 0 : profileDescription!.hashCode) + + (projectionType == null ? 0 : projectionType!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (status.hashCode) + + (thumbhash == null ? 0 : thumbhash!.hashCode) + + (type.hashCode); + + @override + String toString() => 'SyncAssetOwnerDtoV1[bitsPerSample=$bitsPerSample, checksum=$checksum, city=$city, colorspace=$colorspace, country=$country, createdAt=$createdAt, dateTimeOriginal=$dateTimeOriginal, deletedAt=$deletedAt, duration=$duration, exifImageHeight=$exifImageHeight, exifImageWidth=$exifImageWidth, exposureTime=$exposureTime, fNumber=$fNumber, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, fileSizeInByte=$fileSizeInByte, fps=$fps, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, iso=$iso, latitude=$latitude, lensModel=$lensModel, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, make=$make, model=$model, modifyDate=$modifyDate, orientation=$orientation, originalFileName=$originalFileName, originalPath=$originalPath, ownerId=$ownerId, profileDescription=$profileDescription, projectionType=$projectionType, rating=$rating, state=$state, status=$status, thumbhash=$thumbhash, type=$type]'; + + Map toJson() { + final json = {}; + if (this.bitsPerSample != null) { + json[r'bitsPerSample'] = this.bitsPerSample; + } else { + // json[r'bitsPerSample'] = null; + } + json[r'checksum'] = this.checksum; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.colorspace != null) { + json[r'colorspace'] = this.colorspace; + } else { + // json[r'colorspace'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + json[r'createdAt'] = this.createdAt; + if (this.dateTimeOriginal != null) { + json[r'dateTimeOriginal'] = this.dateTimeOriginal; + } else { + // json[r'dateTimeOriginal'] = null; + } + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt; + } else { + // json[r'deletedAt'] = null; + } + if (this.duration != null) { + json[r'duration'] = this.duration; + } else { + // json[r'duration'] = null; + } + if (this.exifImageHeight != null) { + json[r'exifImageHeight'] = this.exifImageHeight; + } else { + // json[r'exifImageHeight'] = null; + } + if (this.exifImageWidth != null) { + json[r'exifImageWidth'] = this.exifImageWidth; + } else { + // json[r'exifImageWidth'] = null; + } + if (this.exposureTime != null) { + json[r'exposureTime'] = this.exposureTime; + } else { + // json[r'exposureTime'] = null; + } + if (this.fNumber != null) { + json[r'fNumber'] = this.fNumber; + } else { + // json[r'fNumber'] = null; + } + json[r'fileCreatedAt'] = this.fileCreatedAt; + json[r'fileModifiedAt'] = this.fileModifiedAt; + if (this.fileSizeInByte != null) { + json[r'fileSizeInByte'] = this.fileSizeInByte; + } else { + // json[r'fileSizeInByte'] = null; + } + if (this.fps != null) { + json[r'fps'] = this.fps; + } else { + // json[r'fps'] = null; + } + json[r'id'] = this.id; + json[r'isArchived'] = this.isArchived; + json[r'isExternal'] = this.isExternal; + json[r'isFavorite'] = this.isFavorite; + json[r'isOffline'] = this.isOffline; + if (this.iso != null) { + json[r'iso'] = this.iso; + } else { + // json[r'iso'] = null; + } + if (this.latitude != null) { + json[r'latitude'] = this.latitude; + } else { + // json[r'latitude'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + 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; + } + if (this.longitude != null) { + json[r'longitude'] = this.longitude; + } else { + // json[r'longitude'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.modifyDate != null) { + json[r'modifyDate'] = this.modifyDate; + } else { + // json[r'modifyDate'] = null; + } + if (this.orientation != null) { + json[r'orientation'] = this.orientation; + } else { + // json[r'orientation'] = null; + } + json[r'originalFileName'] = this.originalFileName; + json[r'originalPath'] = this.originalPath; + json[r'ownerId'] = this.ownerId; + if (this.profileDescription != null) { + json[r'profileDescription'] = this.profileDescription; + } else { + // json[r'profileDescription'] = null; + } + if (this.projectionType != null) { + json[r'projectionType'] = this.projectionType; + } else { + // json[r'projectionType'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + json[r'status'] = this.status; + if (this.thumbhash != null) { + json[r'thumbhash'] = this.thumbhash; + } else { + // json[r'thumbhash'] = null; + } + json[r'type'] = this.type; + return json; + } + + /// Returns a new [SyncAssetOwnerDtoV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetOwnerDtoV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetOwnerDtoV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetOwnerDtoV1( + bitsPerSample: num.parse('${json[r'bitsPerSample']}'), + checksum: mapValueOfType(json, r'checksum')!, + city: mapValueOfType(json, r'city'), + colorspace: mapValueOfType(json, r'colorspace'), + country: mapValueOfType(json, r'country'), + createdAt: mapValueOfType(json, r'createdAt')!, + dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), + deletedAt: mapValueOfType(json, r'deletedAt'), + duration: mapValueOfType(json, r'duration'), + exifImageHeight: num.parse('${json[r'exifImageHeight']}'), + exifImageWidth: num.parse('${json[r'exifImageWidth']}'), + exposureTime: mapValueOfType(json, r'exposureTime'), + fNumber: num.parse('${json[r'fNumber']}'), + fileCreatedAt: mapValueOfType(json, r'fileCreatedAt')!, + fileModifiedAt: mapValueOfType(json, r'fileModifiedAt')!, + fileSizeInByte: num.parse('${json[r'fileSizeInByte']}'), + fps: num.parse('${json[r'fps']}'), + id: mapValueOfType(json, r'id')!, + isArchived: mapValueOfType(json, r'isArchived')!, + isExternal: mapValueOfType(json, r'isExternal')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + isOffline: mapValueOfType(json, r'isOffline')!, + iso: num.parse('${json[r'iso']}'), + latitude: num.parse('${json[r'latitude']}'), + lensModel: mapValueOfType(json, r'lensModel'), + libraryId: mapValueOfType(json, r'libraryId'), + livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + longitude: num.parse('${json[r'longitude']}'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + modifyDate: mapValueOfType(json, r'modifyDate'), + orientation: mapValueOfType(json, r'orientation'), + originalFileName: mapValueOfType(json, r'originalFileName')!, + originalPath: mapValueOfType(json, r'originalPath')!, + ownerId: mapValueOfType(json, r'ownerId')!, + profileDescription: mapValueOfType(json, r'profileDescription'), + projectionType: mapValueOfType(json, r'projectionType'), + rating: num.parse('${json[r'rating']}'), + state: mapValueOfType(json, r'state'), + status: SyncAssetOwnerDtoV1StatusEnum.fromJson(json[r'status'])!, + thumbhash: mapValueOfType(json, r'thumbhash'), + type: SyncAssetOwnerDtoV1TypeEnum.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetOwnerDtoV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetOwnerDtoV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetOwnerDtoV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetOwnerDtoV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum', + 'createdAt', + 'fileCreatedAt', + 'fileModifiedAt', + 'id', + 'isArchived', + 'isExternal', + 'isFavorite', + 'isOffline', + 'originalFileName', + 'originalPath', + 'ownerId', + 'status', + 'type', + }; +} + + +class SyncAssetOwnerDtoV1StatusEnum { + /// Instantiate a new enum with the provided [value]. + const SyncAssetOwnerDtoV1StatusEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const active = SyncAssetOwnerDtoV1StatusEnum._(r'active'); + static const trashed = SyncAssetOwnerDtoV1StatusEnum._(r'trashed'); + static const deleted = SyncAssetOwnerDtoV1StatusEnum._(r'deleted'); + + /// List of all possible values in this [enum][SyncAssetOwnerDtoV1StatusEnum]. + static const values = [ + active, + trashed, + deleted, + ]; + + static SyncAssetOwnerDtoV1StatusEnum? fromJson(dynamic value) => SyncAssetOwnerDtoV1StatusEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetOwnerDtoV1StatusEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncAssetOwnerDtoV1StatusEnum] to String, +/// and [decode] dynamic data back to [SyncAssetOwnerDtoV1StatusEnum]. +class SyncAssetOwnerDtoV1StatusEnumTypeTransformer { + factory SyncAssetOwnerDtoV1StatusEnumTypeTransformer() => _instance ??= const SyncAssetOwnerDtoV1StatusEnumTypeTransformer._(); + + const SyncAssetOwnerDtoV1StatusEnumTypeTransformer._(); + + String encode(SyncAssetOwnerDtoV1StatusEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncAssetOwnerDtoV1StatusEnum. + /// + /// 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. + SyncAssetOwnerDtoV1StatusEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'active': return SyncAssetOwnerDtoV1StatusEnum.active; + case r'trashed': return SyncAssetOwnerDtoV1StatusEnum.trashed; + case r'deleted': return SyncAssetOwnerDtoV1StatusEnum.deleted; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncAssetOwnerDtoV1StatusEnumTypeTransformer] instance. + static SyncAssetOwnerDtoV1StatusEnumTypeTransformer? _instance; +} + + + +class SyncAssetOwnerDtoV1TypeEnum { + /// Instantiate a new enum with the provided [value]. + const SyncAssetOwnerDtoV1TypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const IMAGE = SyncAssetOwnerDtoV1TypeEnum._(r'IMAGE'); + static const VIDEO = SyncAssetOwnerDtoV1TypeEnum._(r'VIDEO'); + static const AUDIO = SyncAssetOwnerDtoV1TypeEnum._(r'AUDIO'); + static const OTHER = SyncAssetOwnerDtoV1TypeEnum._(r'OTHER'); + + /// List of all possible values in this [enum][SyncAssetOwnerDtoV1TypeEnum]. + static const values = [ + IMAGE, + VIDEO, + AUDIO, + OTHER, + ]; + + static SyncAssetOwnerDtoV1TypeEnum? fromJson(dynamic value) => SyncAssetOwnerDtoV1TypeEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetOwnerDtoV1TypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncAssetOwnerDtoV1TypeEnum] to String, +/// and [decode] dynamic data back to [SyncAssetOwnerDtoV1TypeEnum]. +class SyncAssetOwnerDtoV1TypeEnumTypeTransformer { + factory SyncAssetOwnerDtoV1TypeEnumTypeTransformer() => _instance ??= const SyncAssetOwnerDtoV1TypeEnumTypeTransformer._(); + + const SyncAssetOwnerDtoV1TypeEnumTypeTransformer._(); + + String encode(SyncAssetOwnerDtoV1TypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncAssetOwnerDtoV1TypeEnum. + /// + /// 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. + SyncAssetOwnerDtoV1TypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'IMAGE': return SyncAssetOwnerDtoV1TypeEnum.IMAGE; + case r'VIDEO': return SyncAssetOwnerDtoV1TypeEnum.VIDEO; + case r'AUDIO': return SyncAssetOwnerDtoV1TypeEnum.AUDIO; + case r'OTHER': return SyncAssetOwnerDtoV1TypeEnum.OTHER; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncAssetOwnerDtoV1TypeEnumTypeTransformer] instance. + static SyncAssetOwnerDtoV1TypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/sync_asset_partner_delete_v1.dart b/mobile/openapi/lib/model/sync_asset_partner_delete_v1.dart new file mode 100644 index 00000000000000..acf2da588d0d95 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_partner_delete_v1.dart @@ -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 SyncAssetPartnerDeleteV1 { + /// Returns a new [SyncAssetPartnerDeleteV1] instance. + SyncAssetPartnerDeleteV1({ + required this.deletedAt, + required this.id, + }); + + String deletedAt; + + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetPartnerDeleteV1 && + other.deletedAt == deletedAt && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deletedAt.hashCode) + + (id.hashCode); + + @override + String toString() => 'SyncAssetPartnerDeleteV1[deletedAt=$deletedAt, id=$id]'; + + Map toJson() { + final json = {}; + json[r'deletedAt'] = this.deletedAt; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [SyncAssetPartnerDeleteV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetPartnerDeleteV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetPartnerDeleteV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetPartnerDeleteV1( + deletedAt: mapValueOfType(json, r'deletedAt')!, + id: mapValueOfType(json, r'id')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetPartnerDeleteV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetPartnerDeleteV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetPartnerDeleteV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetPartnerDeleteV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deletedAt', + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/sync_asset_partner_dto_v1.dart b/mobile/openapi/lib/model/sync_asset_partner_dto_v1.dart new file mode 100644 index 00000000000000..c09fb8c000ae63 --- /dev/null +++ b/mobile/openapi/lib/model/sync_asset_partner_dto_v1.dart @@ -0,0 +1,836 @@ +// +// 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 SyncAssetPartnerDtoV1 { + /// Returns a new [SyncAssetPartnerDtoV1] instance. + SyncAssetPartnerDtoV1({ + this.bitsPerSample, + required this.checksum, + this.city, + this.colorspace, + this.country, + required this.createdAt, + this.dateTimeOriginal, + this.deletedAt, + this.duration, + this.exifImageHeight, + this.exifImageWidth, + this.exposureTime, + this.fNumber, + required this.fileCreatedAt, + required this.fileModifiedAt, + this.fileSizeInByte, + this.fps, + required this.id, + required this.isArchived, + required this.isExternal, + required this.isFavorite, + required this.isOffline, + this.iso, + this.latitude, + this.lensModel, + this.libraryId, + this.livePhotoVideoId, + this.longitude, + this.make, + this.model, + this.modifyDate, + this.orientation, + required this.originalFileName, + required this.originalPath, + required this.ownerId, + this.profileDescription, + this.projectionType, + this.rating, + this.state, + required this.status, + this.thumbhash, + required this.type, + }); + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? bitsPerSample; + + String checksum; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? city; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? colorspace; + + /// + /// 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? country; + + String createdAt; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? dateTimeOriginal; + + /// + /// 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? deletedAt; + + /// + /// 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? 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. + /// + num? exifImageHeight; + + /// + /// 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. + /// + num? exifImageWidth; + + /// + /// 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? exposureTime; + + /// + /// 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. + /// + num? fNumber; + + String fileCreatedAt; + + String fileModifiedAt; + + /// + /// 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. + /// + num? fileSizeInByte; + + /// + /// 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. + /// + num? fps; + + String id; + + bool isArchived; + + bool isExternal; + + bool isFavorite; + + bool isOffline; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + num? iso; + + /// + /// 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. + /// + num? latitude; + + /// + /// 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? lensModel; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? libraryId; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? livePhotoVideoId; + + /// + /// 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. + /// + num? longitude; + + /// + /// 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? make; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? model; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? modifyDate; + + /// + /// 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? orientation; + + String originalFileName; + + String originalPath; + + String ownerId; + + /// + /// 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? profileDescription; + + /// + /// 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? projectionType; + + /// + /// 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. + /// + num? rating; + + /// + /// Please note: This property should have been non-nullable! Since the specification file + /// does not include a default value (using the "default:" property), however, the generated + /// source code must fall back to having a nullable type. + /// Consider adding a "default:" property in the specification file to hide this note. + /// + String? state; + + SyncAssetPartnerDtoV1StatusEnum status; + + /// + /// 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? thumbhash; + + SyncAssetPartnerDtoV1TypeEnum type; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncAssetPartnerDtoV1 && + other.bitsPerSample == bitsPerSample && + other.checksum == checksum && + other.city == city && + other.colorspace == colorspace && + other.country == country && + other.createdAt == createdAt && + other.dateTimeOriginal == dateTimeOriginal && + other.deletedAt == deletedAt && + other.duration == duration && + other.exifImageHeight == exifImageHeight && + other.exifImageWidth == exifImageWidth && + other.exposureTime == exposureTime && + other.fNumber == fNumber && + other.fileCreatedAt == fileCreatedAt && + other.fileModifiedAt == fileModifiedAt && + other.fileSizeInByte == fileSizeInByte && + other.fps == fps && + other.id == id && + other.isArchived == isArchived && + other.isExternal == isExternal && + other.isFavorite == isFavorite && + other.isOffline == isOffline && + other.iso == iso && + other.latitude == latitude && + other.lensModel == lensModel && + other.libraryId == libraryId && + other.livePhotoVideoId == livePhotoVideoId && + other.longitude == longitude && + other.make == make && + other.model == model && + other.modifyDate == modifyDate && + other.orientation == orientation && + other.originalFileName == originalFileName && + other.originalPath == originalPath && + other.ownerId == ownerId && + other.profileDescription == profileDescription && + other.projectionType == projectionType && + other.rating == rating && + other.state == state && + other.status == status && + other.thumbhash == thumbhash && + other.type == type; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (bitsPerSample == null ? 0 : bitsPerSample!.hashCode) + + (checksum.hashCode) + + (city == null ? 0 : city!.hashCode) + + (colorspace == null ? 0 : colorspace!.hashCode) + + (country == null ? 0 : country!.hashCode) + + (createdAt.hashCode) + + (dateTimeOriginal == null ? 0 : dateTimeOriginal!.hashCode) + + (deletedAt == null ? 0 : deletedAt!.hashCode) + + (duration == null ? 0 : duration!.hashCode) + + (exifImageHeight == null ? 0 : exifImageHeight!.hashCode) + + (exifImageWidth == null ? 0 : exifImageWidth!.hashCode) + + (exposureTime == null ? 0 : exposureTime!.hashCode) + + (fNumber == null ? 0 : fNumber!.hashCode) + + (fileCreatedAt.hashCode) + + (fileModifiedAt.hashCode) + + (fileSizeInByte == null ? 0 : fileSizeInByte!.hashCode) + + (fps == null ? 0 : fps!.hashCode) + + (id.hashCode) + + (isArchived.hashCode) + + (isExternal.hashCode) + + (isFavorite.hashCode) + + (isOffline.hashCode) + + (iso == null ? 0 : iso!.hashCode) + + (latitude == null ? 0 : latitude!.hashCode) + + (lensModel == null ? 0 : lensModel!.hashCode) + + (libraryId == null ? 0 : libraryId!.hashCode) + + (livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) + + (longitude == null ? 0 : longitude!.hashCode) + + (make == null ? 0 : make!.hashCode) + + (model == null ? 0 : model!.hashCode) + + (modifyDate == null ? 0 : modifyDate!.hashCode) + + (orientation == null ? 0 : orientation!.hashCode) + + (originalFileName.hashCode) + + (originalPath.hashCode) + + (ownerId.hashCode) + + (profileDescription == null ? 0 : profileDescription!.hashCode) + + (projectionType == null ? 0 : projectionType!.hashCode) + + (rating == null ? 0 : rating!.hashCode) + + (state == null ? 0 : state!.hashCode) + + (status.hashCode) + + (thumbhash == null ? 0 : thumbhash!.hashCode) + + (type.hashCode); + + @override + String toString() => 'SyncAssetPartnerDtoV1[bitsPerSample=$bitsPerSample, checksum=$checksum, city=$city, colorspace=$colorspace, country=$country, createdAt=$createdAt, dateTimeOriginal=$dateTimeOriginal, deletedAt=$deletedAt, duration=$duration, exifImageHeight=$exifImageHeight, exifImageWidth=$exifImageWidth, exposureTime=$exposureTime, fNumber=$fNumber, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, fileSizeInByte=$fileSizeInByte, fps=$fps, id=$id, isArchived=$isArchived, isExternal=$isExternal, isFavorite=$isFavorite, isOffline=$isOffline, iso=$iso, latitude=$latitude, lensModel=$lensModel, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, longitude=$longitude, make=$make, model=$model, modifyDate=$modifyDate, orientation=$orientation, originalFileName=$originalFileName, originalPath=$originalPath, ownerId=$ownerId, profileDescription=$profileDescription, projectionType=$projectionType, rating=$rating, state=$state, status=$status, thumbhash=$thumbhash, type=$type]'; + + Map toJson() { + final json = {}; + if (this.bitsPerSample != null) { + json[r'bitsPerSample'] = this.bitsPerSample; + } else { + // json[r'bitsPerSample'] = null; + } + json[r'checksum'] = this.checksum; + if (this.city != null) { + json[r'city'] = this.city; + } else { + // json[r'city'] = null; + } + if (this.colorspace != null) { + json[r'colorspace'] = this.colorspace; + } else { + // json[r'colorspace'] = null; + } + if (this.country != null) { + json[r'country'] = this.country; + } else { + // json[r'country'] = null; + } + json[r'createdAt'] = this.createdAt; + if (this.dateTimeOriginal != null) { + json[r'dateTimeOriginal'] = this.dateTimeOriginal; + } else { + // json[r'dateTimeOriginal'] = null; + } + if (this.deletedAt != null) { + json[r'deletedAt'] = this.deletedAt; + } else { + // json[r'deletedAt'] = null; + } + if (this.duration != null) { + json[r'duration'] = this.duration; + } else { + // json[r'duration'] = null; + } + if (this.exifImageHeight != null) { + json[r'exifImageHeight'] = this.exifImageHeight; + } else { + // json[r'exifImageHeight'] = null; + } + if (this.exifImageWidth != null) { + json[r'exifImageWidth'] = this.exifImageWidth; + } else { + // json[r'exifImageWidth'] = null; + } + if (this.exposureTime != null) { + json[r'exposureTime'] = this.exposureTime; + } else { + // json[r'exposureTime'] = null; + } + if (this.fNumber != null) { + json[r'fNumber'] = this.fNumber; + } else { + // json[r'fNumber'] = null; + } + json[r'fileCreatedAt'] = this.fileCreatedAt; + json[r'fileModifiedAt'] = this.fileModifiedAt; + if (this.fileSizeInByte != null) { + json[r'fileSizeInByte'] = this.fileSizeInByte; + } else { + // json[r'fileSizeInByte'] = null; + } + if (this.fps != null) { + json[r'fps'] = this.fps; + } else { + // json[r'fps'] = null; + } + json[r'id'] = this.id; + json[r'isArchived'] = this.isArchived; + json[r'isExternal'] = this.isExternal; + json[r'isFavorite'] = this.isFavorite; + json[r'isOffline'] = this.isOffline; + if (this.iso != null) { + json[r'iso'] = this.iso; + } else { + // json[r'iso'] = null; + } + if (this.latitude != null) { + json[r'latitude'] = this.latitude; + } else { + // json[r'latitude'] = null; + } + if (this.lensModel != null) { + json[r'lensModel'] = this.lensModel; + } else { + // json[r'lensModel'] = null; + } + 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; + } + if (this.longitude != null) { + json[r'longitude'] = this.longitude; + } else { + // json[r'longitude'] = null; + } + if (this.make != null) { + json[r'make'] = this.make; + } else { + // json[r'make'] = null; + } + if (this.model != null) { + json[r'model'] = this.model; + } else { + // json[r'model'] = null; + } + if (this.modifyDate != null) { + json[r'modifyDate'] = this.modifyDate; + } else { + // json[r'modifyDate'] = null; + } + if (this.orientation != null) { + json[r'orientation'] = this.orientation; + } else { + // json[r'orientation'] = null; + } + json[r'originalFileName'] = this.originalFileName; + json[r'originalPath'] = this.originalPath; + json[r'ownerId'] = this.ownerId; + if (this.profileDescription != null) { + json[r'profileDescription'] = this.profileDescription; + } else { + // json[r'profileDescription'] = null; + } + if (this.projectionType != null) { + json[r'projectionType'] = this.projectionType; + } else { + // json[r'projectionType'] = null; + } + if (this.rating != null) { + json[r'rating'] = this.rating; + } else { + // json[r'rating'] = null; + } + if (this.state != null) { + json[r'state'] = this.state; + } else { + // json[r'state'] = null; + } + json[r'status'] = this.status; + if (this.thumbhash != null) { + json[r'thumbhash'] = this.thumbhash; + } else { + // json[r'thumbhash'] = null; + } + json[r'type'] = this.type; + return json; + } + + /// Returns a new [SyncAssetPartnerDtoV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncAssetPartnerDtoV1? fromJson(dynamic value) { + upgradeDto(value, "SyncAssetPartnerDtoV1"); + if (value is Map) { + final json = value.cast(); + + return SyncAssetPartnerDtoV1( + bitsPerSample: num.parse('${json[r'bitsPerSample']}'), + checksum: mapValueOfType(json, r'checksum')!, + city: mapValueOfType(json, r'city'), + colorspace: mapValueOfType(json, r'colorspace'), + country: mapValueOfType(json, r'country'), + createdAt: mapValueOfType(json, r'createdAt')!, + dateTimeOriginal: mapValueOfType(json, r'dateTimeOriginal'), + deletedAt: mapValueOfType(json, r'deletedAt'), + duration: mapValueOfType(json, r'duration'), + exifImageHeight: num.parse('${json[r'exifImageHeight']}'), + exifImageWidth: num.parse('${json[r'exifImageWidth']}'), + exposureTime: mapValueOfType(json, r'exposureTime'), + fNumber: num.parse('${json[r'fNumber']}'), + fileCreatedAt: mapValueOfType(json, r'fileCreatedAt')!, + fileModifiedAt: mapValueOfType(json, r'fileModifiedAt')!, + fileSizeInByte: num.parse('${json[r'fileSizeInByte']}'), + fps: num.parse('${json[r'fps']}'), + id: mapValueOfType(json, r'id')!, + isArchived: mapValueOfType(json, r'isArchived')!, + isExternal: mapValueOfType(json, r'isExternal')!, + isFavorite: mapValueOfType(json, r'isFavorite')!, + isOffline: mapValueOfType(json, r'isOffline')!, + iso: num.parse('${json[r'iso']}'), + latitude: num.parse('${json[r'latitude']}'), + lensModel: mapValueOfType(json, r'lensModel'), + libraryId: mapValueOfType(json, r'libraryId'), + livePhotoVideoId: mapValueOfType(json, r'livePhotoVideoId'), + longitude: num.parse('${json[r'longitude']}'), + make: mapValueOfType(json, r'make'), + model: mapValueOfType(json, r'model'), + modifyDate: mapValueOfType(json, r'modifyDate'), + orientation: mapValueOfType(json, r'orientation'), + originalFileName: mapValueOfType(json, r'originalFileName')!, + originalPath: mapValueOfType(json, r'originalPath')!, + ownerId: mapValueOfType(json, r'ownerId')!, + profileDescription: mapValueOfType(json, r'profileDescription'), + projectionType: mapValueOfType(json, r'projectionType'), + rating: num.parse('${json[r'rating']}'), + state: mapValueOfType(json, r'state'), + status: SyncAssetPartnerDtoV1StatusEnum.fromJson(json[r'status'])!, + thumbhash: mapValueOfType(json, r'thumbhash'), + type: SyncAssetPartnerDtoV1TypeEnum.fromJson(json[r'type'])!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetPartnerDtoV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncAssetPartnerDtoV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncAssetPartnerDtoV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncAssetPartnerDtoV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'checksum', + 'createdAt', + 'fileCreatedAt', + 'fileModifiedAt', + 'id', + 'isArchived', + 'isExternal', + 'isFavorite', + 'isOffline', + 'originalFileName', + 'originalPath', + 'ownerId', + 'status', + 'type', + }; +} + + +class SyncAssetPartnerDtoV1StatusEnum { + /// Instantiate a new enum with the provided [value]. + const SyncAssetPartnerDtoV1StatusEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const active = SyncAssetPartnerDtoV1StatusEnum._(r'active'); + static const trashed = SyncAssetPartnerDtoV1StatusEnum._(r'trashed'); + static const deleted = SyncAssetPartnerDtoV1StatusEnum._(r'deleted'); + + /// List of all possible values in this [enum][SyncAssetPartnerDtoV1StatusEnum]. + static const values = [ + active, + trashed, + deleted, + ]; + + static SyncAssetPartnerDtoV1StatusEnum? fromJson(dynamic value) => SyncAssetPartnerDtoV1StatusEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetPartnerDtoV1StatusEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncAssetPartnerDtoV1StatusEnum] to String, +/// and [decode] dynamic data back to [SyncAssetPartnerDtoV1StatusEnum]. +class SyncAssetPartnerDtoV1StatusEnumTypeTransformer { + factory SyncAssetPartnerDtoV1StatusEnumTypeTransformer() => _instance ??= const SyncAssetPartnerDtoV1StatusEnumTypeTransformer._(); + + const SyncAssetPartnerDtoV1StatusEnumTypeTransformer._(); + + String encode(SyncAssetPartnerDtoV1StatusEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncAssetPartnerDtoV1StatusEnum. + /// + /// 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. + SyncAssetPartnerDtoV1StatusEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'active': return SyncAssetPartnerDtoV1StatusEnum.active; + case r'trashed': return SyncAssetPartnerDtoV1StatusEnum.trashed; + case r'deleted': return SyncAssetPartnerDtoV1StatusEnum.deleted; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncAssetPartnerDtoV1StatusEnumTypeTransformer] instance. + static SyncAssetPartnerDtoV1StatusEnumTypeTransformer? _instance; +} + + + +class SyncAssetPartnerDtoV1TypeEnum { + /// Instantiate a new enum with the provided [value]. + const SyncAssetPartnerDtoV1TypeEnum._(this.value); + + /// The underlying value of this enum member. + final String value; + + @override + String toString() => value; + + String toJson() => value; + + static const IMAGE = SyncAssetPartnerDtoV1TypeEnum._(r'IMAGE'); + static const VIDEO = SyncAssetPartnerDtoV1TypeEnum._(r'VIDEO'); + static const AUDIO = SyncAssetPartnerDtoV1TypeEnum._(r'AUDIO'); + static const OTHER = SyncAssetPartnerDtoV1TypeEnum._(r'OTHER'); + + /// List of all possible values in this [enum][SyncAssetPartnerDtoV1TypeEnum]. + static const values = [ + IMAGE, + VIDEO, + AUDIO, + OTHER, + ]; + + static SyncAssetPartnerDtoV1TypeEnum? fromJson(dynamic value) => SyncAssetPartnerDtoV1TypeEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncAssetPartnerDtoV1TypeEnum.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } +} + +/// Transformation class that can [encode] an instance of [SyncAssetPartnerDtoV1TypeEnum] to String, +/// and [decode] dynamic data back to [SyncAssetPartnerDtoV1TypeEnum]. +class SyncAssetPartnerDtoV1TypeEnumTypeTransformer { + factory SyncAssetPartnerDtoV1TypeEnumTypeTransformer() => _instance ??= const SyncAssetPartnerDtoV1TypeEnumTypeTransformer._(); + + const SyncAssetPartnerDtoV1TypeEnumTypeTransformer._(); + + String encode(SyncAssetPartnerDtoV1TypeEnum data) => data.value; + + /// Decodes a [dynamic value][data] to a SyncAssetPartnerDtoV1TypeEnum. + /// + /// 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. + SyncAssetPartnerDtoV1TypeEnum? decode(dynamic data, {bool allowNull = true}) { + if (data != null) { + switch (data) { + case r'IMAGE': return SyncAssetPartnerDtoV1TypeEnum.IMAGE; + case r'VIDEO': return SyncAssetPartnerDtoV1TypeEnum.VIDEO; + case r'AUDIO': return SyncAssetPartnerDtoV1TypeEnum.AUDIO; + case r'OTHER': return SyncAssetPartnerDtoV1TypeEnum.OTHER; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncAssetPartnerDtoV1TypeEnumTypeTransformer] instance. + static SyncAssetPartnerDtoV1TypeEnumTypeTransformer? _instance; +} + + diff --git a/mobile/openapi/lib/model/sync_memory_delete.dart b/mobile/openapi/lib/model/sync_memory_delete.dart new file mode 100644 index 00000000000000..c029b539855e2e --- /dev/null +++ b/mobile/openapi/lib/model/sync_memory_delete.dart @@ -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 SyncMemoryDelete { + /// Returns a new [SyncMemoryDelete] instance. + SyncMemoryDelete({ + required this.deletedAt, + required this.id, + }); + + String deletedAt; + + String id; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncMemoryDelete && + other.deletedAt == deletedAt && + other.id == id; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (deletedAt.hashCode) + + (id.hashCode); + + @override + String toString() => 'SyncMemoryDelete[deletedAt=$deletedAt, id=$id]'; + + Map toJson() { + final json = {}; + json[r'deletedAt'] = this.deletedAt; + json[r'id'] = this.id; + return json; + } + + /// Returns a new [SyncMemoryDelete] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncMemoryDelete? fromJson(dynamic value) { + upgradeDto(value, "SyncMemoryDelete"); + if (value is Map) { + final json = value.cast(); + + return SyncMemoryDelete( + deletedAt: mapValueOfType(json, r'deletedAt')!, + id: mapValueOfType(json, r'id')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncMemoryDelete.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncMemoryDelete.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncMemoryDelete-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncMemoryDelete.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'deletedAt', + 'id', + }; +} + diff --git a/mobile/openapi/lib/model/sync_memory_dto_v1.dart b/mobile/openapi/lib/model/sync_memory_dto_v1.dart new file mode 100644 index 00000000000000..baff17d6ef4fc3 --- /dev/null +++ b/mobile/openapi/lib/model/sync_memory_dto_v1.dart @@ -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 SyncMemoryDtoV1 { + /// Returns a new [SyncMemoryDtoV1] instance. + SyncMemoryDtoV1({ + required this.description, + required this.id, + required this.name, + }); + + String description; + + String id; + + String name; + + @override + bool operator ==(Object other) => identical(this, other) || other is SyncMemoryDtoV1 && + other.description == description && + other.id == id && + other.name == name; + + @override + int get hashCode => + // ignore: unnecessary_parenthesis + (description.hashCode) + + (id.hashCode) + + (name.hashCode); + + @override + String toString() => 'SyncMemoryDtoV1[description=$description, id=$id, name=$name]'; + + Map toJson() { + final json = {}; + json[r'description'] = this.description; + json[r'id'] = this.id; + json[r'name'] = this.name; + return json; + } + + /// Returns a new [SyncMemoryDtoV1] instance and imports its values from + /// [value] if it's a [Map], null otherwise. + // ignore: prefer_constructors_over_static_methods + static SyncMemoryDtoV1? fromJson(dynamic value) { + upgradeDto(value, "SyncMemoryDtoV1"); + if (value is Map) { + final json = value.cast(); + + return SyncMemoryDtoV1( + description: mapValueOfType(json, r'description')!, + id: mapValueOfType(json, r'id')!, + name: mapValueOfType(json, r'name')!, + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + if (json is List && json.isNotEmpty) { + for (final row in json) { + final value = SyncMemoryDtoV1.fromJson(row); + if (value != null) { + result.add(value); + } + } + } + return result.toList(growable: growable); + } + + static Map mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // ignore: parameter_assignments + for (final entry in json.entries) { + final value = SyncMemoryDtoV1.fromJson(entry.value); + if (value != null) { + map[entry.key] = value; + } + } + } + return map; + } + + // maps a json object with a list of SyncMemoryDtoV1-objects as value to a dart map + static Map> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + for (final entry in json.entries) { + map[entry.key] = SyncMemoryDtoV1.listFromJson(entry.value, growable: growable,); + } + } + return map; + } + + /// The list of required keys that must be present in a JSON. + static const requiredKeys = { + 'description', + 'id', + 'name', + }; +} + diff --git a/mobile/openapi/lib/model/sync_stream_dto.dart b/mobile/openapi/lib/model/sync_stream_dto.dart new file mode 100644 index 00000000000000..48b4b3cc0d0150 --- /dev/null +++ b/mobile/openapi/lib/model/sync_stream_dto.dart @@ -0,0 +1,251 @@ +// +// 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 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 toJson() { + final json = {}; + 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(); + + return SyncStreamDto( + types: SyncStreamDtoTypesEnum.listFromJson(json[r'types']), + ); + } + return null; + } + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + 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 mapFromJson(dynamic json) { + final map = {}; + if (json is Map && json.isNotEmpty) { + json = json.cast(); // 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> mapListFromJson(dynamic json, {bool growable = false,}) { + final map = >{}; + if (json is Map && json.isNotEmpty) { + // ignore: parameter_assignments + json = json.cast(); + 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 = { + '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 activityV1 = SyncStreamDtoTypesEnum._(r'ActivityV1'); + static const activityDeleteV1 = SyncStreamDtoTypesEnum._(r'ActivityDeleteV1'); + static const assetOwnerV1 = SyncStreamDtoTypesEnum._(r'AssetOwnerV1'); + static const assetOwnerDeleteV1 = SyncStreamDtoTypesEnum._(r'AssetOwnerDeleteV1'); + static const assetPartnerV1 = SyncStreamDtoTypesEnum._(r'AssetPartnerV1'); + static const assetPartnerDeleteV1 = SyncStreamDtoTypesEnum._(r'AssetPartnerDeleteV1'); + static const assetAlbumV1 = SyncStreamDtoTypesEnum._(r'AssetAlbumV1'); + static const assetAlbumDeleteV1 = SyncStreamDtoTypesEnum._(r'AssetAlbumDeleteV1'); + static const albumV1 = SyncStreamDtoTypesEnum._(r'AlbumV1'); + static const albumDeleteV1 = SyncStreamDtoTypesEnum._(r'AlbumDeleteV1'); + static const memoryV1 = SyncStreamDtoTypesEnum._(r'MemoryV1'); + static const memoryDeleteV1 = SyncStreamDtoTypesEnum._(r'MemoryDeleteV1'); + static const partnerV1 = SyncStreamDtoTypesEnum._(r'PartnerV1'); + static const partnerDeleteV1 = SyncStreamDtoTypesEnum._(r'PartnerDeleteV1'); + static const personV1 = SyncStreamDtoTypesEnum._(r'PersonV1'); + static const personDeleteV1 = SyncStreamDtoTypesEnum._(r'PersonDeleteV1'); + static const sharedLinkV1 = SyncStreamDtoTypesEnum._(r'SharedLinkV1'); + static const sharedLinkDeleteV1 = SyncStreamDtoTypesEnum._(r'SharedLinkDeleteV1'); + static const stackV1 = SyncStreamDtoTypesEnum._(r'StackV1'); + static const stackDeleteV1 = SyncStreamDtoTypesEnum._(r'StackDeleteV1'); + static const tagV1 = SyncStreamDtoTypesEnum._(r'TagV1'); + static const tagDeleteV1 = SyncStreamDtoTypesEnum._(r'TagDeleteV1'); + static const userV1 = SyncStreamDtoTypesEnum._(r'UserV1'); + static const userDeleteV1 = SyncStreamDtoTypesEnum._(r'UserDeleteV1'); + static const albumAssetV1 = SyncStreamDtoTypesEnum._(r'AlbumAssetV1'); + static const albumAssetDeleteV1 = SyncStreamDtoTypesEnum._(r'AlbumAssetDeleteV1'); + static const albumUserV1 = SyncStreamDtoTypesEnum._(r'AlbumUserV1'); + static const albumDeleteUserV1 = SyncStreamDtoTypesEnum._(r'AlbumDeleteUserV1'); + + /// List of all possible values in this [enum][SyncStreamDtoTypesEnum]. + static const values = [ + activityV1, + activityDeleteV1, + assetOwnerV1, + assetOwnerDeleteV1, + assetPartnerV1, + assetPartnerDeleteV1, + assetAlbumV1, + assetAlbumDeleteV1, + albumV1, + albumDeleteV1, + memoryV1, + memoryDeleteV1, + partnerV1, + partnerDeleteV1, + personV1, + personDeleteV1, + sharedLinkV1, + sharedLinkDeleteV1, + stackV1, + stackDeleteV1, + tagV1, + tagDeleteV1, + userV1, + userDeleteV1, + albumAssetV1, + albumAssetDeleteV1, + albumUserV1, + albumDeleteUserV1, + ]; + + static SyncStreamDtoTypesEnum? fromJson(dynamic value) => SyncStreamDtoTypesEnumTypeTransformer().decode(value); + + static List listFromJson(dynamic json, {bool growable = false,}) { + final result = []; + 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'ActivityV1': return SyncStreamDtoTypesEnum.activityV1; + case r'ActivityDeleteV1': return SyncStreamDtoTypesEnum.activityDeleteV1; + case r'AssetOwnerV1': return SyncStreamDtoTypesEnum.assetOwnerV1; + case r'AssetOwnerDeleteV1': return SyncStreamDtoTypesEnum.assetOwnerDeleteV1; + case r'AssetPartnerV1': return SyncStreamDtoTypesEnum.assetPartnerV1; + case r'AssetPartnerDeleteV1': return SyncStreamDtoTypesEnum.assetPartnerDeleteV1; + case r'AssetAlbumV1': return SyncStreamDtoTypesEnum.assetAlbumV1; + case r'AssetAlbumDeleteV1': return SyncStreamDtoTypesEnum.assetAlbumDeleteV1; + case r'AlbumV1': return SyncStreamDtoTypesEnum.albumV1; + case r'AlbumDeleteV1': return SyncStreamDtoTypesEnum.albumDeleteV1; + case r'MemoryV1': return SyncStreamDtoTypesEnum.memoryV1; + case r'MemoryDeleteV1': return SyncStreamDtoTypesEnum.memoryDeleteV1; + case r'PartnerV1': return SyncStreamDtoTypesEnum.partnerV1; + case r'PartnerDeleteV1': return SyncStreamDtoTypesEnum.partnerDeleteV1; + case r'PersonV1': return SyncStreamDtoTypesEnum.personV1; + case r'PersonDeleteV1': return SyncStreamDtoTypesEnum.personDeleteV1; + case r'SharedLinkV1': return SyncStreamDtoTypesEnum.sharedLinkV1; + case r'SharedLinkDeleteV1': return SyncStreamDtoTypesEnum.sharedLinkDeleteV1; + case r'StackV1': return SyncStreamDtoTypesEnum.stackV1; + case r'StackDeleteV1': return SyncStreamDtoTypesEnum.stackDeleteV1; + case r'TagV1': return SyncStreamDtoTypesEnum.tagV1; + case r'TagDeleteV1': return SyncStreamDtoTypesEnum.tagDeleteV1; + case r'UserV1': return SyncStreamDtoTypesEnum.userV1; + case r'UserDeleteV1': return SyncStreamDtoTypesEnum.userDeleteV1; + case r'AlbumAssetV1': return SyncStreamDtoTypesEnum.albumAssetV1; + case r'AlbumAssetDeleteV1': return SyncStreamDtoTypesEnum.albumAssetDeleteV1; + case r'AlbumUserV1': return SyncStreamDtoTypesEnum.albumUserV1; + case r'AlbumDeleteUserV1': return SyncStreamDtoTypesEnum.albumDeleteUserV1; + default: + if (!allowNull) { + throw ArgumentError('Unknown enum value to decode: $data'); + } + } + } + return null; + } + + /// Singleton [SyncStreamDtoTypesEnumTypeTransformer] instance. + static SyncStreamDtoTypesEnumTypeTransformer? _instance; +} + + diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 8465b6bb40e65f..b229b4f10daa9f 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -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,41 @@ ] } }, + "/sync/stream": { + "post": { + "operationId": "getSyncStream", + "parameters": [], + "requestBody": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/SyncStreamDto" + } + } + }, + "required": true + }, + "responses": { + "200": { + "description": "" + } + }, + "security": [ + { + "bearer": [] + }, + { + "cookie": [] + }, + { + "api_key": [] + } + ], + "tags": [ + "Sync" + ] + } + }, "/system-config": { "get": { "operationId": "getConfig", @@ -11481,6 +11551,743 @@ }, "type": "object" }, + "SyncAcknowledgeDto": { + "properties": { + "timestamp": { + "type": "string" + }, + "type": { + "enum": [ + "Activity", + "ActivityDelete", + "AssetOwner", + "AssetOwnerDelete", + "AssetPartner", + "AssetPartnerDelete", + "AssetAlbum", + "AssetAlbumDelete", + "Album", + "AlbumDelete", + "Memory", + "MemoryDelete", + "Partner", + "PartnerDelete", + "Person", + "PersonDelete", + "SharedLink", + "SharedLinkDelete", + "Stack", + "StackDelete", + "Tag", + "TagDelete", + "User", + "UserDelete", + "AlbumAsset", + "AlbumAssetDelete", + "AlbumUser", + "AlbumUserDelete" + ], + "type": "string" + } + }, + "required": [ + "timestamp", + "type" + ], + "type": "object" + }, + "SyncActivityDeleteDtoV1": { + "properties": { + "deletedAt": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "deletedAt", + "id" + ], + "type": "object" + }, + "SyncActivityDtoV1": { + "properties": {}, + "type": "object" + }, + "SyncAlbumAssetDeleteDtoV1": { + "properties": { + "albumId": { + "format": "uuid", + "type": "string" + }, + "assetId": { + "format": "uuid", + "type": "string" + }, + "deletedAt": { + "type": "string" + } + }, + "required": [ + "albumId", + "assetId", + "deletedAt" + ], + "type": "object" + }, + "SyncAlbumAssetDtoV1": { + "properties": { + "albumId": { + "format": "uuid", + "type": "string" + }, + "assetId": { + "format": "uuid", + "type": "string" + }, + "createdAt": { + "type": "string" + } + }, + "required": [ + "albumId", + "assetId", + "createdAt" + ], + "type": "object" + }, + "SyncAlbumDeleteV1": { + "properties": { + "deletedAt": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "deletedAt", + "id" + ], + "type": "object" + }, + "SyncAlbumDtoV1": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, + "SyncAssetAlbumDeleteV1": { + "properties": { + "deletedAt": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "deletedAt", + "id" + ], + "type": "object" + }, + "SyncAssetAlbumDtoV1": { + "properties": { + "bitsPerSample": { + "type": "number" + }, + "checksum": { + "type": "string" + }, + "city": { + "type": "string" + }, + "colorspace": { + "type": "string" + }, + "country": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dateTimeOriginal": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "duration": { + "type": "string" + }, + "exifImageHeight": { + "type": "number" + }, + "exifImageWidth": { + "type": "number" + }, + "exposureTime": { + "type": "string" + }, + "fNumber": { + "type": "number" + }, + "fileCreatedAt": { + "type": "string" + }, + "fileModifiedAt": { + "type": "string" + }, + "fileSizeInByte": { + "type": "number" + }, + "fps": { + "type": "number" + }, + "id": { + "type": "string" + }, + "isArchived": { + "type": "boolean" + }, + "isExternal": { + "type": "boolean" + }, + "isFavorite": { + "type": "boolean" + }, + "isOffline": { + "type": "boolean" + }, + "iso": { + "type": "number" + }, + "latitude": { + "type": "number" + }, + "lensModel": { + "type": "string" + }, + "libraryId": { + "type": "string" + }, + "livePhotoVideoId": { + "type": "string" + }, + "longitude": { + "type": "number" + }, + "make": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modifyDate": { + "type": "string" + }, + "orientation": { + "type": "string" + }, + "originalFileName": { + "type": "string" + }, + "originalPath": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "profileDescription": { + "type": "string" + }, + "projectionType": { + "type": "string" + }, + "rating": { + "type": "number" + }, + "state": { + "type": "string" + }, + "status": { + "enum": [ + "active", + "trashed", + "deleted" + ], + "type": "string" + }, + "thumbhash": { + "type": "string" + }, + "type": { + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ], + "type": "string" + } + }, + "required": [ + "checksum", + "createdAt", + "fileCreatedAt", + "fileModifiedAt", + "id", + "isArchived", + "isExternal", + "isFavorite", + "isOffline", + "originalFileName", + "originalPath", + "ownerId", + "status", + "type" + ], + "type": "object" + }, + "SyncAssetOwnerDeleteV1": { + "properties": { + "deletedAt": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "deletedAt", + "id" + ], + "type": "object" + }, + "SyncAssetOwnerDtoV1": { + "properties": { + "bitsPerSample": { + "type": "number" + }, + "checksum": { + "type": "string" + }, + "city": { + "type": "string" + }, + "colorspace": { + "type": "string" + }, + "country": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dateTimeOriginal": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "duration": { + "type": "string" + }, + "exifImageHeight": { + "type": "number" + }, + "exifImageWidth": { + "type": "number" + }, + "exposureTime": { + "type": "string" + }, + "fNumber": { + "type": "number" + }, + "fileCreatedAt": { + "type": "string" + }, + "fileModifiedAt": { + "type": "string" + }, + "fileSizeInByte": { + "type": "number" + }, + "fps": { + "type": "number" + }, + "id": { + "type": "string" + }, + "isArchived": { + "type": "boolean" + }, + "isExternal": { + "type": "boolean" + }, + "isFavorite": { + "type": "boolean" + }, + "isOffline": { + "type": "boolean" + }, + "iso": { + "type": "number" + }, + "latitude": { + "type": "number" + }, + "lensModel": { + "type": "string" + }, + "libraryId": { + "type": "string" + }, + "livePhotoVideoId": { + "type": "string" + }, + "longitude": { + "type": "number" + }, + "make": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modifyDate": { + "type": "string" + }, + "orientation": { + "type": "string" + }, + "originalFileName": { + "type": "string" + }, + "originalPath": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "profileDescription": { + "type": "string" + }, + "projectionType": { + "type": "string" + }, + "rating": { + "type": "number" + }, + "state": { + "type": "string" + }, + "status": { + "enum": [ + "active", + "trashed", + "deleted" + ], + "type": "string" + }, + "thumbhash": { + "type": "string" + }, + "type": { + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ], + "type": "string" + } + }, + "required": [ + "checksum", + "createdAt", + "fileCreatedAt", + "fileModifiedAt", + "id", + "isArchived", + "isExternal", + "isFavorite", + "isOffline", + "originalFileName", + "originalPath", + "ownerId", + "status", + "type" + ], + "type": "object" + }, + "SyncAssetPartnerDeleteV1": { + "properties": { + "deletedAt": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "deletedAt", + "id" + ], + "type": "object" + }, + "SyncAssetPartnerDtoV1": { + "properties": { + "bitsPerSample": { + "type": "number" + }, + "checksum": { + "type": "string" + }, + "city": { + "type": "string" + }, + "colorspace": { + "type": "string" + }, + "country": { + "type": "string" + }, + "createdAt": { + "type": "string" + }, + "dateTimeOriginal": { + "type": "string" + }, + "deletedAt": { + "type": "string" + }, + "duration": { + "type": "string" + }, + "exifImageHeight": { + "type": "number" + }, + "exifImageWidth": { + "type": "number" + }, + "exposureTime": { + "type": "string" + }, + "fNumber": { + "type": "number" + }, + "fileCreatedAt": { + "type": "string" + }, + "fileModifiedAt": { + "type": "string" + }, + "fileSizeInByte": { + "type": "number" + }, + "fps": { + "type": "number" + }, + "id": { + "type": "string" + }, + "isArchived": { + "type": "boolean" + }, + "isExternal": { + "type": "boolean" + }, + "isFavorite": { + "type": "boolean" + }, + "isOffline": { + "type": "boolean" + }, + "iso": { + "type": "number" + }, + "latitude": { + "type": "number" + }, + "lensModel": { + "type": "string" + }, + "libraryId": { + "type": "string" + }, + "livePhotoVideoId": { + "type": "string" + }, + "longitude": { + "type": "number" + }, + "make": { + "type": "string" + }, + "model": { + "type": "string" + }, + "modifyDate": { + "type": "string" + }, + "orientation": { + "type": "string" + }, + "originalFileName": { + "type": "string" + }, + "originalPath": { + "type": "string" + }, + "ownerId": { + "type": "string" + }, + "profileDescription": { + "type": "string" + }, + "projectionType": { + "type": "string" + }, + "rating": { + "type": "number" + }, + "state": { + "type": "string" + }, + "status": { + "enum": [ + "active", + "trashed", + "deleted" + ], + "type": "string" + }, + "thumbhash": { + "type": "string" + }, + "type": { + "enum": [ + "IMAGE", + "VIDEO", + "AUDIO", + "OTHER" + ], + "type": "string" + } + }, + "required": [ + "checksum", + "createdAt", + "fileCreatedAt", + "fileModifiedAt", + "id", + "isArchived", + "isExternal", + "isFavorite", + "isOffline", + "originalFileName", + "originalPath", + "ownerId", + "status", + "type" + ], + "type": "object" + }, + "SyncMemoryDelete": { + "properties": { + "deletedAt": { + "type": "string" + }, + "id": { + "type": "string" + } + }, + "required": [ + "deletedAt", + "id" + ], + "type": "object" + }, + "SyncMemoryDtoV1": { + "properties": { + "description": { + "type": "string" + }, + "id": { + "type": "string" + }, + "name": { + "type": "string" + } + }, + "required": [ + "description", + "id", + "name" + ], + "type": "object" + }, + "SyncStreamDto": { + "properties": { + "types": { + "items": { + "enum": [ + "ActivityV1", + "ActivityDeleteV1", + "AssetOwnerV1", + "AssetOwnerDeleteV1", + "AssetPartnerV1", + "AssetPartnerDeleteV1", + "AssetAlbumV1", + "AssetAlbumDeleteV1", + "AlbumV1", + "AlbumDeleteV1", + "MemoryV1", + "MemoryDeleteV1", + "PartnerV1", + "PartnerDeleteV1", + "PersonV1", + "PersonDeleteV1", + "SharedLinkV1", + "SharedLinkDeleteV1", + "StackV1", + "StackDeleteV1", + "TagV1", + "TagDeleteV1", + "UserV1", + "UserDeleteV1", + "AlbumAssetV1", + "AlbumAssetDeleteV1", + "AlbumUserV1", + "AlbumDeleteUserV1" + ], + "type": "string" + }, + "type": "array" + } + }, + "required": [ + "types" + ], + "type": "object" + }, "SystemConfigBackupsDto": { "properties": { "database": { diff --git a/open-api/typescript-sdk/src/fetch-client.ts b/open-api/typescript-sdk/src/fetch-client.ts index 6a66906f31d6ba..8b9991815b4038 100644 --- a/open-api/typescript-sdk/src/fetch-client.ts +++ b/open-api/typescript-sdk/src/fetch-client.ts @@ -1070,6 +1070,10 @@ export type StackCreateDto = { export type StackUpdateDto = { primaryAssetId?: string; }; +export type SyncAcknowledgeDto = { + timestamp: string; + "type": Type; +}; export type AssetDeltaSyncDto = { updatedAfter: string; userIds: string[]; @@ -1085,6 +1089,9 @@ export type AssetFullSyncDto = { updatedUntil: string; userId?: string; }; +export type SyncStreamDto = { + types: ("ActivityV1" | "ActivityDeleteV1" | "AssetOwnerV1" | "AssetOwnerDeleteV1" | "AssetPartnerV1" | "AssetPartnerDeleteV1" | "AssetAlbumV1" | "AssetAlbumDeleteV1" | "AlbumV1" | "AlbumDeleteV1" | "MemoryV1" | "MemoryDeleteV1" | "PartnerV1" | "PartnerDeleteV1" | "PersonV1" | "PersonDeleteV1" | "SharedLinkV1" | "SharedLinkDeleteV1" | "StackV1" | "StackDeleteV1" | "TagV1" | "TagDeleteV1" | "UserV1" | "UserDeleteV1" | "AlbumAssetV1" | "AlbumAssetDeleteV1" | "AlbumUserV1" | "AlbumDeleteUserV1")[]; +}; export type DatabaseBackupConfig = { cronExpression: string; enabled: boolean; @@ -2861,6 +2868,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) { @@ -2885,6 +2901,15 @@ export function getFullSyncForUser({ assetFullSyncDto }: { body: assetFullSyncDto }))); } +export function getSyncStream({ syncStreamDto }: { + syncStreamDto: SyncStreamDto; +}, opts?: Oazapfts.RequestOpts) { + return oazapfts.ok(oazapfts.fetchText("/sync/stream", oazapfts.json({ + ...opts, + method: "POST", + body: syncStreamDto + }))); +} export function getConfig(opts?: Oazapfts.RequestOpts) { return oazapfts.ok(oazapfts.fetchJson<{ status: 200; @@ -3501,6 +3526,36 @@ export enum Error2 { NoPermission = "no_permission", NotFound = "not_found" } +export enum Type { + Activity = "Activity", + ActivityDelete = "ActivityDelete", + AssetOwner = "AssetOwner", + AssetOwnerDelete = "AssetOwnerDelete", + AssetPartner = "AssetPartner", + AssetPartnerDelete = "AssetPartnerDelete", + AssetAlbum = "AssetAlbum", + AssetAlbumDelete = "AssetAlbumDelete", + Album = "Album", + AlbumDelete = "AlbumDelete", + Memory = "Memory", + MemoryDelete = "MemoryDelete", + Partner = "Partner", + PartnerDelete = "PartnerDelete", + Person = "Person", + PersonDelete = "PersonDelete", + SharedLink = "SharedLink", + SharedLinkDelete = "SharedLinkDelete", + Stack = "Stack", + StackDelete = "StackDelete", + Tag = "Tag", + TagDelete = "TagDelete", + User = "User", + UserDelete = "UserDelete", + AlbumAsset = "AlbumAsset", + AlbumAssetDelete = "AlbumAssetDelete", + AlbumUser = "AlbumUser", + AlbumUserDelete = "AlbumUserDelete" +} export enum TranscodeHWAccel { Nvenc = "nvenc", Qsv = "qsv", diff --git a/server/src/app.module.ts b/server/src/app.module.ts index 7b9098c01003b6..9e5f8e675fbe40 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -25,7 +25,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, diff --git a/server/src/controllers/sync.controller.ts b/server/src/controllers/sync.controller.ts index 4d970a7102a8fa..cf6039b5eb5aaa 100644 --- a/server/src/controllers/sync.controller.ts +++ b/server/src/controllers/sync.controller.ts @@ -1,27 +1,58 @@ -import { Body, Controller, HttpCode, HttpStatus, Post } from '@nestjs/common'; +import { Body, Controller, Header, HttpCode, HttpStatus, Post, Res } from '@nestjs/common'; import { 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, +} 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) + @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 { - 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 { - return this.service.getDeltaSync(auth, dto); + return this.syncService.getDeltaSync(auth, dto); } } diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index 820de8d6c3320f..5b312bfb06729c 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -1,8 +1,162 @@ -import { ApiProperty } from '@nestjs/swagger'; -import { IsInt, IsPositive } from 'class-validator'; +import { ApiProperty, getSchemaPath } from '@nestjs/swagger'; +import { ArrayNotEmpty, IsDateString, IsEnum, IsInt, IsPositive } from 'class-validator'; import { AssetResponseDto } from 'src/dtos/asset-response.dto'; +import { AssetStatus, AssetType, SyncAcknowledgeType, SyncResponseType } from 'src/enum'; import { ValidateDate, ValidateUUID } from 'src/validation'; +export class SyncAcknowledgeDto { + @IsEnum(SyncAcknowledgeType) + type!: SyncAcknowledgeType; + + @IsDateString({ strict: true }) + timestamp!: string; +} + +export class SyncCommonDeleteDtoV1 { + id!: string; + deletedAt!: string; +} + +export class SyncCommonAssetDtoV1 { + // TODO stop using these + // deviceAssetId!: string; + // deviceId!: string; + + id!: string; + ownerId!: string; + libraryId?: string; + livePhotoVideoId?: string; + + createdAt!: string; + deletedAt?: string; + fileCreatedAt!: string; + fileModifiedAt!: string; + + isFavorite!: boolean; + isArchived!: boolean; + isExternal!: boolean; + isOffline!: boolean; + + type!: AssetType; + status!: AssetStatus; + + thumbhash?: string; + originalPath!: string; + originalFileName!: string; + checksum!: string; + duration?: string; + + exifImageWidth?: number; + exifImageHeight?: number; + fileSizeInByte?: number; + orientation?: string; + dateTimeOriginal?: string; + modifyDate?: string; + latitude?: number; + longitude?: number; + projectionType?: string; + city?: string; + state?: string; + country?: string; + make?: string; + model?: string; + lensModel?: string; + fNumber?: number; + iso?: number; + exposureTime?: string; + profileDescription?: string; + colorspace?: string; + bitsPerSample?: number; + rating?: number; + fps?: number; +} + +export class SyncActivityDtoV1 {} +export class SyncActivityDeleteDtoV1 extends SyncCommonDeleteDtoV1 {} + +export class SyncAssetOwnerDtoV1 extends SyncCommonAssetDtoV1 {} +export class SyncAssetOwnerDeleteV1 extends SyncCommonDeleteDtoV1 {} + +export class SyncAssetPartnerDtoV1 extends SyncCommonAssetDtoV1 {} +export class SyncAssetPartnerDeleteV1 extends SyncCommonDeleteDtoV1 {} + +export class SyncAssetAlbumDtoV1 extends SyncCommonAssetDtoV1 {} +export class SyncAssetAlbumDeleteV1 extends SyncCommonDeleteDtoV1 {} + +export class SyncAlbumDtoV1 { + id!: string; + name!: string; + description!: string; +} +export class SyncAlbumDeleteV1 extends SyncCommonDeleteDtoV1 {} + +export class SyncMemoryDtoV1 { + id!: string; + name!: string; + description!: string; +} +export class SyncMemoryDelete extends SyncCommonDeleteDtoV1 {} + +export class SyncAlbumAssetDtoV1 { + @ApiProperty({ format: 'uuid' }) + albumId!: string; + + @ApiProperty({ format: 'uuid' }) + assetId!: string; + + createdAt!: string; +} + +export class SyncAlbumAssetDeleteDtoV1 { + @ApiProperty({ format: 'uuid' }) + albumId!: string; + + @ApiProperty({ format: 'uuid' }) + assetId!: string; + + deletedAt!: string; +} + +const responseDtos = [ + SyncActivityDtoV1, + SyncActivityDeleteDtoV1, + + SyncAssetOwnerDtoV1, + SyncAssetOwnerDeleteV1, + + SyncAssetPartnerDtoV1, + SyncAssetPartnerDeleteV1, + + SyncAssetAlbumDtoV1, + SyncAssetAlbumDeleteV1, + + SyncAlbumDtoV1, + SyncAlbumDeleteV1, + + SyncMemoryDtoV1, + SyncMemoryDelete, + + SyncAlbumAssetDtoV1, + SyncAlbumAssetDeleteDtoV1, +]; + +export const extraSyncModels = responseDtos; + +export class SyncStreamResponseDto { + @ApiProperty({ enum: SyncResponseType, enumName: 'SyncType' }) + type!: SyncResponseType; + + @ApiProperty({ anyOf: responseDtos.map((schema) => ({ $ref: getSchemaPath(schema) })) }) + data!: SyncCommonAssetDtoV1; +} + +export class SyncStreamDto { + @IsEnum(SyncResponseType, { each: true }) + @ApiProperty({ enum: SyncResponseType, isArray: true }) + @ArrayNotEmpty() + types!: SyncResponseType[]; +} + export class AssetFullSyncDto { @ValidateUUID({ optional: true }) lastId?: string; diff --git a/server/src/entities/index.ts b/server/src/entities/index.ts index 7425ee67d8a6e5..c619e24e615321 100644 --- a/server/src/entities/index.ts +++ b/server/src/entities/index.ts @@ -21,6 +21,7 @@ import { SharedLinkEntity } from 'src/entities/shared-link.entity'; import { SmartInfoEntity } from 'src/entities/smart-info.entity'; import { SmartSearchEntity } from 'src/entities/smart-search.entity'; import { StackEntity } from 'src/entities/stack.entity'; +import { SyncCheckpointEntity } from 'src/entities/sync-checkpoint.entity'; import { SystemMetadataEntity } from 'src/entities/system-metadata.entity'; import { TagEntity } from 'src/entities/tag.entity'; import { UserMetadataEntity } from 'src/entities/user-metadata.entity'; @@ -54,6 +55,7 @@ export const entities = [ UserEntity, UserMetadataEntity, SessionEntity, + SyncCheckpointEntity, LibraryEntity, VersionHistoryEntity, ]; diff --git a/server/src/entities/session.entity.ts b/server/src/entities/session.entity.ts index 1cc9ad98572ab2..213b68d95bbea7 100644 --- a/server/src/entities/session.entity.ts +++ b/server/src/entities/session.entity.ts @@ -1,5 +1,14 @@ +import { SyncCheckpointEntity } from 'src/entities/sync-checkpoint.entity'; import { UserEntity } from 'src/entities/user.entity'; -import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryGeneratedColumn, UpdateDateColumn } from 'typeorm'; +import { + Column, + CreateDateColumn, + Entity, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, + UpdateDateColumn, +} from 'typeorm'; @Entity('sessions') export class SessionEntity { @@ -26,4 +35,7 @@ export class SessionEntity { @Column({ default: '' }) deviceOS!: string; + + @OneToMany(() => SyncCheckpointEntity, (checkpoint) => checkpoint.session) + checkpoints?: SyncCheckpointEntity[]; } diff --git a/server/src/entities/sync-checkpoint.entity.ts b/server/src/entities/sync-checkpoint.entity.ts new file mode 100644 index 00000000000000..4cc6e3b084aeac --- /dev/null +++ b/server/src/entities/sync-checkpoint.entity.ts @@ -0,0 +1,24 @@ +import { SessionEntity } from 'src/entities/session.entity'; +import { SyncAcknowledgeType } from 'src/enum'; +import { Column, CreateDateColumn, Entity, ManyToOne, PrimaryColumn, UpdateDateColumn } from 'typeorm'; + +@Entity('session_sync_checkpoints') +export class SyncCheckpointEntity { + @ManyToOne(() => SessionEntity, { onDelete: 'CASCADE', onUpdate: 'CASCADE' }) + session?: SessionEntity; + + @PrimaryColumn() + sessionId!: string; + + @PrimaryColumn({ type: 'varchar' }) + type!: SyncAcknowledgeType; + + @CreateDateColumn({ type: 'timestamptz' }) + createdAt!: Date; + + @UpdateDateColumn({ type: 'timestamptz' }) + updatedAt!: Date; + + @Column({ type: 'timestamptz' }) + lastTimestamp!: string; +} diff --git a/server/src/enum.ts b/server/src/enum.ts index 0b82185285c297..cbf1d37b7ddcfd 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -53,6 +53,98 @@ export enum DatabaseAction { DELETE = 'DELETE', } +export enum SyncAcknowledgeType { + // base types + Activity = 'Activity', + ActivityDelete = 'ActivityDelete', + + AssetOwner = 'AssetOwner', + AssetOwnerDelete = 'AssetOwnerDelete', + + AssetPartner = 'AssetPartner', + AssetPartnerDelete = 'AssetPartnerDelete', + + AssetAlbum = 'AssetAlbum', + AssetAlbumDelete = 'AssetAlbumDelete', + + Album = 'Album', + AlbumDelete = 'AlbumDelete', + + Memory = 'Memory', + MemoryDelete = 'MemoryDelete', + + Partner = 'Partner', + PartnerDelete = 'PartnerDelete', + + Person = 'Person', + PersonDelete = 'PersonDelete', + + SharedLink = 'SharedLink', + SharedLinkDelete = 'SharedLinkDelete', + + Stack = 'Stack', + StackDelete = 'StackDelete', + + Tag = 'Tag', + TagDelete = 'TagDelete', + + User = 'User', + UserDelete = 'UserDelete', + + // relation types + AlbumAsset = 'AlbumAsset', + AlbumAssetDelete = 'AlbumAssetDelete', + + AlbumUser = 'AlbumUser', + AlbumUserDelete = 'AlbumUserDelete', +} + +export enum SyncResponseType { + // base types + ActivityV1 = 'ActivityV1', + ActivityDeleteV1 = 'ActivityDeleteV1', + + AssetOwnerV1 = 'AssetOwnerV1', + AssetOwnerDeleteV1 = 'AssetOwnerDeleteV1', + + AssetPartnerV1 = 'AssetPartnerV1', + AssetPartnerDeleteV1 = 'AssetPartnerDeleteV1', + + AssetAlbumV1 = 'AssetAlbumV1', + AssetAlbumDeleteV1 = 'AssetAlbumDeleteV1', + + AlbumV1 = 'AlbumV1', + AlbumDeleteV1 = 'AlbumDeleteV1', + + MemoryV1 = 'MemoryV1', + MemoryDeleteV1 = 'MemoryDeleteV1', + + PartnerV1 = 'PartnerV1', + PartnerDeleteV1 = 'PartnerDeleteV1', + + PersonV1 = 'PersonV1', + PersonDeleteV1 = 'PersonDeleteV1', + + SharedLinkV1 = 'SharedLinkV1', + SharedLinkDeleteV1 = 'SharedLinkDeleteV1', + + StackV1 = 'StackV1', + StackDeleteV1 = 'StackDeleteV1', + + TagV1 = 'TagV1', + TagDeleteV1 = 'TagDeleteV1', + + UserV1 = 'UserV1', + UserDeleteV1 = 'UserDeleteV1', + + // relation types + AlbumAssetV1 = 'AlbumAssetV1', + AlbumAssetDeleteV1 = 'AlbumAssetDeleteV1', + + AlbumUserV1 = 'AlbumUserV1', + AlbumUserDeleteV1 = 'AlbumDeleteUserV1', +} + export enum EntityType { ASSET = 'ASSET', ALBUM = 'ALBUM', diff --git a/server/src/interfaces/sync.interface.ts b/server/src/interfaces/sync.interface.ts new file mode 100644 index 00000000000000..a8afe01dd8adab --- /dev/null +++ b/server/src/interfaces/sync.interface.ts @@ -0,0 +1,44 @@ +import { AlbumEntity } from 'src/entities/album.entity'; +import { AssetEntity } from 'src/entities/asset.entity'; +import { SessionEntity } from 'src/entities/session.entity'; +import { SyncCheckpointEntity } from 'src/entities/sync-checkpoint.entity'; +import { Paginated, PaginationOptions } from 'src/utils/pagination'; + +export const ISyncRepository = 'ISyncRepository'; + +export type SyncOptions = PaginationOptions & { + userId: string; + lastCheckpointTimestamp?: string; +}; + +export type AssetPartnerSyncOptions = SyncOptions & { partnerIds: string[] }; + +export type EntityPK = { id: string }; +export type DeletedEntity = T & { + deletedAt: Date; +}; +export type AlbumAssetPK = { + albumId: string; + assetId: string; +}; + +export type AlbumAssetEntity = AlbumAssetPK & { + createdAt: Date; +}; + +export interface ISyncRepository { + get(sessionId: string): Promise; + upsertCheckpoint(checkpoint: Partial): Promise; + + getAssets(options: SyncOptions): Paginated; + getDeletedAssets(options: SyncOptions): Paginated; + + getAssetsPartner(options: AssetPartnerSyncOptions): Paginated; + getDeletedAssetsPartner(options: AssetPartnerSyncOptions): Paginated; + + getAlbums(options: SyncOptions): Paginated; + getDeletedAlbums(options: SyncOptions): Paginated; + + getAlbumAssets(options: SyncOptions): Paginated; + getDeletedAlbumAssets(options: SyncOptions): Paginated>; +} diff --git a/server/src/middleware/global-exception.filter.ts b/server/src/middleware/global-exception.filter.ts index 6200363e86e9e6..9aeeed5c578907 100644 --- a/server/src/middleware/global-exception.filter.ts +++ b/server/src/middleware/global-exception.filter.ts @@ -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 { constructor( @@ -15,10 +16,13 @@ export class GlobalExceptionFilter implements ExceptionFilter { catch(error: Error, host: ArgumentsHost) { const ctx = host.switchToHttp(); - const response = ctx.getResponse(); + 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() }); } } diff --git a/server/src/migrations/1730742937135-AddSyncCheckpointTable.ts b/server/src/migrations/1730742937135-AddSyncCheckpointTable.ts new file mode 100644 index 00000000000000..47b218eae9c714 --- /dev/null +++ b/server/src/migrations/1730742937135-AddSyncCheckpointTable.ts @@ -0,0 +1,16 @@ +import { MigrationInterface, QueryRunner } from "typeorm"; + +export class AddSyncCheckpointTable1730742937135 implements MigrationInterface { + name = 'AddSyncCheckpointTable1730742937135' + + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.query(`CREATE TABLE "session_sync_checkpoints" ("sessionId" uuid NOT NULL, "type" character varying NOT NULL, "createdAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updatedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "lastTimestamp" TIMESTAMP WITH TIME ZONE NOT NULL, CONSTRAINT "PK_b846ab547a702863ef7cd9412fb" PRIMARY KEY ("sessionId", "type"))`); + await queryRunner.query(`ALTER TABLE "session_sync_checkpoints" ADD CONSTRAINT "FK_d8ddd9d687816cc490432b3d4bc" FOREIGN KEY ("sessionId") REFERENCES "sessions"("id") ON DELETE CASCADE ON UPDATE CASCADE`); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.query(`ALTER TABLE "session_sync_checkpoints" DROP CONSTRAINT "FK_d8ddd9d687816cc490432b3d4bc"`); + await queryRunner.query(`DROP TABLE "session_sync_checkpoints"`); + } + +} diff --git a/server/src/repositories/index.ts b/server/src/repositories/index.ts index e487df503c824e..4f8867d4edda1f 100644 --- a/server/src/repositories/index.ts +++ b/server/src/repositories/index.ts @@ -29,6 +29,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'; @@ -67,6 +68,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'; @@ -107,6 +109,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 }, diff --git a/server/src/repositories/sync.repository.ts b/server/src/repositories/sync.repository.ts new file mode 100644 index 00000000000000..4d479dbdb62abf --- /dev/null +++ b/server/src/repositories/sync.repository.ts @@ -0,0 +1,115 @@ +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 { SessionEntity } from 'src/entities/session.entity'; +import { SyncCheckpointEntity } from 'src/entities/sync-checkpoint.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, Repository } from 'typeorm'; + +const withCheckpoint = (where: FindOptionsWhere, key: keyof T, lastCheckpointTimestamp?: string) => { + return lastCheckpointTimestamp ? { ...where, [key]: MoreThan(lastCheckpointTimestamp) } : where; +}; + +@Injectable() +export class SyncRepository implements ISyncRepository { + constructor( + private dataSource: DataSource, + @InjectRepository(AssetEntity) private assetRepository: Repository, + @InjectRepository(AlbumEntity) private albumRepository: Repository, + @InjectRepository(SyncCheckpointEntity) private checkpointRepository: Repository, + @InjectRepository(SessionEntity) private sessionRepository: Repository, + ) {} + + get(sessionId: string): Promise { + return this.sessionRepository.findOne({ + where: { + id: sessionId, + }, + relations: { + checkpoints: true, + }, + }); + } + + async upsertCheckpoint(checkpoint: Partial): Promise { + await this.checkpointRepository.upsert(checkpoint, { conflictPaths: ['sessionId', 'type'] }); + } + + getAssets({ + lastCheckpointTimestamp: checkpoint, + userId, + ...options + }: AssetPartnerSyncOptions): Paginated { + return paginate(this.assetRepository, options, { + where: withCheckpoint( + { + ownerId: userId, + isVisible: true, + }, + 'updatedAt', + checkpoint, + ), + relations: { + exifInfo: true, + }, + order: { + updatedAt: 'ASC', + id: 'ASC', + }, + }); + } + + getDeletedAssets(): Paginated> { + return Promise.resolve({ items: [], hasNextPage: false }); + } + + getAssetsPartner({ + lastCheckpointTimestamp: checkpoint, + partnerIds, + ...options + }: AssetPartnerSyncOptions): Paginated { + return paginate(this.assetRepository, options, { + where: withCheckpoint({ ownerId: In(partnerIds) }, 'updatedAt', checkpoint), + order: { + updatedAt: 'ASC', + id: 'ASC', + }, + }); + } + + getDeletedAssetsPartner(): Paginated> { + return Promise.resolve({ items: [], hasNextPage: false }); + } + + getAlbums({ lastCheckpointTimestamp: checkpoint, userId, ...options }: SyncOptions): Paginated { + return paginate(this.albumRepository, options, { + where: withCheckpoint({ ownerId: userId }, 'updatedAt', checkpoint), + order: { + updatedAt: 'ASC', + id: 'ASC', + }, + }); + } + + getDeletedAlbums(): Paginated> { + return Promise.resolve({ items: [], hasNextPage: false }); + } + + getAlbumAssets(): Paginated { + return Promise.resolve({ items: [], hasNextPage: false }); + } + + getDeletedAlbumAssets(): Paginated> { + return Promise.resolve({ items: [], hasNextPage: false }); + } +} diff --git a/server/src/services/base.service.ts b/server/src/services/base.service.ts index dc7dab102c69d7..fcb48977051111 100644 --- a/server/src/services/base.service.ts +++ b/server/src/services/base.service.ts @@ -35,6 +35,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'; @@ -80,6 +81,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, diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts index f85200db489fad..ebedca97629d41 100644 --- a/server/src/services/sync.service.ts +++ b/server/src/services/sync.service.ts @@ -1,16 +1,337 @@ +import { ForbiddenException } from '@nestjs/common'; import { DateTime } from 'luxon'; +import { Writable } from 'node:stream'; import { AUDIT_LOG_MAX_DURATION } from 'src/constants'; +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, + SyncAlbumAssetDeleteDtoV1, + SyncAlbumAssetDtoV1, + SyncAlbumDeleteV1, + SyncAssetOwnerDeleteV1, + SyncAssetOwnerDtoV1, + SyncAssetPartnerDeleteV1, + SyncCommonAssetDtoV1, + SyncCommonDeleteDtoV1, + SyncStreamDto, +} from 'src/dtos/sync.dto'; +import { AlbumEntity } from 'src/entities/album.entity'; +import { AssetEntity } from 'src/entities/asset.entity'; +import { SyncCheckpointEntity } from 'src/entities/sync-checkpoint.entity'; +import { DatabaseAction, EntityType, Permission, SyncAcknowledgeType, SyncResponseType } from 'src/enum'; +import { AlbumAssetEntity, AlbumAssetPK, 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 createdAt = (item: { createdAt: Date }) => item.createdAt.toISOString(); +const updatedAt = (item: { updatedAt: Date }) => item.updatedAt.toISOString(); +const deletedAt = (item: { deletedAt: Date }) => item.deletedAt.toISOString(); + +const mapJsonLine = (item: unknown) => JSON.stringify(item) + '\n'; + +const v1 = { + mapDeletedDto: (item: DeletedEntity): SyncCommonDeleteDtoV1 => ({ + id: item.id, + deletedAt: item.deletedAt.toISOString(), + }), + mapAsset: (item: AssetEntity): SyncCommonAssetDtoV1 => ({ + id: item.id, + ownerId: item.ownerId, + libraryId: item.libraryId ?? undefined, + livePhotoVideoId: item.livePhotoVideoId ?? undefined, + + createdAt: item.createdAt.toISOString(), + deletedAt: item.deletedAt?.toISOString(), + fileCreatedAt: item.fileCreatedAt.toISOString(), + fileModifiedAt: item.fileModifiedAt.toISOString(), + + isFavorite: item.isFavorite, + isArchived: item.isArchived, + isExternal: item.isExternal, + isOffline: item.isOffline, + + type: item.type, + status: item.status, + + thumbhash: item.thumbhash?.toString('base64'), + originalPath: item.originalPath, + originalFileName: item.originalFileName, + checksum: item.checksum.toString('base64'), + duration: item.duration ?? '0:00:00.00000', + + exifImageWidth: item.exifInfo?.exifImageWidth ?? undefined, + exifImageHeight: item.exifInfo?.exifImageHeight ?? undefined, + fileSizeInByte: item.exifInfo?.fileSizeInByte ?? undefined, + orientation: item.exifInfo?.orientation ?? undefined, + + dateTimeOriginal: item.exifInfo?.dateTimeOriginal?.toISOString(), + modifyDate: item.exifInfo?.modifyDate?.toISOString(), + latitude: item.exifInfo?.latitude ?? undefined, + longitude: item.exifInfo?.longitude ?? undefined, + projectionType: item.exifInfo?.projectionType ?? undefined, + city: item.exifInfo?.city ?? undefined, + state: item.exifInfo?.state ?? undefined, + country: item.exifInfo?.country ?? undefined, + make: item.exifInfo?.make ?? undefined, + model: item.exifInfo?.model ?? undefined, + lensModel: item.exifInfo?.lensModel ?? undefined, + fNumber: item.exifInfo?.fNumber ?? undefined, + iso: item.exifInfo?.iso ?? undefined, + exposureTime: item.exifInfo?.exposureTime ?? undefined, + profileDescription: item.exifInfo?.profileDescription ?? undefined, + colorspace: item.exifInfo?.colorspace ?? undefined, + bitsPerSample: item.exifInfo?.bitsPerSample ?? undefined, + rating: item.exifInfo?.rating ?? undefined, + fps: item.exifInfo?.fps ?? undefined, + }), + mapAlbumAsset: (item: AlbumAssetEntity) => ({ + assetId: item.assetId, + albumId: item.albumId, + createdAt: createdAt(item), + }), + mapAlbumAssetDeleted: (item: DeletedEntity) => ({ + assetId: item.assetId, + albumId: item.albumId, + deletedAt: deletedAt(item), + }), +}; + +type CheckpointMap = Partial>; +type Loader = (options: SyncOptions) => Paginated; +type Mapper = (item: T) => R; +type SyncResponseWriteArgs = { + type: SyncResponseType; + load: Loader; + map: Mapper; + getTimestamp: Mapper; +}; + +const ackTypes: Record = { + [SyncResponseType.ActivityV1]: SyncAcknowledgeType.Activity, + [SyncResponseType.ActivityDeleteV1]: SyncAcknowledgeType.ActivityDelete, + + [SyncResponseType.AssetOwnerV1]: SyncAcknowledgeType.AssetOwner, + [SyncResponseType.AssetOwnerDeleteV1]: SyncAcknowledgeType.AssetOwnerDelete, + + [SyncResponseType.AssetPartnerV1]: SyncAcknowledgeType.AssetPartner, + [SyncResponseType.AssetPartnerDeleteV1]: SyncAcknowledgeType.AssetPartnerDelete, + + [SyncResponseType.AssetAlbumV1]: SyncAcknowledgeType.AssetAlbum, + [SyncResponseType.AssetAlbumDeleteV1]: SyncAcknowledgeType.AssetAlbumDelete, + + [SyncResponseType.AlbumV1]: SyncAcknowledgeType.Album, + [SyncResponseType.AlbumDeleteV1]: SyncAcknowledgeType.AlbumDelete, + + [SyncResponseType.MemoryV1]: SyncAcknowledgeType.Memory, + [SyncResponseType.MemoryDeleteV1]: SyncAcknowledgeType.MemoryDelete, + + [SyncResponseType.PartnerV1]: SyncAcknowledgeType.Partner, + [SyncResponseType.PartnerDeleteV1]: SyncAcknowledgeType.PartnerDelete, + + [SyncResponseType.PersonV1]: SyncAcknowledgeType.Person, + [SyncResponseType.PersonDeleteV1]: SyncAcknowledgeType.PersonDelete, + + [SyncResponseType.SharedLinkV1]: SyncAcknowledgeType.SharedLink, + [SyncResponseType.SharedLinkDeleteV1]: SyncAcknowledgeType.SharedLinkDelete, + + [SyncResponseType.StackV1]: SyncAcknowledgeType.Stack, + [SyncResponseType.StackDeleteV1]: SyncAcknowledgeType.StackDelete, + + [SyncResponseType.TagV1]: SyncAcknowledgeType.Tag, + [SyncResponseType.TagDeleteV1]: SyncAcknowledgeType.TagDelete, + + [SyncResponseType.UserV1]: SyncAcknowledgeType.User, + [SyncResponseType.UserDeleteV1]: SyncAcknowledgeType.UserDelete, + + // relation types + [SyncResponseType.AlbumAssetV1]: SyncAcknowledgeType.AlbumAsset, + [SyncResponseType.AlbumAssetDeleteV1]: SyncAcknowledgeType.AlbumAssetDelete, + + [SyncResponseType.AlbumUserV1]: SyncAcknowledgeType.AlbumUser, + [SyncResponseType.AlbumUserDeleteV1]: SyncAcknowledgeType.AlbumUserDelete, +}; + +const asAcknowledgeType = (type: SyncResponseType) => ackTypes[type]; + +class SyncResponseWriter { + private checkpoints?: CheckpointMap; + + constructor(private args: SyncResponseWriteArgs) {} + + async write({ stream, userId }: { stream: Writable; userId: string }) { + const { type: responseType, load, map, getTimestamp } = this.args; + const acknowledgeType = asAcknowledgeType(responseType); + let lastCheckpointTimestamp = this.getLastTimestamp(acknowledgeType); + + const pagination = usePagination(SYNC_PAGE_SIZE, (options) => + load({ ...options, userId, lastCheckpointTimestamp }), + ); + + for await (const items of pagination) { + for (const item of items) { + const timestamp = getTimestamp(item); + const ack = timestamp === lastCheckpointTimestamp ? undefined : { timestamp, type: responseType }; + stream.write(mapJsonLine({ type: responseType, data: map(item) || (item as unknown as R), ack })); + lastCheckpointTimestamp = timestamp; + } + } + } + + withCheckpoints(checkpoints: CheckpointMap) { + this.checkpoints = checkpoints; + return this; + } + + private getLastTimestamp(type: SyncAcknowledgeType) { + if (!this.checkpoints) { + throw new Error('checkpoints not set'); + } + + return this.checkpoints[type]?.lastTimestamp; + } +} export class SyncService extends BaseService { + async acknowledge(auth: AuthDto, dto: SyncAcknowledgeDto) { + const { id: sessionId } = this.assertSession(auth); + await this.syncRepository.upsertCheckpoint({ + sessionId, + type: dto.type, + lastTimestamp: dto.timestamp, + }); + } + + async stream(auth: AuthDto, stream: Writable, dto: SyncStreamDto) { + const { id: sessionId, userId } = this.assertSession(auth); + const session = await this.syncRepository.get(sessionId); + const checkpoints: CheckpointMap = Object.fromEntries( + (session?.checkpoints ?? []).map((item) => [item.type, item]), + ); + + const streamers: SyncResponseWriter[] = []; + + for (const type of dto.types) { + switch (type) { + case SyncResponseType.AssetOwnerV1: { + streamers.push( + new SyncResponseWriter({ + type: SyncResponseType.AssetOwnerV1, + load: (options) => this.syncRepository.getAssets(options), + map: (item) => v1.mapAsset(item), + getTimestamp: updatedAt, + }), + ); + break; + } + + case SyncResponseType.AssetOwnerDeleteV1: { + streamers.push( + new SyncResponseWriter({ + type: SyncResponseType.AssetOwnerDeleteV1, + load: (options) => this.syncRepository.getDeletedAssets(options), + map: v1.mapDeletedDto, + getTimestamp: deletedAt, + }), + ); + break; + } + + case SyncResponseType.AssetPartnerV1: { + const partnerIds = await getMyPartnerIds({ userId, repository: this.partnerRepository }); + streamers.push( + new SyncResponseWriter({ + type: SyncResponseType.AssetPartnerV1, + load: (options) => this.syncRepository.getDeletedAssetsPartner({ ...options, partnerIds }), + map: v1.mapDeletedDto, + getTimestamp: deletedAt, + }), + ); + break; + } + + case SyncResponseType.AssetPartnerDeleteV1: { + const partnerIds = await getMyPartnerIds({ userId, repository: this.partnerRepository }); + streamers.push( + new SyncResponseWriter({ + type: SyncResponseType.AssetPartnerDeleteV1, + load: (options) => this.syncRepository.getAssetsPartner({ ...options, partnerIds }), + map: (item) => mapAsset(item, { auth, stripMetadata: false }), + getTimestamp: updatedAt, + }), + ); + break; + } + + case SyncResponseType.AlbumV1: { + streamers.push( + new SyncResponseWriter({ + type: SyncResponseType.AlbumV1, + load: (options) => this.syncRepository.getAlbums(options), + map: (item) => mapAlbumWithoutAssets(item), + getTimestamp: updatedAt, + }), + ); + break; + } + + case SyncResponseType.AlbumDeleteV1: { + streamers.push( + new SyncResponseWriter({ + type: SyncResponseType.AlbumDeleteV1, + load: (options) => this.syncRepository.getDeletedAlbums(options), + map: v1.mapDeletedDto, + getTimestamp: deletedAt, + }), + ); + break; + } + + case SyncResponseType.AlbumAssetV1: { + streamers.push( + new SyncResponseWriter({ + type: SyncResponseType.AlbumAssetV1, + load: (options) => this.syncRepository.getAlbumAssets(options), + map: v1.mapAlbumAsset, + getTimestamp: createdAt, + }), + ); + } + + case SyncResponseType.AlbumAssetDeleteV1: { + streamers.push( + new SyncResponseWriter, SyncAlbumAssetDeleteDtoV1>({ + type: SyncResponseType.AlbumAssetDeleteV1, + load: (options) => this.syncRepository.getDeletedAlbumAssets(options), + map: v1.mapAlbumAssetDeleted, + getTimestamp: deletedAt, + }), + ); + } + + default: { + this.logger.warn(`Unsupported sync type: ${type}`); + break; + } + } + } + + for (const streamer of streamers) { + await streamer.withCheckpoints(checkpoints).write({ stream, userId }); + } + + stream.end(); + } + async getFullSync(auth: AuthDto, dto: AssetFullSyncDto): Promise { // mobile implementation is faster if this is a single id const userId = dto.userId || auth.user.id; @@ -71,4 +392,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; + } } diff --git a/server/src/utils/misc.ts b/server/src/utils/misc.ts index 6a64923a3bf7b1..89725d3e0ff366 100644 --- a/server/src/utils/misc.ts +++ b/server/src/utils/misc.ts @@ -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 { extraSyncModels } from 'src/dtos/sync.dto'; import { ImmichCookie, ImmichHeader, MetadataKey } from 'src/enum'; import { ILoggerRepository } from 'src/interfaces/logger.interface'; @@ -245,6 +246,7 @@ export const useSwagger = (app: INestApplication, { write }: { write: boolean }) const options: SwaggerDocumentOptions = { operationIdFactory: (controllerKey: string, methodKey: string) => methodKey, + extraModels: [...extraSyncModels], }; const specification = SwaggerModule.createDocument(app, config, options); diff --git a/server/test/repositories/sync.repository.mock.ts b/server/test/repositories/sync.repository.mock.ts new file mode 100644 index 00000000000000..112c02fd306755 --- /dev/null +++ b/server/test/repositories/sync.repository.mock.ts @@ -0,0 +1,21 @@ +import { ISyncRepository } from 'src/interfaces/sync.interface'; +import { Mocked, vitest } from 'vitest'; + +export const newSyncRepositoryMock = (): Mocked => { + return { + get: vitest.fn(), + upsertCheckpoint: vitest.fn(), + + getAssets: vitest.fn(), + getDeletedAssets: vitest.fn(), + + getAssetsPartner: vitest.fn(), + getDeletedAssetsPartner: vitest.fn(), + + getAlbums: vitest.fn(), + getDeletedAlbums: vitest.fn(), + + getAlbumAssets: vitest.fn(), + getDeletedAlbumAssets: vitest.fn(), + }; +}; diff --git a/server/test/utils.ts b/server/test/utils.ts index d37af5118d323e..b39153910e6a27 100644 --- a/server/test/utils.ts +++ b/server/test/utils.ts @@ -35,6 +35,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'; @@ -91,6 +92,7 @@ export const newTestService = ( const sharedLinkMock = newSharedLinkRepositoryMock(); const stackMock = newStackRepositoryMock(); const storageMock = newStorageRepositoryMock(); + const syncMock = newSyncRepositoryMock(); const systemMock = newSystemMetadataRepositoryMock(); const tagMock = newTagRepositoryMock(); const telemetryMock = newTelemetryRepositoryMock(); @@ -131,6 +133,7 @@ export const newTestService = ( sharedLinkMock, stackMock, storageMock, + syncMock, systemMock, tagMock, telemetryMock, @@ -173,6 +176,7 @@ export const newTestService = ( sharedLinkMock, stackMock, storageMock, + syncMock, systemMock, tagMock, telemetryMock, diff --git a/web/src/routes/+layout.svelte b/web/src/routes/+layout.svelte index 8f8bd033eb4126..eb4a28784887c3 100644 --- a/web/src/routes/+layout.svelte +++ b/web/src/routes/+layout.svelte @@ -29,6 +29,34 @@ $: if ($user) { openWebsocketConnection(); + + void fetch('/api/sync/stream', { + method: 'POST', + body: JSON.stringify({ types: ['AssetOwnerV1'] }), + 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(); }