mirror of
https://github.com/immich-app/immich.git
synced 2026-05-18 03:10:24 +03:00
chore!: duration in milliseconds (#28003)
* server changes * openapi * web changes * mobile changes * assume 3.0 client * deprecate * review feedback * update medium tests * linting
This commit is contained in:
@@ -32,8 +32,12 @@ export function generateThumbhash(rng: SeededRandom): string {
|
|||||||
return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join('');
|
return Array.from({ length: 10 }, () => rng.nextInt(0, 256).toString(16).padStart(2, '0')).join('');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateDuration(rng: SeededRandom): string {
|
export function generateDuration(rng: SeededRandom): number {
|
||||||
return `${rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS)}.${rng.nextInt(0, 1000).toString().padStart(3, '0')}`;
|
return (
|
||||||
|
rng.nextInt(GENERATION_CONSTANTS.MIN_VIDEO_DURATION_SECONDS, GENERATION_CONSTANTS.MAX_VIDEO_DURATION_SECONDS) *
|
||||||
|
1000 +
|
||||||
|
rng.nextInt(0, 1000)
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateUUID(): string {
|
export function generateUUID(): string {
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export type MockTimelineAsset = {
|
|||||||
isTrashed: boolean;
|
isTrashed: boolean;
|
||||||
isVideo: boolean;
|
isVideo: boolean;
|
||||||
isImage: boolean;
|
isImage: boolean;
|
||||||
duration: string | null;
|
duration: number | null;
|
||||||
projectionType: string | null;
|
projectionType: string | null;
|
||||||
livePhotoVideoId: string | null;
|
livePhotoVideoId: string | null;
|
||||||
city: string | null;
|
city: string | null;
|
||||||
|
|||||||
@@ -192,23 +192,23 @@ class SyncStreamService {
|
|||||||
case SyncEntityType.assetV1:
|
case SyncEntityType.assetV1:
|
||||||
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
final remoteSyncAssets = data.cast<SyncAssetV1>();
|
||||||
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
await _syncStreamRepository.updateAssetsV1(remoteSyncAssets);
|
||||||
await _runWithManageMediaPermission(
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
logContext: "Trashed Assets",
|
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||||
action: () async {
|
}
|
||||||
await _handleRemoteDeleted(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id));
|
return;
|
||||||
await _applyRemoteRestoreToLocal();
|
case SyncEntityType.assetV2:
|
||||||
},
|
final remoteSyncAssets = data.cast<SyncAssetV2>();
|
||||||
);
|
await _syncStreamRepository.updateAssetsV2(remoteSyncAssets);
|
||||||
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
|
await _syncAssetTrashStatus(remoteSyncAssets.where((e) => e.deletedAt != null).map((e) => e.id).toList());
|
||||||
|
}
|
||||||
return;
|
return;
|
||||||
case SyncEntityType.assetDeleteV1:
|
case SyncEntityType.assetDeleteV1:
|
||||||
await _runWithManageMediaPermission(
|
|
||||||
logContext: "Deleted Assets",
|
|
||||||
action: () async {
|
|
||||||
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>();
|
final remoteSyncAssets = data.cast<SyncAssetDeleteV1>();
|
||||||
await _handleRemoteDeleted(remoteSyncAssets.map((e) => e.assetId));
|
if (CurrentPlatform.isAndroid && Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
||||||
},
|
await _syncAssetDeletion(remoteSyncAssets.map((e) => e.assetId).toList());
|
||||||
);
|
}
|
||||||
return _syncStreamRepository.deleteAssetsV1(data.cast());
|
return _syncStreamRepository.deleteAssetsV1(remoteSyncAssets);
|
||||||
case SyncEntityType.assetExifV1:
|
case SyncEntityType.assetExifV1:
|
||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast());
|
return _syncStreamRepository.updateAssetsExifV1(data.cast());
|
||||||
case SyncEntityType.assetEditV1:
|
case SyncEntityType.assetEditV1:
|
||||||
@@ -221,8 +221,12 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.deleteAssetsMetadataV1(data.cast());
|
return _syncStreamRepository.deleteAssetsMetadataV1(data.cast());
|
||||||
case SyncEntityType.partnerAssetV1:
|
case SyncEntityType.partnerAssetV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner');
|
||||||
|
case SyncEntityType.partnerAssetV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner');
|
||||||
case SyncEntityType.partnerAssetBackfillV1:
|
case SyncEntityType.partnerAssetBackfillV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'partner backfill');
|
||||||
|
case SyncEntityType.partnerAssetBackfillV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'partner backfill');
|
||||||
case SyncEntityType.partnerAssetDeleteV1:
|
case SyncEntityType.partnerAssetDeleteV1:
|
||||||
return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
|
return _syncStreamRepository.deleteAssetsV1(data.cast(), debugLabel: "partner");
|
||||||
case SyncEntityType.partnerAssetExifV1:
|
case SyncEntityType.partnerAssetExifV1:
|
||||||
@@ -243,10 +247,16 @@ class SyncStreamService {
|
|||||||
return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
|
return _syncStreamRepository.deleteAlbumUsersV1(data.cast());
|
||||||
case SyncEntityType.albumAssetCreateV1:
|
case SyncEntityType.albumAssetCreateV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset create');
|
||||||
|
case SyncEntityType.albumAssetCreateV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset create');
|
||||||
case SyncEntityType.albumAssetUpdateV1:
|
case SyncEntityType.albumAssetUpdateV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset update');
|
||||||
|
case SyncEntityType.albumAssetUpdateV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset update');
|
||||||
case SyncEntityType.albumAssetBackfillV1:
|
case SyncEntityType.albumAssetBackfillV1:
|
||||||
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill');
|
return _syncStreamRepository.updateAssetsV1(data.cast(), debugLabel: 'album asset backfill');
|
||||||
|
case SyncEntityType.albumAssetBackfillV2:
|
||||||
|
return _syncStreamRepository.updateAssetsV2(data.cast(), debugLabel: 'album asset backfill');
|
||||||
case SyncEntityType.albumAssetExifCreateV1:
|
case SyncEntityType.albumAssetExifCreateV1:
|
||||||
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create');
|
return _syncStreamRepository.updateAssetsExifV1(data.cast(), debugLabel: 'album asset exif create');
|
||||||
case SyncEntityType.albumAssetExifUpdateV1:
|
case SyncEntityType.albumAssetExifUpdateV1:
|
||||||
@@ -348,6 +358,47 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> handleWsAssetUploadReadyV2Batch(List<dynamic> batchData) async {
|
||||||
|
if (batchData.isEmpty) return;
|
||||||
|
|
||||||
|
_logger.info('Processing batch of ${batchData.length} AssetUploadReadyV2 events');
|
||||||
|
|
||||||
|
final List<SyncAssetV2> assets = [];
|
||||||
|
final List<SyncAssetExifV1> exifs = [];
|
||||||
|
|
||||||
|
try {
|
||||||
|
for (final data in batchData) {
|
||||||
|
if (data is! Map<String, dynamic>) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final payload = data;
|
||||||
|
final assetData = payload['asset'];
|
||||||
|
final exifData = payload['exif'];
|
||||||
|
|
||||||
|
if (assetData == null || exifData == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
final asset = SyncAssetV2.fromJson(assetData);
|
||||||
|
final exif = SyncAssetExifV1.fromJson(exifData);
|
||||||
|
|
||||||
|
if (asset != null && exif != null) {
|
||||||
|
assets.add(asset);
|
||||||
|
exifs.add(exif);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (assets.isNotEmpty && exifs.isNotEmpty) {
|
||||||
|
await _syncStreamRepository.updateAssetsV2(assets, debugLabel: 'websocket-batch');
|
||||||
|
await _syncStreamRepository.updateAssetsExifV1(exifs, debugLabel: 'websocket-batch');
|
||||||
|
_logger.info('Successfully processed ${assets.length} assets in batch');
|
||||||
|
}
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error processing AssetUploadReadyV2 websocket batch events", error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> handleWsAssetEditReadyV1(dynamic data) async {
|
Future<void> handleWsAssetEditReadyV1(dynamic data) async {
|
||||||
_logger.info('Processing AssetEditReadyV1 event');
|
_logger.info('Processing AssetEditReadyV1 event');
|
||||||
|
|
||||||
@@ -388,6 +439,41 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> handleWsAssetEditReadyV2(dynamic data) async {
|
||||||
|
_logger.info('Processing AssetEditReadyV2 event');
|
||||||
|
|
||||||
|
try {
|
||||||
|
if (data is! Map<String, dynamic>) {
|
||||||
|
throw ArgumentError("Invalid data format for AssetEditReadyV2 event");
|
||||||
|
}
|
||||||
|
|
||||||
|
final payload = data;
|
||||||
|
|
||||||
|
if (payload['asset'] == null) {
|
||||||
|
throw ArgumentError("Missing 'asset' field in AssetEditReadyV2 event data");
|
||||||
|
}
|
||||||
|
|
||||||
|
final asset = SyncAssetV2.fromJson(payload['asset']);
|
||||||
|
if (asset == null) {
|
||||||
|
throw ArgumentError("Failed to parse 'asset' field in AssetEditReadyV2 event data");
|
||||||
|
}
|
||||||
|
|
||||||
|
final assetEdits = (payload['edit'] as List<dynamic>)
|
||||||
|
.map((e) => SyncAssetEditV1.fromJson(e))
|
||||||
|
.whereType<SyncAssetEditV1>()
|
||||||
|
.toList();
|
||||||
|
|
||||||
|
await _syncStreamRepository.updateAssetsV2([asset], debugLabel: 'websocket-edit');
|
||||||
|
await _syncStreamRepository.replaceAssetEditsV1(asset.id, assetEdits, debugLabel: 'websocket-edit');
|
||||||
|
|
||||||
|
_logger.info(
|
||||||
|
'Successfully processed AssetEditReadyV2 event for asset ${asset.id} with ${assetEdits.length} edits',
|
||||||
|
);
|
||||||
|
} catch (error, stackTrace) {
|
||||||
|
_logger.severe("Error processing AssetEditReadyV2 websocket event", error, stackTrace);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
|
Future<void> _handleRemoteDeleted(Iterable<String> remoteIds) async {
|
||||||
if (remoteIds.isEmpty) {
|
if (remoteIds.isEmpty) {
|
||||||
return Future.value();
|
return Future.value();
|
||||||
@@ -424,20 +510,22 @@ class SyncStreamService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> _runWithManageMediaPermission({
|
Future<void> _syncAssetTrashStatus(List<String> remoteIds) async {
|
||||||
required String logContext,
|
if (!(await _localFilesManager.hasManageMediaPermission())) {
|
||||||
required Future<void> Function() action,
|
_logger.warning("Syncing asset trash status cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
}) async {
|
|
||||||
if (!CurrentPlatform.isAndroid || !Store.get(StoreKey.manageLocalMediaAndroid, false)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
final hasPermission = await _localFilesManager.hasManageMediaPermission();
|
await _handleRemoteDeleted(remoteIds);
|
||||||
if (!hasPermission) {
|
await _applyRemoteRestoreToLocal();
|
||||||
_logger.warning("sync $logContext cannot proceed because MANAGE_MEDIA permission is missing");
|
}
|
||||||
|
|
||||||
|
Future<void> _syncAssetDeletion(List<String> remoteIds) async {
|
||||||
|
if (!(await _localFilesManager.hasManageMediaPermission())) {
|
||||||
|
_logger.warning("Syncing asset deletion cannot proceed because MANAGE_MEDIA permission is missing");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
await action();
|
await _handleRemoteDeleted(remoteIds);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -186,7 +186,7 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncWebsocketBatch(List<dynamic> batchData) {
|
Future<void> syncWebsocketBatchV1(List<dynamic> batchData) {
|
||||||
if (_syncWebsocketTask != null) {
|
if (_syncWebsocketTask != null) {
|
||||||
return _syncWebsocketTask!.future;
|
return _syncWebsocketTask!.future;
|
||||||
}
|
}
|
||||||
@@ -196,7 +196,17 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> syncWebsocketEdit(dynamic data) {
|
Future<void> syncWebsocketBatchV2(List<dynamic> batchData) {
|
||||||
|
if (_syncWebsocketTask != null) {
|
||||||
|
return _syncWebsocketTask!.future;
|
||||||
|
}
|
||||||
|
_syncWebsocketTask = _handleWsAssetUploadReadyV2Batch(batchData);
|
||||||
|
return _syncWebsocketTask!.whenComplete(() {
|
||||||
|
_syncWebsocketTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> syncWebsocketEditV1(dynamic data) {
|
||||||
if (_syncWebsocketTask != null) {
|
if (_syncWebsocketTask != null) {
|
||||||
return _syncWebsocketTask!.future;
|
return _syncWebsocketTask!.future;
|
||||||
}
|
}
|
||||||
@@ -206,6 +216,16 @@ class BackgroundSyncManager {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> syncWebsocketEditV2(dynamic data) {
|
||||||
|
if (_syncWebsocketTask != null) {
|
||||||
|
return _syncWebsocketTask!.future;
|
||||||
|
}
|
||||||
|
_syncWebsocketTask = _handleWsAssetEditReadyV2(data);
|
||||||
|
return _syncWebsocketTask!.whenComplete(() {
|
||||||
|
_syncWebsocketTask = null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> syncLinkedAlbum() {
|
Future<void> syncLinkedAlbum() {
|
||||||
if (_linkedAlbumSyncTask != null) {
|
if (_linkedAlbumSyncTask != null) {
|
||||||
return _linkedAlbumSyncTask!.future;
|
return _linkedAlbumSyncTask!.future;
|
||||||
@@ -242,7 +262,17 @@ Cancelable<void> _handleWsAssetUploadReadyV1Batch(List<dynamic> batchData) => ru
|
|||||||
debugLabel: 'websocket-batch',
|
debugLabel: 'websocket-batch',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cancelable<void> _handleWsAssetUploadReadyV2Batch(List<dynamic> batchData) => runInIsolateGentle(
|
||||||
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetUploadReadyV2Batch(batchData),
|
||||||
|
debugLabel: 'websocket-batch',
|
||||||
|
);
|
||||||
|
|
||||||
Cancelable<void> _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle(
|
Cancelable<void> _handleWsAssetEditReadyV1(dynamic data) => runInIsolateGentle(
|
||||||
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data),
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV1(data),
|
||||||
debugLabel: 'websocket-edit',
|
debugLabel: 'websocket-edit',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
Cancelable<void> _handleWsAssetEditReadyV2(dynamic data) => runInIsolateGentle(
|
||||||
|
computation: (ref) => ref.read(syncStreamServiceProvider).handleWsAssetEditReadyV2(data),
|
||||||
|
debugLabel: 'websocket-edit',
|
||||||
|
);
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
import 'package:immich_mobile/domain/models/asset/base_asset.model.dart';
|
||||||
import 'package:immich_mobile/domain/models/exif.model.dart';
|
import 'package:immich_mobile/domain/models/exif.model.dart';
|
||||||
import 'package:immich_mobile/extensions/string_extensions.dart';
|
|
||||||
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
import 'package:immich_mobile/infrastructure/utils/exif.converter.dart';
|
||||||
import 'package:openapi/api.dart' as api;
|
import 'package:openapi/api.dart' as api;
|
||||||
|
|
||||||
@@ -14,7 +13,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationMs: duration?.toDuration()?.inMilliseconds ?? 0,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
@@ -36,7 +35,7 @@ extension DTOToAsset on api.AssetResponseDto {
|
|||||||
updatedAt: updatedAt,
|
updatedAt: updatedAt,
|
||||||
ownerId: ownerId,
|
ownerId: ownerId,
|
||||||
visibility: visibility.toAssetVisibility(),
|
visibility: visibility.toAssetVisibility(),
|
||||||
durationMs: duration?.toDuration()?.inMilliseconds ?? 0,
|
durationMs: duration,
|
||||||
height: height?.toInt(),
|
height: height?.toInt(),
|
||||||
width: width?.toInt(),
|
width: width?.toInt(),
|
||||||
isFavorite: isFavorite,
|
isFavorite: isFavorite,
|
||||||
|
|||||||
@@ -46,19 +46,25 @@ class SyncApiRepository {
|
|||||||
types: [
|
types: [
|
||||||
SyncRequestType.authUsersV1,
|
SyncRequestType.authUsersV1,
|
||||||
SyncRequestType.usersV1,
|
SyncRequestType.usersV1,
|
||||||
SyncRequestType.assetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.assetsV2
|
||||||
|
: SyncRequestType.assetsV1,
|
||||||
SyncRequestType.assetExifsV1,
|
SyncRequestType.assetExifsV1,
|
||||||
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1,
|
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetEditsV1,
|
||||||
SyncRequestType.assetMetadataV1,
|
SyncRequestType.assetMetadataV1,
|
||||||
SyncRequestType.partnersV1,
|
SyncRequestType.partnersV1,
|
||||||
SyncRequestType.partnerAssetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.partnerAssetsV2
|
||||||
|
: SyncRequestType.partnerAssetsV1,
|
||||||
SyncRequestType.partnerAssetExifsV1,
|
SyncRequestType.partnerAssetExifsV1,
|
||||||
if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0))
|
if (serverVersion < const SemVer(major: 3, minor: 0, patch: 0))
|
||||||
SyncRequestType.albumsV1
|
SyncRequestType.albumsV1
|
||||||
else
|
else
|
||||||
SyncRequestType.albumsV2,
|
SyncRequestType.albumsV2,
|
||||||
SyncRequestType.albumUsersV1,
|
SyncRequestType.albumUsersV1,
|
||||||
SyncRequestType.albumAssetsV1,
|
serverVersion >= const SemVer(major: 3, minor: 0, patch: 0)
|
||||||
|
? SyncRequestType.albumAssetsV2
|
||||||
|
: SyncRequestType.albumAssetsV1,
|
||||||
SyncRequestType.albumAssetExifsV1,
|
SyncRequestType.albumAssetExifsV1,
|
||||||
SyncRequestType.albumToAssetsV1,
|
SyncRequestType.albumToAssetsV1,
|
||||||
SyncRequestType.memoriesV1,
|
SyncRequestType.memoriesV1,
|
||||||
@@ -67,8 +73,9 @@ class SyncApiRepository {
|
|||||||
SyncRequestType.partnerStacksV1,
|
SyncRequestType.partnerStacksV1,
|
||||||
SyncRequestType.userMetadataV1,
|
SyncRequestType.userMetadataV1,
|
||||||
SyncRequestType.peopleV1,
|
SyncRequestType.peopleV1,
|
||||||
if (serverVersion < const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV1,
|
serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)
|
||||||
if (serverVersion >= const SemVer(major: 2, minor: 6, patch: 0)) SyncRequestType.assetFacesV2,
|
? SyncRequestType.assetFacesV2
|
||||||
|
: SyncRequestType.assetFacesV1,
|
||||||
],
|
],
|
||||||
reset: shouldReset,
|
reset: shouldReset,
|
||||||
).toJson(),
|
).toJson(),
|
||||||
@@ -153,6 +160,7 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
SyncEntityType.partnerV1: SyncPartnerV1.fromJson,
|
||||||
SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson,
|
SyncEntityType.partnerDeleteV1: SyncPartnerDeleteV1.fromJson,
|
||||||
SyncEntityType.assetV1: SyncAssetV1.fromJson,
|
SyncEntityType.assetV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.assetV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
|
SyncEntityType.assetDeleteV1: SyncAssetDeleteV1.fromJson,
|
||||||
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.assetExifV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson,
|
SyncEntityType.assetEditV1: SyncAssetEditV1.fromJson,
|
||||||
@@ -160,7 +168,9 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson,
|
SyncEntityType.assetMetadataV1: SyncAssetMetadataV1.fromJson,
|
||||||
SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson,
|
SyncEntityType.assetMetadataDeleteV1: SyncAssetMetadataDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
|
SyncEntityType.partnerAssetV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.partnerAssetV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
|
SyncEntityType.partnerAssetBackfillV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.partnerAssetBackfillV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
|
SyncEntityType.partnerAssetDeleteV1: SyncAssetDeleteV1.fromJson,
|
||||||
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.partnerAssetExifV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.partnerAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
||||||
@@ -171,8 +181,11 @@ const _kResponseMap = <SyncEntityType, Function(Object)>{
|
|||||||
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
|
SyncEntityType.albumUserBackfillV1: SyncAlbumUserV1.fromJson,
|
||||||
SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson,
|
SyncEntityType.albumUserDeleteV1: SyncAlbumUserDeleteV1.fromJson,
|
||||||
SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetCreateV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetCreateV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetUpdateV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetUpdateV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson,
|
SyncEntityType.albumAssetBackfillV1: SyncAssetV1.fromJson,
|
||||||
|
SyncEntityType.albumAssetBackfillV2: SyncAssetV2.fromJson,
|
||||||
SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifCreateV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifUpdateV1: SyncAssetExifV1.fromJson,
|
||||||
SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
SyncEntityType.albumAssetExifBackfillV1: SyncAssetExifV1.fromJson,
|
||||||
|
|||||||
@@ -220,6 +220,44 @@ class SyncStreamRepository extends DriftDatabaseRepository {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Future<void> updateAssetsV2(Iterable<SyncAssetV2> data, {String debugLabel = 'user'}) async {
|
||||||
|
try {
|
||||||
|
await _db.batch((batch) {
|
||||||
|
for (final asset in data) {
|
||||||
|
final companion = RemoteAssetEntityCompanion(
|
||||||
|
name: Value(asset.originalFileName),
|
||||||
|
type: Value(asset.type.toAssetType()),
|
||||||
|
createdAt: Value.absentIfNull(asset.fileCreatedAt),
|
||||||
|
updatedAt: Value.absentIfNull(asset.fileModifiedAt),
|
||||||
|
durationMs: Value(asset.duration),
|
||||||
|
checksum: Value(asset.checksum),
|
||||||
|
isFavorite: Value(asset.isFavorite),
|
||||||
|
ownerId: Value(asset.ownerId),
|
||||||
|
localDateTime: Value(asset.localDateTime),
|
||||||
|
thumbHash: Value(asset.thumbhash),
|
||||||
|
deletedAt: Value(asset.deletedAt),
|
||||||
|
visibility: Value(asset.visibility.toAssetVisibility()),
|
||||||
|
livePhotoVideoId: Value(asset.livePhotoVideoId),
|
||||||
|
stackId: Value(asset.stackId),
|
||||||
|
libraryId: Value(asset.libraryId),
|
||||||
|
width: Value(asset.width),
|
||||||
|
height: Value(asset.height),
|
||||||
|
isEdited: Value(asset.isEdited),
|
||||||
|
);
|
||||||
|
|
||||||
|
batch.insert(
|
||||||
|
_db.remoteAssetEntity,
|
||||||
|
companion.copyWith(id: Value(asset.id)),
|
||||||
|
onConflict: DoUpdate((_) => companion),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} catch (error, stack) {
|
||||||
|
_logger.severe('Error: updateAssetsV2 - $debugLabel', error, stack);
|
||||||
|
rethrow;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
|
Future<void> updateAssetsExifV1(Iterable<SyncAssetExifV1> data, {String debugLabel = 'user'}) async {
|
||||||
try {
|
try {
|
||||||
await _db.batch((batch) {
|
await _db.batch((batch) {
|
||||||
|
|||||||
@@ -94,8 +94,10 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
state = const WebsocketState(isConnected: false, socket: null);
|
state = const WebsocketState(isConnected: false, socket: null);
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReady);
|
socket.on('AssetUploadReadyV1', _handleSyncAssetUploadReadyV1);
|
||||||
socket.on('AssetEditReadyV1', _handleSyncAssetEditReady);
|
socket.on('AssetUploadReadyV2', _handleSyncAssetUploadReadyV2);
|
||||||
|
socket.on('AssetEditReadyV1', _handleSyncAssetEditReadyV1);
|
||||||
|
socket.on('AssetEditReadyV2', _handleSyncAssetEditReadyV2);
|
||||||
socket.on('on_config_update', _handleOnConfigUpdate);
|
socket.on('on_config_update', _handleOnConfigUpdate);
|
||||||
socket.on('on_new_release', _handleReleaseUpdates);
|
socket.on('on_new_release', _handleReleaseUpdates);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -163,16 +165,25 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
_ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion);
|
_ref.read(serverInfoProvider.notifier).handleReleaseInfo(serverVersion, releaseVersion);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSyncAssetUploadReady(dynamic data) {
|
void _handleSyncAssetUploadReadyV1(dynamic data) {
|
||||||
_batchedAssetUploadReady.add(data);
|
_batchedAssetUploadReady.add(data);
|
||||||
_batchDebouncer.run(_processBatchedAssetUploadReady);
|
_batchDebouncer.run(_processBatchedAssetUploadReadyV1);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _handleSyncAssetEditReady(dynamic data) {
|
void _handleSyncAssetUploadReadyV2(dynamic data) {
|
||||||
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEdit(data));
|
_batchedAssetUploadReady.add(data);
|
||||||
|
_batchDebouncer.run(_processBatchedAssetUploadReadyV2);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _processBatchedAssetUploadReady() {
|
void _handleSyncAssetEditReadyV1(dynamic data) {
|
||||||
|
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV1(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _handleSyncAssetEditReadyV2(dynamic data) {
|
||||||
|
unawaited(_ref.read(backgroundSyncProvider).syncWebsocketEditV2(data));
|
||||||
|
}
|
||||||
|
|
||||||
|
void _processBatchedAssetUploadReadyV1() {
|
||||||
if (_batchedAssetUploadReady.isEmpty) {
|
if (_batchedAssetUploadReady.isEmpty) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -180,7 +191,7 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false);
|
final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false);
|
||||||
try {
|
try {
|
||||||
unawaited(
|
unawaited(
|
||||||
_ref.read(backgroundSyncProvider).syncWebsocketBatch(_batchedAssetUploadReady.toList()).then((_) {
|
_ref.read(backgroundSyncProvider).syncWebsocketBatchV1(_batchedAssetUploadReady.toList()).then((_) {
|
||||||
if (isSyncAlbumEnabled) {
|
if (isSyncAlbumEnabled) {
|
||||||
_ref.read(backgroundSyncProvider).syncLinkedAlbum();
|
_ref.read(backgroundSyncProvider).syncLinkedAlbum();
|
||||||
}
|
}
|
||||||
@@ -192,6 +203,27 @@ class WebsocketNotifier extends StateNotifier<WebsocketState> {
|
|||||||
|
|
||||||
_batchedAssetUploadReady.clear();
|
_batchedAssetUploadReady.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void _processBatchedAssetUploadReadyV2() {
|
||||||
|
if (_batchedAssetUploadReady.isEmpty) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
final isSyncAlbumEnabled = Store.get(StoreKey.syncAlbums, false);
|
||||||
|
try {
|
||||||
|
unawaited(
|
||||||
|
_ref.read(backgroundSyncProvider).syncWebsocketBatchV2(_batchedAssetUploadReady.toList()).then((_) {
|
||||||
|
if (isSyncAlbumEnabled) {
|
||||||
|
_ref.read(backgroundSyncProvider).syncLinkedAlbum();
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
_log.severe("Error processing batched AssetUploadReadyV2 events: $error");
|
||||||
|
}
|
||||||
|
|
||||||
|
_batchedAssetUploadReady.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebsocketState>((ref) {
|
final websocketProvider = StateNotifierProvider<WebsocketNotifier, WebsocketState>((ref) {
|
||||||
|
|||||||
Generated
+1
@@ -579,6 +579,7 @@ Class | Method | HTTP request | Description
|
|||||||
- [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md)
|
- [SyncAssetMetadataDeleteV1](doc//SyncAssetMetadataDeleteV1.md)
|
||||||
- [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md)
|
- [SyncAssetMetadataV1](doc//SyncAssetMetadataV1.md)
|
||||||
- [SyncAssetV1](doc//SyncAssetV1.md)
|
- [SyncAssetV1](doc//SyncAssetV1.md)
|
||||||
|
- [SyncAssetV2](doc//SyncAssetV2.md)
|
||||||
- [SyncAuthUserV1](doc//SyncAuthUserV1.md)
|
- [SyncAuthUserV1](doc//SyncAuthUserV1.md)
|
||||||
- [SyncEntityType](doc//SyncEntityType.md)
|
- [SyncEntityType](doc//SyncEntityType.md)
|
||||||
- [SyncMemoryAssetDeleteV1](doc//SyncMemoryAssetDeleteV1.md)
|
- [SyncMemoryAssetDeleteV1](doc//SyncMemoryAssetDeleteV1.md)
|
||||||
|
|||||||
Generated
+1
@@ -327,6 +327,7 @@ part 'model/sync_asset_face_v2.dart';
|
|||||||
part 'model/sync_asset_metadata_delete_v1.dart';
|
part 'model/sync_asset_metadata_delete_v1.dart';
|
||||||
part 'model/sync_asset_metadata_v1.dart';
|
part 'model/sync_asset_metadata_v1.dart';
|
||||||
part 'model/sync_asset_v1.dart';
|
part 'model/sync_asset_v1.dart';
|
||||||
|
part 'model/sync_asset_v2.dart';
|
||||||
part 'model/sync_auth_user_v1.dart';
|
part 'model/sync_auth_user_v1.dart';
|
||||||
part 'model/sync_entity_type.dart';
|
part 'model/sync_entity_type.dart';
|
||||||
part 'model/sync_memory_asset_delete_v1.dart';
|
part 'model/sync_memory_asset_delete_v1.dart';
|
||||||
|
|||||||
Generated
+6
-6
@@ -1234,8 +1234,8 @@ class AssetsApi {
|
|||||||
/// * [String] xImmichChecksum:
|
/// * [String] xImmichChecksum:
|
||||||
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
||||||
///
|
///
|
||||||
/// * [String] duration:
|
/// * [int] duration:
|
||||||
/// Duration (for videos)
|
/// Duration in milliseconds (for videos)
|
||||||
///
|
///
|
||||||
/// * [String] filename:
|
/// * [String] filename:
|
||||||
/// Filename
|
/// Filename
|
||||||
@@ -1253,7 +1253,7 @@ class AssetsApi {
|
|||||||
/// Sidecar file data
|
/// Sidecar file data
|
||||||
///
|
///
|
||||||
/// * [AssetVisibility] visibility:
|
/// * [AssetVisibility] visibility:
|
||||||
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
Future<Response> uploadAssetWithHttpInfo(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||||
// ignore: prefer_const_declarations
|
// ignore: prefer_const_declarations
|
||||||
final apiPath = r'/assets';
|
final apiPath = r'/assets';
|
||||||
|
|
||||||
@@ -1358,8 +1358,8 @@ class AssetsApi {
|
|||||||
/// * [String] xImmichChecksum:
|
/// * [String] xImmichChecksum:
|
||||||
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
/// sha1 checksum that can be used for duplicate detection before the file is uploaded
|
||||||
///
|
///
|
||||||
/// * [String] duration:
|
/// * [int] duration:
|
||||||
/// Duration (for videos)
|
/// Duration in milliseconds (for videos)
|
||||||
///
|
///
|
||||||
/// * [String] filename:
|
/// * [String] filename:
|
||||||
/// Filename
|
/// Filename
|
||||||
@@ -1377,7 +1377,7 @@ class AssetsApi {
|
|||||||
/// Sidecar file data
|
/// Sidecar file data
|
||||||
///
|
///
|
||||||
/// * [AssetVisibility] visibility:
|
/// * [AssetVisibility] visibility:
|
||||||
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, String? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
Future<AssetMediaResponseDto?> uploadAsset(MultipartFile assetData, DateTime fileCreatedAt, DateTime fileModifiedAt, { String? key, String? slug, String? xImmichChecksum, int? duration, String? filename, bool? isFavorite, String? livePhotoVideoId, List<AssetMetadataUpsertItemDto>? metadata, MultipartFile? sidecarData, AssetVisibility? visibility, }) async {
|
||||||
final response = await uploadAssetWithHttpInfo(assetData, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, );
|
final response = await uploadAssetWithHttpInfo(assetData, fileCreatedAt, fileModifiedAt, key: key, slug: slug, xImmichChecksum: xImmichChecksum, duration: duration, filename: filename, isFavorite: isFavorite, livePhotoVideoId: livePhotoVideoId, metadata: metadata, sidecarData: sidecarData, visibility: visibility, );
|
||||||
if (response.statusCode >= HttpStatus.badRequest) {
|
if (response.statusCode >= HttpStatus.badRequest) {
|
||||||
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
throw ApiException(response.statusCode, await _decodeBodyBytes(response));
|
||||||
|
|||||||
Generated
+2
@@ -700,6 +700,8 @@ class ApiClient {
|
|||||||
return SyncAssetMetadataV1.fromJson(value);
|
return SyncAssetMetadataV1.fromJson(value);
|
||||||
case 'SyncAssetV1':
|
case 'SyncAssetV1':
|
||||||
return SyncAssetV1.fromJson(value);
|
return SyncAssetV1.fromJson(value);
|
||||||
|
case 'SyncAssetV2':
|
||||||
|
return SyncAssetV2.fromJson(value);
|
||||||
case 'SyncAuthUserV1':
|
case 'SyncAuthUserV1':
|
||||||
return SyncAuthUserV1.fromJson(value);
|
return SyncAuthUserV1.fromJson(value);
|
||||||
case 'SyncEntityType':
|
case 'SyncEntityType':
|
||||||
|
|||||||
+6
-3
@@ -57,8 +57,11 @@ class AssetResponseDto {
|
|||||||
/// Duplicate group ID
|
/// Duplicate group ID
|
||||||
String? duplicateId;
|
String? duplicateId;
|
||||||
|
|
||||||
/// Video/gif duration in hh:mm:ss.SSS format (null for static images)
|
/// Video/gif duration in milliseconds (null for static images)
|
||||||
String? duration;
|
///
|
||||||
|
/// Minimum value: 0
|
||||||
|
/// Maximum value: 2147483647
|
||||||
|
int? duration;
|
||||||
|
|
||||||
///
|
///
|
||||||
/// Please note: This property should have been non-nullable! Since the specification file
|
/// Please note: This property should have been non-nullable! Since the specification file
|
||||||
@@ -343,7 +346,7 @@ class AssetResponseDto {
|
|||||||
checksum: mapValueOfType<String>(json, r'checksum')!,
|
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||||
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
createdAt: mapDateTime(json, r'createdAt', r'')!,
|
||||||
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
duplicateId: mapValueOfType<String>(json, r'duplicateId'),
|
||||||
duration: mapValueOfType<String>(json, r'duration'),
|
duration: mapValueOfType<int>(json, r'duration'),
|
||||||
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
exifInfo: ExifResponseDto.fromJson(json[r'exifInfo']),
|
||||||
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
|
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'')!,
|
||||||
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,
|
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'')!,
|
||||||
|
|||||||
+321
@@ -0,0 +1,321 @@
|
|||||||
|
//
|
||||||
|
// 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 SyncAssetV2 {
|
||||||
|
/// Returns a new [SyncAssetV2] instance.
|
||||||
|
SyncAssetV2({
|
||||||
|
required this.checksum,
|
||||||
|
required this.deletedAt,
|
||||||
|
required this.duration,
|
||||||
|
required this.fileCreatedAt,
|
||||||
|
required this.fileModifiedAt,
|
||||||
|
required this.height,
|
||||||
|
required this.id,
|
||||||
|
required this.isEdited,
|
||||||
|
required this.isFavorite,
|
||||||
|
required this.libraryId,
|
||||||
|
required this.livePhotoVideoId,
|
||||||
|
required this.localDateTime,
|
||||||
|
required this.originalFileName,
|
||||||
|
required this.ownerId,
|
||||||
|
required this.stackId,
|
||||||
|
required this.thumbhash,
|
||||||
|
required this.type,
|
||||||
|
required this.visibility,
|
||||||
|
required this.width,
|
||||||
|
});
|
||||||
|
|
||||||
|
/// Checksum
|
||||||
|
String checksum;
|
||||||
|
|
||||||
|
/// Deleted at
|
||||||
|
DateTime? deletedAt;
|
||||||
|
|
||||||
|
/// Duration
|
||||||
|
///
|
||||||
|
/// Minimum value: 0
|
||||||
|
/// Maximum value: 2147483647
|
||||||
|
int? duration;
|
||||||
|
|
||||||
|
/// File created at
|
||||||
|
DateTime? fileCreatedAt;
|
||||||
|
|
||||||
|
/// File modified at
|
||||||
|
DateTime? fileModifiedAt;
|
||||||
|
|
||||||
|
/// Asset height
|
||||||
|
///
|
||||||
|
/// Minimum value: -9007199254740991
|
||||||
|
/// Maximum value: 9007199254740991
|
||||||
|
int? height;
|
||||||
|
|
||||||
|
/// Asset ID
|
||||||
|
String id;
|
||||||
|
|
||||||
|
/// Is edited
|
||||||
|
bool isEdited;
|
||||||
|
|
||||||
|
/// Is favorite
|
||||||
|
bool isFavorite;
|
||||||
|
|
||||||
|
/// Library ID
|
||||||
|
String? libraryId;
|
||||||
|
|
||||||
|
/// Live photo video ID
|
||||||
|
String? livePhotoVideoId;
|
||||||
|
|
||||||
|
/// Local date time
|
||||||
|
DateTime? localDateTime;
|
||||||
|
|
||||||
|
/// Original file name
|
||||||
|
String originalFileName;
|
||||||
|
|
||||||
|
/// Owner ID
|
||||||
|
String ownerId;
|
||||||
|
|
||||||
|
/// Stack ID
|
||||||
|
String? stackId;
|
||||||
|
|
||||||
|
/// Thumbhash
|
||||||
|
String? thumbhash;
|
||||||
|
|
||||||
|
AssetTypeEnum type;
|
||||||
|
|
||||||
|
AssetVisibility visibility;
|
||||||
|
|
||||||
|
/// Asset width
|
||||||
|
///
|
||||||
|
/// Minimum value: -9007199254740991
|
||||||
|
/// Maximum value: 9007199254740991
|
||||||
|
int? width;
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool operator ==(Object other) => identical(this, other) || other is SyncAssetV2 &&
|
||||||
|
other.checksum == checksum &&
|
||||||
|
other.deletedAt == deletedAt &&
|
||||||
|
other.duration == duration &&
|
||||||
|
other.fileCreatedAt == fileCreatedAt &&
|
||||||
|
other.fileModifiedAt == fileModifiedAt &&
|
||||||
|
other.height == height &&
|
||||||
|
other.id == id &&
|
||||||
|
other.isEdited == isEdited &&
|
||||||
|
other.isFavorite == isFavorite &&
|
||||||
|
other.libraryId == libraryId &&
|
||||||
|
other.livePhotoVideoId == livePhotoVideoId &&
|
||||||
|
other.localDateTime == localDateTime &&
|
||||||
|
other.originalFileName == originalFileName &&
|
||||||
|
other.ownerId == ownerId &&
|
||||||
|
other.stackId == stackId &&
|
||||||
|
other.thumbhash == thumbhash &&
|
||||||
|
other.type == type &&
|
||||||
|
other.visibility == visibility &&
|
||||||
|
other.width == width;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int get hashCode =>
|
||||||
|
// ignore: unnecessary_parenthesis
|
||||||
|
(checksum.hashCode) +
|
||||||
|
(deletedAt == null ? 0 : deletedAt!.hashCode) +
|
||||||
|
(duration == null ? 0 : duration!.hashCode) +
|
||||||
|
(fileCreatedAt == null ? 0 : fileCreatedAt!.hashCode) +
|
||||||
|
(fileModifiedAt == null ? 0 : fileModifiedAt!.hashCode) +
|
||||||
|
(height == null ? 0 : height!.hashCode) +
|
||||||
|
(id.hashCode) +
|
||||||
|
(isEdited.hashCode) +
|
||||||
|
(isFavorite.hashCode) +
|
||||||
|
(libraryId == null ? 0 : libraryId!.hashCode) +
|
||||||
|
(livePhotoVideoId == null ? 0 : livePhotoVideoId!.hashCode) +
|
||||||
|
(localDateTime == null ? 0 : localDateTime!.hashCode) +
|
||||||
|
(originalFileName.hashCode) +
|
||||||
|
(ownerId.hashCode) +
|
||||||
|
(stackId == null ? 0 : stackId!.hashCode) +
|
||||||
|
(thumbhash == null ? 0 : thumbhash!.hashCode) +
|
||||||
|
(type.hashCode) +
|
||||||
|
(visibility.hashCode) +
|
||||||
|
(width == null ? 0 : width!.hashCode);
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'SyncAssetV2[checksum=$checksum, deletedAt=$deletedAt, duration=$duration, fileCreatedAt=$fileCreatedAt, fileModifiedAt=$fileModifiedAt, height=$height, id=$id, isEdited=$isEdited, isFavorite=$isFavorite, libraryId=$libraryId, livePhotoVideoId=$livePhotoVideoId, localDateTime=$localDateTime, originalFileName=$originalFileName, ownerId=$ownerId, stackId=$stackId, thumbhash=$thumbhash, type=$type, visibility=$visibility, width=$width]';
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
final json = <String, dynamic>{};
|
||||||
|
json[r'checksum'] = this.checksum;
|
||||||
|
if (this.deletedAt != null) {
|
||||||
|
json[r'deletedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||||
|
? this.deletedAt!.millisecondsSinceEpoch
|
||||||
|
: this.deletedAt!.toUtc().toIso8601String();
|
||||||
|
} else {
|
||||||
|
// json[r'deletedAt'] = null;
|
||||||
|
}
|
||||||
|
if (this.duration != null) {
|
||||||
|
json[r'duration'] = this.duration;
|
||||||
|
} else {
|
||||||
|
// json[r'duration'] = null;
|
||||||
|
}
|
||||||
|
if (this.fileCreatedAt != null) {
|
||||||
|
json[r'fileCreatedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||||
|
? this.fileCreatedAt!.millisecondsSinceEpoch
|
||||||
|
: this.fileCreatedAt!.toUtc().toIso8601String();
|
||||||
|
} else {
|
||||||
|
// json[r'fileCreatedAt'] = null;
|
||||||
|
}
|
||||||
|
if (this.fileModifiedAt != null) {
|
||||||
|
json[r'fileModifiedAt'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||||
|
? this.fileModifiedAt!.millisecondsSinceEpoch
|
||||||
|
: this.fileModifiedAt!.toUtc().toIso8601String();
|
||||||
|
} else {
|
||||||
|
// json[r'fileModifiedAt'] = null;
|
||||||
|
}
|
||||||
|
if (this.height != null) {
|
||||||
|
json[r'height'] = this.height;
|
||||||
|
} else {
|
||||||
|
// json[r'height'] = null;
|
||||||
|
}
|
||||||
|
json[r'id'] = this.id;
|
||||||
|
json[r'isEdited'] = this.isEdited;
|
||||||
|
json[r'isFavorite'] = this.isFavorite;
|
||||||
|
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.localDateTime != null) {
|
||||||
|
json[r'localDateTime'] = _isEpochMarker(r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/')
|
||||||
|
? this.localDateTime!.millisecondsSinceEpoch
|
||||||
|
: this.localDateTime!.toUtc().toIso8601String();
|
||||||
|
} else {
|
||||||
|
// json[r'localDateTime'] = null;
|
||||||
|
}
|
||||||
|
json[r'originalFileName'] = this.originalFileName;
|
||||||
|
json[r'ownerId'] = this.ownerId;
|
||||||
|
if (this.stackId != null) {
|
||||||
|
json[r'stackId'] = this.stackId;
|
||||||
|
} else {
|
||||||
|
// json[r'stackId'] = null;
|
||||||
|
}
|
||||||
|
if (this.thumbhash != null) {
|
||||||
|
json[r'thumbhash'] = this.thumbhash;
|
||||||
|
} else {
|
||||||
|
// json[r'thumbhash'] = null;
|
||||||
|
}
|
||||||
|
json[r'type'] = this.type;
|
||||||
|
json[r'visibility'] = this.visibility;
|
||||||
|
if (this.width != null) {
|
||||||
|
json[r'width'] = this.width;
|
||||||
|
} else {
|
||||||
|
// json[r'width'] = null;
|
||||||
|
}
|
||||||
|
return json;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a new [SyncAssetV2] instance and imports its values from
|
||||||
|
/// [value] if it's a [Map], null otherwise.
|
||||||
|
// ignore: prefer_constructors_over_static_methods
|
||||||
|
static SyncAssetV2? fromJson(dynamic value) {
|
||||||
|
upgradeDto(value, "SyncAssetV2");
|
||||||
|
if (value is Map) {
|
||||||
|
final json = value.cast<String, dynamic>();
|
||||||
|
|
||||||
|
return SyncAssetV2(
|
||||||
|
checksum: mapValueOfType<String>(json, r'checksum')!,
|
||||||
|
deletedAt: mapDateTime(json, r'deletedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||||
|
duration: mapValueOfType<int>(json, r'duration'),
|
||||||
|
fileCreatedAt: mapDateTime(json, r'fileCreatedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||||
|
fileModifiedAt: mapDateTime(json, r'fileModifiedAt', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||||
|
height: mapValueOfType<int>(json, r'height'),
|
||||||
|
id: mapValueOfType<String>(json, r'id')!,
|
||||||
|
isEdited: mapValueOfType<bool>(json, r'isEdited')!,
|
||||||
|
isFavorite: mapValueOfType<bool>(json, r'isFavorite')!,
|
||||||
|
libraryId: mapValueOfType<String>(json, r'libraryId'),
|
||||||
|
livePhotoVideoId: mapValueOfType<String>(json, r'livePhotoVideoId'),
|
||||||
|
localDateTime: mapDateTime(json, r'localDateTime', r'/^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$/'),
|
||||||
|
originalFileName: mapValueOfType<String>(json, r'originalFileName')!,
|
||||||
|
ownerId: mapValueOfType<String>(json, r'ownerId')!,
|
||||||
|
stackId: mapValueOfType<String>(json, r'stackId'),
|
||||||
|
thumbhash: mapValueOfType<String>(json, r'thumbhash'),
|
||||||
|
type: AssetTypeEnum.fromJson(json[r'type'])!,
|
||||||
|
visibility: AssetVisibility.fromJson(json[r'visibility'])!,
|
||||||
|
width: mapValueOfType<int>(json, r'width'),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
static List<SyncAssetV2> listFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final result = <SyncAssetV2>[];
|
||||||
|
if (json is List && json.isNotEmpty) {
|
||||||
|
for (final row in json) {
|
||||||
|
final value = SyncAssetV2.fromJson(row);
|
||||||
|
if (value != null) {
|
||||||
|
result.add(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result.toList(growable: growable);
|
||||||
|
}
|
||||||
|
|
||||||
|
static Map<String, SyncAssetV2> mapFromJson(dynamic json) {
|
||||||
|
final map = <String, SyncAssetV2>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
json = json.cast<String, dynamic>(); // ignore: parameter_assignments
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
final value = SyncAssetV2.fromJson(entry.value);
|
||||||
|
if (value != null) {
|
||||||
|
map[entry.key] = value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
// maps a json object with a list of SyncAssetV2-objects as value to a dart map
|
||||||
|
static Map<String, List<SyncAssetV2>> mapListFromJson(dynamic json, {bool growable = false,}) {
|
||||||
|
final map = <String, List<SyncAssetV2>>{};
|
||||||
|
if (json is Map && json.isNotEmpty) {
|
||||||
|
// ignore: parameter_assignments
|
||||||
|
json = json.cast<String, dynamic>();
|
||||||
|
for (final entry in json.entries) {
|
||||||
|
map[entry.key] = SyncAssetV2.listFromJson(entry.value, growable: growable,);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The list of required keys that must be present in a JSON.
|
||||||
|
static const requiredKeys = <String>{
|
||||||
|
'checksum',
|
||||||
|
'deletedAt',
|
||||||
|
'duration',
|
||||||
|
'fileCreatedAt',
|
||||||
|
'fileModifiedAt',
|
||||||
|
'height',
|
||||||
|
'id',
|
||||||
|
'isEdited',
|
||||||
|
'isFavorite',
|
||||||
|
'libraryId',
|
||||||
|
'livePhotoVideoId',
|
||||||
|
'localDateTime',
|
||||||
|
'originalFileName',
|
||||||
|
'ownerId',
|
||||||
|
'stackId',
|
||||||
|
'thumbhash',
|
||||||
|
'type',
|
||||||
|
'visibility',
|
||||||
|
'width',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
+18
@@ -27,6 +27,7 @@ class SyncEntityType {
|
|||||||
static const userV1 = SyncEntityType._(r'UserV1');
|
static const userV1 = SyncEntityType._(r'UserV1');
|
||||||
static const userDeleteV1 = SyncEntityType._(r'UserDeleteV1');
|
static const userDeleteV1 = SyncEntityType._(r'UserDeleteV1');
|
||||||
static const assetV1 = SyncEntityType._(r'AssetV1');
|
static const assetV1 = SyncEntityType._(r'AssetV1');
|
||||||
|
static const assetV2 = SyncEntityType._(r'AssetV2');
|
||||||
static const assetDeleteV1 = SyncEntityType._(r'AssetDeleteV1');
|
static const assetDeleteV1 = SyncEntityType._(r'AssetDeleteV1');
|
||||||
static const assetExifV1 = SyncEntityType._(r'AssetExifV1');
|
static const assetExifV1 = SyncEntityType._(r'AssetExifV1');
|
||||||
static const assetEditV1 = SyncEntityType._(r'AssetEditV1');
|
static const assetEditV1 = SyncEntityType._(r'AssetEditV1');
|
||||||
@@ -36,7 +37,9 @@ class SyncEntityType {
|
|||||||
static const partnerV1 = SyncEntityType._(r'PartnerV1');
|
static const partnerV1 = SyncEntityType._(r'PartnerV1');
|
||||||
static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1');
|
static const partnerDeleteV1 = SyncEntityType._(r'PartnerDeleteV1');
|
||||||
static const partnerAssetV1 = SyncEntityType._(r'PartnerAssetV1');
|
static const partnerAssetV1 = SyncEntityType._(r'PartnerAssetV1');
|
||||||
|
static const partnerAssetV2 = SyncEntityType._(r'PartnerAssetV2');
|
||||||
static const partnerAssetBackfillV1 = SyncEntityType._(r'PartnerAssetBackfillV1');
|
static const partnerAssetBackfillV1 = SyncEntityType._(r'PartnerAssetBackfillV1');
|
||||||
|
static const partnerAssetBackfillV2 = SyncEntityType._(r'PartnerAssetBackfillV2');
|
||||||
static const partnerAssetDeleteV1 = SyncEntityType._(r'PartnerAssetDeleteV1');
|
static const partnerAssetDeleteV1 = SyncEntityType._(r'PartnerAssetDeleteV1');
|
||||||
static const partnerAssetExifV1 = SyncEntityType._(r'PartnerAssetExifV1');
|
static const partnerAssetExifV1 = SyncEntityType._(r'PartnerAssetExifV1');
|
||||||
static const partnerAssetExifBackfillV1 = SyncEntityType._(r'PartnerAssetExifBackfillV1');
|
static const partnerAssetExifBackfillV1 = SyncEntityType._(r'PartnerAssetExifBackfillV1');
|
||||||
@@ -50,8 +53,11 @@ class SyncEntityType {
|
|||||||
static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1');
|
static const albumUserBackfillV1 = SyncEntityType._(r'AlbumUserBackfillV1');
|
||||||
static const albumUserDeleteV1 = SyncEntityType._(r'AlbumUserDeleteV1');
|
static const albumUserDeleteV1 = SyncEntityType._(r'AlbumUserDeleteV1');
|
||||||
static const albumAssetCreateV1 = SyncEntityType._(r'AlbumAssetCreateV1');
|
static const albumAssetCreateV1 = SyncEntityType._(r'AlbumAssetCreateV1');
|
||||||
|
static const albumAssetCreateV2 = SyncEntityType._(r'AlbumAssetCreateV2');
|
||||||
static const albumAssetUpdateV1 = SyncEntityType._(r'AlbumAssetUpdateV1');
|
static const albumAssetUpdateV1 = SyncEntityType._(r'AlbumAssetUpdateV1');
|
||||||
|
static const albumAssetUpdateV2 = SyncEntityType._(r'AlbumAssetUpdateV2');
|
||||||
static const albumAssetBackfillV1 = SyncEntityType._(r'AlbumAssetBackfillV1');
|
static const albumAssetBackfillV1 = SyncEntityType._(r'AlbumAssetBackfillV1');
|
||||||
|
static const albumAssetBackfillV2 = SyncEntityType._(r'AlbumAssetBackfillV2');
|
||||||
static const albumAssetExifCreateV1 = SyncEntityType._(r'AlbumAssetExifCreateV1');
|
static const albumAssetExifCreateV1 = SyncEntityType._(r'AlbumAssetExifCreateV1');
|
||||||
static const albumAssetExifUpdateV1 = SyncEntityType._(r'AlbumAssetExifUpdateV1');
|
static const albumAssetExifUpdateV1 = SyncEntityType._(r'AlbumAssetExifUpdateV1');
|
||||||
static const albumAssetExifBackfillV1 = SyncEntityType._(r'AlbumAssetExifBackfillV1');
|
static const albumAssetExifBackfillV1 = SyncEntityType._(r'AlbumAssetExifBackfillV1');
|
||||||
@@ -81,6 +87,7 @@ class SyncEntityType {
|
|||||||
userV1,
|
userV1,
|
||||||
userDeleteV1,
|
userDeleteV1,
|
||||||
assetV1,
|
assetV1,
|
||||||
|
assetV2,
|
||||||
assetDeleteV1,
|
assetDeleteV1,
|
||||||
assetExifV1,
|
assetExifV1,
|
||||||
assetEditV1,
|
assetEditV1,
|
||||||
@@ -90,7 +97,9 @@ class SyncEntityType {
|
|||||||
partnerV1,
|
partnerV1,
|
||||||
partnerDeleteV1,
|
partnerDeleteV1,
|
||||||
partnerAssetV1,
|
partnerAssetV1,
|
||||||
|
partnerAssetV2,
|
||||||
partnerAssetBackfillV1,
|
partnerAssetBackfillV1,
|
||||||
|
partnerAssetBackfillV2,
|
||||||
partnerAssetDeleteV1,
|
partnerAssetDeleteV1,
|
||||||
partnerAssetExifV1,
|
partnerAssetExifV1,
|
||||||
partnerAssetExifBackfillV1,
|
partnerAssetExifBackfillV1,
|
||||||
@@ -104,8 +113,11 @@ class SyncEntityType {
|
|||||||
albumUserBackfillV1,
|
albumUserBackfillV1,
|
||||||
albumUserDeleteV1,
|
albumUserDeleteV1,
|
||||||
albumAssetCreateV1,
|
albumAssetCreateV1,
|
||||||
|
albumAssetCreateV2,
|
||||||
albumAssetUpdateV1,
|
albumAssetUpdateV1,
|
||||||
|
albumAssetUpdateV2,
|
||||||
albumAssetBackfillV1,
|
albumAssetBackfillV1,
|
||||||
|
albumAssetBackfillV2,
|
||||||
albumAssetExifCreateV1,
|
albumAssetExifCreateV1,
|
||||||
albumAssetExifUpdateV1,
|
albumAssetExifUpdateV1,
|
||||||
albumAssetExifBackfillV1,
|
albumAssetExifBackfillV1,
|
||||||
@@ -170,6 +182,7 @@ class SyncEntityTypeTypeTransformer {
|
|||||||
case r'UserV1': return SyncEntityType.userV1;
|
case r'UserV1': return SyncEntityType.userV1;
|
||||||
case r'UserDeleteV1': return SyncEntityType.userDeleteV1;
|
case r'UserDeleteV1': return SyncEntityType.userDeleteV1;
|
||||||
case r'AssetV1': return SyncEntityType.assetV1;
|
case r'AssetV1': return SyncEntityType.assetV1;
|
||||||
|
case r'AssetV2': return SyncEntityType.assetV2;
|
||||||
case r'AssetDeleteV1': return SyncEntityType.assetDeleteV1;
|
case r'AssetDeleteV1': return SyncEntityType.assetDeleteV1;
|
||||||
case r'AssetExifV1': return SyncEntityType.assetExifV1;
|
case r'AssetExifV1': return SyncEntityType.assetExifV1;
|
||||||
case r'AssetEditV1': return SyncEntityType.assetEditV1;
|
case r'AssetEditV1': return SyncEntityType.assetEditV1;
|
||||||
@@ -179,7 +192,9 @@ class SyncEntityTypeTypeTransformer {
|
|||||||
case r'PartnerV1': return SyncEntityType.partnerV1;
|
case r'PartnerV1': return SyncEntityType.partnerV1;
|
||||||
case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1;
|
case r'PartnerDeleteV1': return SyncEntityType.partnerDeleteV1;
|
||||||
case r'PartnerAssetV1': return SyncEntityType.partnerAssetV1;
|
case r'PartnerAssetV1': return SyncEntityType.partnerAssetV1;
|
||||||
|
case r'PartnerAssetV2': return SyncEntityType.partnerAssetV2;
|
||||||
case r'PartnerAssetBackfillV1': return SyncEntityType.partnerAssetBackfillV1;
|
case r'PartnerAssetBackfillV1': return SyncEntityType.partnerAssetBackfillV1;
|
||||||
|
case r'PartnerAssetBackfillV2': return SyncEntityType.partnerAssetBackfillV2;
|
||||||
case r'PartnerAssetDeleteV1': return SyncEntityType.partnerAssetDeleteV1;
|
case r'PartnerAssetDeleteV1': return SyncEntityType.partnerAssetDeleteV1;
|
||||||
case r'PartnerAssetExifV1': return SyncEntityType.partnerAssetExifV1;
|
case r'PartnerAssetExifV1': return SyncEntityType.partnerAssetExifV1;
|
||||||
case r'PartnerAssetExifBackfillV1': return SyncEntityType.partnerAssetExifBackfillV1;
|
case r'PartnerAssetExifBackfillV1': return SyncEntityType.partnerAssetExifBackfillV1;
|
||||||
@@ -193,8 +208,11 @@ class SyncEntityTypeTypeTransformer {
|
|||||||
case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1;
|
case r'AlbumUserBackfillV1': return SyncEntityType.albumUserBackfillV1;
|
||||||
case r'AlbumUserDeleteV1': return SyncEntityType.albumUserDeleteV1;
|
case r'AlbumUserDeleteV1': return SyncEntityType.albumUserDeleteV1;
|
||||||
case r'AlbumAssetCreateV1': return SyncEntityType.albumAssetCreateV1;
|
case r'AlbumAssetCreateV1': return SyncEntityType.albumAssetCreateV1;
|
||||||
|
case r'AlbumAssetCreateV2': return SyncEntityType.albumAssetCreateV2;
|
||||||
case r'AlbumAssetUpdateV1': return SyncEntityType.albumAssetUpdateV1;
|
case r'AlbumAssetUpdateV1': return SyncEntityType.albumAssetUpdateV1;
|
||||||
|
case r'AlbumAssetUpdateV2': return SyncEntityType.albumAssetUpdateV2;
|
||||||
case r'AlbumAssetBackfillV1': return SyncEntityType.albumAssetBackfillV1;
|
case r'AlbumAssetBackfillV1': return SyncEntityType.albumAssetBackfillV1;
|
||||||
|
case r'AlbumAssetBackfillV2': return SyncEntityType.albumAssetBackfillV2;
|
||||||
case r'AlbumAssetExifCreateV1': return SyncEntityType.albumAssetExifCreateV1;
|
case r'AlbumAssetExifCreateV1': return SyncEntityType.albumAssetExifCreateV1;
|
||||||
case r'AlbumAssetExifUpdateV1': return SyncEntityType.albumAssetExifUpdateV1;
|
case r'AlbumAssetExifUpdateV1': return SyncEntityType.albumAssetExifUpdateV1;
|
||||||
case r'AlbumAssetExifBackfillV1': return SyncEntityType.albumAssetExifBackfillV1;
|
case r'AlbumAssetExifBackfillV1': return SyncEntityType.albumAssetExifBackfillV1;
|
||||||
|
|||||||
+9
@@ -28,8 +28,10 @@ class SyncRequestType {
|
|||||||
static const albumUsersV1 = SyncRequestType._(r'AlbumUsersV1');
|
static const albumUsersV1 = SyncRequestType._(r'AlbumUsersV1');
|
||||||
static const albumToAssetsV1 = SyncRequestType._(r'AlbumToAssetsV1');
|
static const albumToAssetsV1 = SyncRequestType._(r'AlbumToAssetsV1');
|
||||||
static const albumAssetsV1 = SyncRequestType._(r'AlbumAssetsV1');
|
static const albumAssetsV1 = SyncRequestType._(r'AlbumAssetsV1');
|
||||||
|
static const albumAssetsV2 = SyncRequestType._(r'AlbumAssetsV2');
|
||||||
static const albumAssetExifsV1 = SyncRequestType._(r'AlbumAssetExifsV1');
|
static const albumAssetExifsV1 = SyncRequestType._(r'AlbumAssetExifsV1');
|
||||||
static const assetsV1 = SyncRequestType._(r'AssetsV1');
|
static const assetsV1 = SyncRequestType._(r'AssetsV1');
|
||||||
|
static const assetsV2 = SyncRequestType._(r'AssetsV2');
|
||||||
static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1');
|
static const assetExifsV1 = SyncRequestType._(r'AssetExifsV1');
|
||||||
static const assetEditsV1 = SyncRequestType._(r'AssetEditsV1');
|
static const assetEditsV1 = SyncRequestType._(r'AssetEditsV1');
|
||||||
static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1');
|
static const assetMetadataV1 = SyncRequestType._(r'AssetMetadataV1');
|
||||||
@@ -38,6 +40,7 @@ class SyncRequestType {
|
|||||||
static const memoryToAssetsV1 = SyncRequestType._(r'MemoryToAssetsV1');
|
static const memoryToAssetsV1 = SyncRequestType._(r'MemoryToAssetsV1');
|
||||||
static const partnersV1 = SyncRequestType._(r'PartnersV1');
|
static const partnersV1 = SyncRequestType._(r'PartnersV1');
|
||||||
static const partnerAssetsV1 = SyncRequestType._(r'PartnerAssetsV1');
|
static const partnerAssetsV1 = SyncRequestType._(r'PartnerAssetsV1');
|
||||||
|
static const partnerAssetsV2 = SyncRequestType._(r'PartnerAssetsV2');
|
||||||
static const partnerAssetExifsV1 = SyncRequestType._(r'PartnerAssetExifsV1');
|
static const partnerAssetExifsV1 = SyncRequestType._(r'PartnerAssetExifsV1');
|
||||||
static const partnerStacksV1 = SyncRequestType._(r'PartnerStacksV1');
|
static const partnerStacksV1 = SyncRequestType._(r'PartnerStacksV1');
|
||||||
static const stacksV1 = SyncRequestType._(r'StacksV1');
|
static const stacksV1 = SyncRequestType._(r'StacksV1');
|
||||||
@@ -54,8 +57,10 @@ class SyncRequestType {
|
|||||||
albumUsersV1,
|
albumUsersV1,
|
||||||
albumToAssetsV1,
|
albumToAssetsV1,
|
||||||
albumAssetsV1,
|
albumAssetsV1,
|
||||||
|
albumAssetsV2,
|
||||||
albumAssetExifsV1,
|
albumAssetExifsV1,
|
||||||
assetsV1,
|
assetsV1,
|
||||||
|
assetsV2,
|
||||||
assetExifsV1,
|
assetExifsV1,
|
||||||
assetEditsV1,
|
assetEditsV1,
|
||||||
assetMetadataV1,
|
assetMetadataV1,
|
||||||
@@ -64,6 +69,7 @@ class SyncRequestType {
|
|||||||
memoryToAssetsV1,
|
memoryToAssetsV1,
|
||||||
partnersV1,
|
partnersV1,
|
||||||
partnerAssetsV1,
|
partnerAssetsV1,
|
||||||
|
partnerAssetsV2,
|
||||||
partnerAssetExifsV1,
|
partnerAssetExifsV1,
|
||||||
partnerStacksV1,
|
partnerStacksV1,
|
||||||
stacksV1,
|
stacksV1,
|
||||||
@@ -115,8 +121,10 @@ class SyncRequestTypeTypeTransformer {
|
|||||||
case r'AlbumUsersV1': return SyncRequestType.albumUsersV1;
|
case r'AlbumUsersV1': return SyncRequestType.albumUsersV1;
|
||||||
case r'AlbumToAssetsV1': return SyncRequestType.albumToAssetsV1;
|
case r'AlbumToAssetsV1': return SyncRequestType.albumToAssetsV1;
|
||||||
case r'AlbumAssetsV1': return SyncRequestType.albumAssetsV1;
|
case r'AlbumAssetsV1': return SyncRequestType.albumAssetsV1;
|
||||||
|
case r'AlbumAssetsV2': return SyncRequestType.albumAssetsV2;
|
||||||
case r'AlbumAssetExifsV1': return SyncRequestType.albumAssetExifsV1;
|
case r'AlbumAssetExifsV1': return SyncRequestType.albumAssetExifsV1;
|
||||||
case r'AssetsV1': return SyncRequestType.assetsV1;
|
case r'AssetsV1': return SyncRequestType.assetsV1;
|
||||||
|
case r'AssetsV2': return SyncRequestType.assetsV2;
|
||||||
case r'AssetExifsV1': return SyncRequestType.assetExifsV1;
|
case r'AssetExifsV1': return SyncRequestType.assetExifsV1;
|
||||||
case r'AssetEditsV1': return SyncRequestType.assetEditsV1;
|
case r'AssetEditsV1': return SyncRequestType.assetEditsV1;
|
||||||
case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1;
|
case r'AssetMetadataV1': return SyncRequestType.assetMetadataV1;
|
||||||
@@ -125,6 +133,7 @@ class SyncRequestTypeTypeTransformer {
|
|||||||
case r'MemoryToAssetsV1': return SyncRequestType.memoryToAssetsV1;
|
case r'MemoryToAssetsV1': return SyncRequestType.memoryToAssetsV1;
|
||||||
case r'PartnersV1': return SyncRequestType.partnersV1;
|
case r'PartnersV1': return SyncRequestType.partnersV1;
|
||||||
case r'PartnerAssetsV1': return SyncRequestType.partnerAssetsV1;
|
case r'PartnerAssetsV1': return SyncRequestType.partnerAssetsV1;
|
||||||
|
case r'PartnerAssetsV2': return SyncRequestType.partnerAssetsV2;
|
||||||
case r'PartnerAssetExifsV1': return SyncRequestType.partnerAssetExifsV1;
|
case r'PartnerAssetExifsV1': return SyncRequestType.partnerAssetExifsV1;
|
||||||
case r'PartnerStacksV1': return SyncRequestType.partnerStacksV1;
|
case r'PartnerStacksV1': return SyncRequestType.partnerStacksV1;
|
||||||
case r'StacksV1': return SyncRequestType.stacksV1;
|
case r'StacksV1': return SyncRequestType.stacksV1;
|
||||||
|
|||||||
@@ -39,8 +39,8 @@ class TimeBucketAssetResponseDto {
|
|||||||
/// Array of country names extracted from EXIF GPS data
|
/// Array of country names extracted from EXIF GPS data
|
||||||
List<String?> country;
|
List<String?> country;
|
||||||
|
|
||||||
/// Array of video/gif durations in hh:mm:ss.SSS format (null for static images)
|
/// Array of video/gif durations in milliseconds (null for static images)
|
||||||
List<String?> duration;
|
List<int?> duration;
|
||||||
|
|
||||||
/// Array of file creation timestamps in UTC
|
/// Array of file creation timestamps in UTC
|
||||||
List<String> fileCreatedAt;
|
List<String> fileCreatedAt;
|
||||||
@@ -172,7 +172,7 @@ class TimeBucketAssetResponseDto {
|
|||||||
? (json[r'country'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'country'] as Iterable).cast<String>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
duration: json[r'duration'] is Iterable
|
duration: json[r'duration'] is Iterable
|
||||||
? (json[r'duration'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'duration'] as Iterable).cast<int>().toList(growable: false)
|
||||||
: const [],
|
: const [],
|
||||||
fileCreatedAt: json[r'fileCreatedAt'] is Iterable
|
fileCreatedAt: json[r'fileCreatedAt'] is Iterable
|
||||||
? (json[r'fileCreatedAt'] as Iterable).cast<String>().toList(growable: false)
|
? (json[r'fileCreatedAt'] as Iterable).cast<String>().toList(growable: false)
|
||||||
|
|||||||
@@ -16261,8 +16261,10 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"duration": {
|
"duration": {
|
||||||
"description": "Duration (for videos)",
|
"description": "Duration in milliseconds (for videos)",
|
||||||
"type": "string"
|
"maximum": 2147483647,
|
||||||
|
"minimum": 0,
|
||||||
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"fileCreatedAt": {
|
"fileCreatedAt": {
|
||||||
"description": "File creation date",
|
"description": "File creation date",
|
||||||
@@ -16630,9 +16632,11 @@
|
|||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"duration": {
|
"duration": {
|
||||||
"description": "Video/gif duration in hh:mm:ss.SSS format (null for static images)",
|
"description": "Video/gif duration in milliseconds (null for static images)",
|
||||||
|
"maximum": 2147483647,
|
||||||
|
"minimum": 0,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "string"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"exifInfo": {
|
"exifInfo": {
|
||||||
"$ref": "#/components/schemas/ExifResponseDto"
|
"$ref": "#/components/schemas/ExifResponseDto"
|
||||||
@@ -23175,6 +23179,135 @@
|
|||||||
],
|
],
|
||||||
"type": "object"
|
"type": "object"
|
||||||
},
|
},
|
||||||
|
"SyncAssetV2": {
|
||||||
|
"properties": {
|
||||||
|
"checksum": {
|
||||||
|
"description": "Checksum",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"deletedAt": {
|
||||||
|
"description": "Deleted at",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"nullable": true,
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"duration": {
|
||||||
|
"description": "Duration",
|
||||||
|
"maximum": 2147483647,
|
||||||
|
"minimum": 0,
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"fileCreatedAt": {
|
||||||
|
"description": "File created at",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"nullable": true,
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"fileModifiedAt": {
|
||||||
|
"description": "File modified at",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"nullable": true,
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"height": {
|
||||||
|
"description": "Asset height",
|
||||||
|
"maximum": 9007199254740991,
|
||||||
|
"minimum": -9007199254740991,
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer"
|
||||||
|
},
|
||||||
|
"id": {
|
||||||
|
"description": "Asset ID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"isEdited": {
|
||||||
|
"description": "Is edited",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"isFavorite": {
|
||||||
|
"description": "Is favorite",
|
||||||
|
"type": "boolean"
|
||||||
|
},
|
||||||
|
"libraryId": {
|
||||||
|
"description": "Library ID",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"livePhotoVideoId": {
|
||||||
|
"description": "Live photo video ID",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"localDateTime": {
|
||||||
|
"description": "Local date time",
|
||||||
|
"example": "2024-01-01T00:00:00.000Z",
|
||||||
|
"format": "date-time",
|
||||||
|
"nullable": true,
|
||||||
|
"pattern": "^(?:(?:\\d\\d[2468][048]|\\d\\d[13579][26]|\\d\\d0[48]|[02468][048]00|[13579][26]00)-02-29|\\d{4}-(?:(?:0[13578]|1[02])-(?:0[1-9]|[12]\\d|3[01])|(?:0[469]|11)-(?:0[1-9]|[12]\\d|30)|(?:02)-(?:0[1-9]|1\\d|2[0-8])))T(?:(?:[01]\\d|2[0-3]):[0-5]\\d(?::[0-5]\\d(?:\\.\\d+)?)?(?:Z))$",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"originalFileName": {
|
||||||
|
"description": "Original file name",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"ownerId": {
|
||||||
|
"description": "Owner ID",
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"stackId": {
|
||||||
|
"description": "Stack ID",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"thumbhash": {
|
||||||
|
"description": "Thumbhash",
|
||||||
|
"nullable": true,
|
||||||
|
"type": "string"
|
||||||
|
},
|
||||||
|
"type": {
|
||||||
|
"$ref": "#/components/schemas/AssetTypeEnum"
|
||||||
|
},
|
||||||
|
"visibility": {
|
||||||
|
"$ref": "#/components/schemas/AssetVisibility"
|
||||||
|
},
|
||||||
|
"width": {
|
||||||
|
"description": "Asset width",
|
||||||
|
"maximum": 9007199254740991,
|
||||||
|
"minimum": -9007199254740991,
|
||||||
|
"nullable": true,
|
||||||
|
"type": "integer"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": [
|
||||||
|
"checksum",
|
||||||
|
"deletedAt",
|
||||||
|
"duration",
|
||||||
|
"fileCreatedAt",
|
||||||
|
"fileModifiedAt",
|
||||||
|
"height",
|
||||||
|
"id",
|
||||||
|
"isEdited",
|
||||||
|
"isFavorite",
|
||||||
|
"libraryId",
|
||||||
|
"livePhotoVideoId",
|
||||||
|
"localDateTime",
|
||||||
|
"originalFileName",
|
||||||
|
"ownerId",
|
||||||
|
"stackId",
|
||||||
|
"thumbhash",
|
||||||
|
"type",
|
||||||
|
"visibility",
|
||||||
|
"width"
|
||||||
|
],
|
||||||
|
"type": "object"
|
||||||
|
},
|
||||||
"SyncAuthUserV1": {
|
"SyncAuthUserV1": {
|
||||||
"properties": {
|
"properties": {
|
||||||
"avatarColor": {
|
"avatarColor": {
|
||||||
@@ -23275,6 +23408,7 @@
|
|||||||
"UserV1",
|
"UserV1",
|
||||||
"UserDeleteV1",
|
"UserDeleteV1",
|
||||||
"AssetV1",
|
"AssetV1",
|
||||||
|
"AssetV2",
|
||||||
"AssetDeleteV1",
|
"AssetDeleteV1",
|
||||||
"AssetExifV1",
|
"AssetExifV1",
|
||||||
"AssetEditV1",
|
"AssetEditV1",
|
||||||
@@ -23284,7 +23418,9 @@
|
|||||||
"PartnerV1",
|
"PartnerV1",
|
||||||
"PartnerDeleteV1",
|
"PartnerDeleteV1",
|
||||||
"PartnerAssetV1",
|
"PartnerAssetV1",
|
||||||
|
"PartnerAssetV2",
|
||||||
"PartnerAssetBackfillV1",
|
"PartnerAssetBackfillV1",
|
||||||
|
"PartnerAssetBackfillV2",
|
||||||
"PartnerAssetDeleteV1",
|
"PartnerAssetDeleteV1",
|
||||||
"PartnerAssetExifV1",
|
"PartnerAssetExifV1",
|
||||||
"PartnerAssetExifBackfillV1",
|
"PartnerAssetExifBackfillV1",
|
||||||
@@ -23298,8 +23434,11 @@
|
|||||||
"AlbumUserBackfillV1",
|
"AlbumUserBackfillV1",
|
||||||
"AlbumUserDeleteV1",
|
"AlbumUserDeleteV1",
|
||||||
"AlbumAssetCreateV1",
|
"AlbumAssetCreateV1",
|
||||||
|
"AlbumAssetCreateV2",
|
||||||
"AlbumAssetUpdateV1",
|
"AlbumAssetUpdateV1",
|
||||||
|
"AlbumAssetUpdateV2",
|
||||||
"AlbumAssetBackfillV1",
|
"AlbumAssetBackfillV1",
|
||||||
|
"AlbumAssetBackfillV2",
|
||||||
"AlbumAssetExifCreateV1",
|
"AlbumAssetExifCreateV1",
|
||||||
"AlbumAssetExifUpdateV1",
|
"AlbumAssetExifUpdateV1",
|
||||||
"AlbumAssetExifBackfillV1",
|
"AlbumAssetExifBackfillV1",
|
||||||
@@ -23591,8 +23730,10 @@
|
|||||||
"AlbumUsersV1",
|
"AlbumUsersV1",
|
||||||
"AlbumToAssetsV1",
|
"AlbumToAssetsV1",
|
||||||
"AlbumAssetsV1",
|
"AlbumAssetsV1",
|
||||||
|
"AlbumAssetsV2",
|
||||||
"AlbumAssetExifsV1",
|
"AlbumAssetExifsV1",
|
||||||
"AssetsV1",
|
"AssetsV1",
|
||||||
|
"AssetsV2",
|
||||||
"AssetExifsV1",
|
"AssetExifsV1",
|
||||||
"AssetEditsV1",
|
"AssetEditsV1",
|
||||||
"AssetMetadataV1",
|
"AssetMetadataV1",
|
||||||
@@ -23601,6 +23742,7 @@
|
|||||||
"MemoryToAssetsV1",
|
"MemoryToAssetsV1",
|
||||||
"PartnersV1",
|
"PartnersV1",
|
||||||
"PartnerAssetsV1",
|
"PartnerAssetsV1",
|
||||||
|
"PartnerAssetsV2",
|
||||||
"PartnerAssetExifsV1",
|
"PartnerAssetExifsV1",
|
||||||
"PartnerStacksV1",
|
"PartnerStacksV1",
|
||||||
"StacksV1",
|
"StacksV1",
|
||||||
@@ -24994,10 +25136,12 @@
|
|||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"duration": {
|
"duration": {
|
||||||
"description": "Array of video/gif durations in hh:mm:ss.SSS format (null for static images)",
|
"description": "Array of video/gif durations in milliseconds (null for static images)",
|
||||||
"items": {
|
"items": {
|
||||||
|
"maximum": 2147483647,
|
||||||
|
"minimum": 0,
|
||||||
"nullable": true,
|
"nullable": true,
|
||||||
"type": "string"
|
"type": "integer"
|
||||||
},
|
},
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -614,8 +614,8 @@ export type AssetMetadataUpsertItemDto = {
|
|||||||
export type AssetMediaCreateDto = {
|
export type AssetMediaCreateDto = {
|
||||||
/** Asset file data */
|
/** Asset file data */
|
||||||
assetData: Blob;
|
assetData: Blob;
|
||||||
/** Duration (for videos) */
|
/** Duration in milliseconds (for videos) */
|
||||||
duration?: string;
|
duration?: number;
|
||||||
/** File creation date */
|
/** File creation date */
|
||||||
fileCreatedAt: string;
|
fileCreatedAt: string;
|
||||||
/** File modification date */
|
/** File modification date */
|
||||||
@@ -854,8 +854,8 @@ export type AssetResponseDto = {
|
|||||||
createdAt: string;
|
createdAt: string;
|
||||||
/** Duplicate group ID */
|
/** Duplicate group ID */
|
||||||
duplicateId?: string | null;
|
duplicateId?: string | null;
|
||||||
/** Video/gif duration in hh:mm:ss.SSS format (null for static images) */
|
/** Video/gif duration in milliseconds (null for static images) */
|
||||||
duration: string | null;
|
duration: number | null;
|
||||||
exifInfo?: ExifResponseDto;
|
exifInfo?: ExifResponseDto;
|
||||||
/** The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. */
|
/** The actual UTC timestamp when the file was created/captured, preserving timezone information. This is the authoritative timestamp for chronological sorting within timeline groups. Combined with timezone data, this can be used to determine the exact moment the photo was taken. */
|
||||||
fileCreatedAt: string;
|
fileCreatedAt: string;
|
||||||
@@ -2673,8 +2673,8 @@ export type TimeBucketAssetResponseDto = {
|
|||||||
city: (string | null)[];
|
city: (string | null)[];
|
||||||
/** Array of country names extracted from EXIF GPS data */
|
/** Array of country names extracted from EXIF GPS data */
|
||||||
country: (string | null)[];
|
country: (string | null)[];
|
||||||
/** Array of video/gif durations in hh:mm:ss.SSS format (null for static images) */
|
/** Array of video/gif durations in milliseconds (null for static images) */
|
||||||
duration: (string | null)[];
|
duration: (number | null)[];
|
||||||
/** Array of file creation timestamps in UTC */
|
/** Array of file creation timestamps in UTC */
|
||||||
fileCreatedAt: string[];
|
fileCreatedAt: string[];
|
||||||
/** Array of asset IDs in the time bucket */
|
/** Array of asset IDs in the time bucket */
|
||||||
@@ -3075,6 +3075,44 @@ export type SyncAssetV1 = {
|
|||||||
/** Asset width */
|
/** Asset width */
|
||||||
width: number | null;
|
width: number | null;
|
||||||
};
|
};
|
||||||
|
export type SyncAssetV2 = {
|
||||||
|
/** Checksum */
|
||||||
|
checksum: string;
|
||||||
|
/** Deleted at */
|
||||||
|
deletedAt: string | null;
|
||||||
|
/** Duration */
|
||||||
|
duration: number | null;
|
||||||
|
/** File created at */
|
||||||
|
fileCreatedAt: string | null;
|
||||||
|
/** File modified at */
|
||||||
|
fileModifiedAt: string | null;
|
||||||
|
/** Asset height */
|
||||||
|
height: number | null;
|
||||||
|
/** Asset ID */
|
||||||
|
id: string;
|
||||||
|
/** Is edited */
|
||||||
|
isEdited: boolean;
|
||||||
|
/** Is favorite */
|
||||||
|
isFavorite: boolean;
|
||||||
|
/** Library ID */
|
||||||
|
libraryId: string | null;
|
||||||
|
/** Live photo video ID */
|
||||||
|
livePhotoVideoId: string | null;
|
||||||
|
/** Local date time */
|
||||||
|
localDateTime: string | null;
|
||||||
|
/** Original file name */
|
||||||
|
originalFileName: string;
|
||||||
|
/** Owner ID */
|
||||||
|
ownerId: string;
|
||||||
|
/** Stack ID */
|
||||||
|
stackId: string | null;
|
||||||
|
/** Thumbhash */
|
||||||
|
thumbhash: string | null;
|
||||||
|
"type": AssetTypeEnum;
|
||||||
|
visibility: AssetVisibility;
|
||||||
|
/** Asset width */
|
||||||
|
width: number | null;
|
||||||
|
};
|
||||||
export type SyncAuthUserV1 = {
|
export type SyncAuthUserV1 = {
|
||||||
avatarColor?: (UserAvatarColor) | null;
|
avatarColor?: (UserAvatarColor) | null;
|
||||||
/** User deleted at */
|
/** User deleted at */
|
||||||
@@ -7109,6 +7147,7 @@ export enum SyncEntityType {
|
|||||||
UserV1 = "UserV1",
|
UserV1 = "UserV1",
|
||||||
UserDeleteV1 = "UserDeleteV1",
|
UserDeleteV1 = "UserDeleteV1",
|
||||||
AssetV1 = "AssetV1",
|
AssetV1 = "AssetV1",
|
||||||
|
AssetV2 = "AssetV2",
|
||||||
AssetDeleteV1 = "AssetDeleteV1",
|
AssetDeleteV1 = "AssetDeleteV1",
|
||||||
AssetExifV1 = "AssetExifV1",
|
AssetExifV1 = "AssetExifV1",
|
||||||
AssetEditV1 = "AssetEditV1",
|
AssetEditV1 = "AssetEditV1",
|
||||||
@@ -7118,7 +7157,9 @@ export enum SyncEntityType {
|
|||||||
PartnerV1 = "PartnerV1",
|
PartnerV1 = "PartnerV1",
|
||||||
PartnerDeleteV1 = "PartnerDeleteV1",
|
PartnerDeleteV1 = "PartnerDeleteV1",
|
||||||
PartnerAssetV1 = "PartnerAssetV1",
|
PartnerAssetV1 = "PartnerAssetV1",
|
||||||
|
PartnerAssetV2 = "PartnerAssetV2",
|
||||||
PartnerAssetBackfillV1 = "PartnerAssetBackfillV1",
|
PartnerAssetBackfillV1 = "PartnerAssetBackfillV1",
|
||||||
|
PartnerAssetBackfillV2 = "PartnerAssetBackfillV2",
|
||||||
PartnerAssetDeleteV1 = "PartnerAssetDeleteV1",
|
PartnerAssetDeleteV1 = "PartnerAssetDeleteV1",
|
||||||
PartnerAssetExifV1 = "PartnerAssetExifV1",
|
PartnerAssetExifV1 = "PartnerAssetExifV1",
|
||||||
PartnerAssetExifBackfillV1 = "PartnerAssetExifBackfillV1",
|
PartnerAssetExifBackfillV1 = "PartnerAssetExifBackfillV1",
|
||||||
@@ -7132,8 +7173,11 @@ export enum SyncEntityType {
|
|||||||
AlbumUserBackfillV1 = "AlbumUserBackfillV1",
|
AlbumUserBackfillV1 = "AlbumUserBackfillV1",
|
||||||
AlbumUserDeleteV1 = "AlbumUserDeleteV1",
|
AlbumUserDeleteV1 = "AlbumUserDeleteV1",
|
||||||
AlbumAssetCreateV1 = "AlbumAssetCreateV1",
|
AlbumAssetCreateV1 = "AlbumAssetCreateV1",
|
||||||
|
AlbumAssetCreateV2 = "AlbumAssetCreateV2",
|
||||||
AlbumAssetUpdateV1 = "AlbumAssetUpdateV1",
|
AlbumAssetUpdateV1 = "AlbumAssetUpdateV1",
|
||||||
|
AlbumAssetUpdateV2 = "AlbumAssetUpdateV2",
|
||||||
AlbumAssetBackfillV1 = "AlbumAssetBackfillV1",
|
AlbumAssetBackfillV1 = "AlbumAssetBackfillV1",
|
||||||
|
AlbumAssetBackfillV2 = "AlbumAssetBackfillV2",
|
||||||
AlbumAssetExifCreateV1 = "AlbumAssetExifCreateV1",
|
AlbumAssetExifCreateV1 = "AlbumAssetExifCreateV1",
|
||||||
AlbumAssetExifUpdateV1 = "AlbumAssetExifUpdateV1",
|
AlbumAssetExifUpdateV1 = "AlbumAssetExifUpdateV1",
|
||||||
AlbumAssetExifBackfillV1 = "AlbumAssetExifBackfillV1",
|
AlbumAssetExifBackfillV1 = "AlbumAssetExifBackfillV1",
|
||||||
@@ -7163,8 +7207,10 @@ export enum SyncRequestType {
|
|||||||
AlbumUsersV1 = "AlbumUsersV1",
|
AlbumUsersV1 = "AlbumUsersV1",
|
||||||
AlbumToAssetsV1 = "AlbumToAssetsV1",
|
AlbumToAssetsV1 = "AlbumToAssetsV1",
|
||||||
AlbumAssetsV1 = "AlbumAssetsV1",
|
AlbumAssetsV1 = "AlbumAssetsV1",
|
||||||
|
AlbumAssetsV2 = "AlbumAssetsV2",
|
||||||
AlbumAssetExifsV1 = "AlbumAssetExifsV1",
|
AlbumAssetExifsV1 = "AlbumAssetExifsV1",
|
||||||
AssetsV1 = "AssetsV1",
|
AssetsV1 = "AssetsV1",
|
||||||
|
AssetsV2 = "AssetsV2",
|
||||||
AssetExifsV1 = "AssetExifsV1",
|
AssetExifsV1 = "AssetExifsV1",
|
||||||
AssetEditsV1 = "AssetEditsV1",
|
AssetEditsV1 = "AssetEditsV1",
|
||||||
AssetMetadataV1 = "AssetMetadataV1",
|
AssetMetadataV1 = "AssetMetadataV1",
|
||||||
@@ -7173,6 +7219,7 @@ export enum SyncRequestType {
|
|||||||
MemoryToAssetsV1 = "MemoryToAssetsV1",
|
MemoryToAssetsV1 = "MemoryToAssetsV1",
|
||||||
PartnersV1 = "PartnersV1",
|
PartnersV1 = "PartnersV1",
|
||||||
PartnerAssetsV1 = "PartnerAssetsV1",
|
PartnerAssetsV1 = "PartnerAssetsV1",
|
||||||
|
PartnerAssetsV2 = "PartnerAssetsV2",
|
||||||
PartnerAssetExifsV1 = "PartnerAssetExifsV1",
|
PartnerAssetExifsV1 = "PartnerAssetExifsV1",
|
||||||
PartnerStacksV1 = "PartnerStacksV1",
|
PartnerStacksV1 = "PartnerStacksV1",
|
||||||
StacksV1 = "StacksV1",
|
StacksV1 = "StacksV1",
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ export enum UploadFieldName {
|
|||||||
const AssetMediaBaseSchema = z.object({
|
const AssetMediaBaseSchema = z.object({
|
||||||
fileCreatedAt: isoDatetimeToDate.describe('File creation date'),
|
fileCreatedAt: isoDatetimeToDate.describe('File creation date'),
|
||||||
fileModifiedAt: isoDatetimeToDate.describe('File modification date'),
|
fileModifiedAt: isoDatetimeToDate.describe('File modification date'),
|
||||||
duration: z.string().optional().describe('Duration (for videos)'),
|
duration: z.int32().min(0).optional().describe('Duration in milliseconds (for videos)'),
|
||||||
filename: z.string().optional().describe('Filename'),
|
filename: z.string().optional().describe('Filename'),
|
||||||
/** The properties below are added to correctly generate the API docs and client SDKs. Validation should be handled in the controller. */
|
/** The properties below are added to correctly generate the API docs and client SDKs. Validation should be handled in the controller. */
|
||||||
[UploadFieldName.ASSET_DATA]: z.any().describe('Asset file data').meta({ type: 'string', format: 'binary' }),
|
[UploadFieldName.ASSET_DATA]: z.any().describe('Asset file data').meta({ type: 'string', format: 'binary' }),
|
||||||
|
|||||||
@@ -47,7 +47,7 @@ const SanitizedAssetResponseSchema = z
|
|||||||
.describe(
|
.describe(
|
||||||
'The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer\'s local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months.',
|
'The local date and time when the photo/video was taken, derived from EXIF metadata. This represents the photographer\'s local time regardless of timezone, stored as a timezone-agnostic timestamp. Used for timeline grouping by "local" days and months.',
|
||||||
),
|
),
|
||||||
duration: z.string().nullable().describe('Video/gif duration in hh:mm:ss.SSS format (null for static images)'),
|
duration: z.int32().min(0).nullable().describe('Video/gif duration in milliseconds (null for static images)'),
|
||||||
livePhotoVideoId: z.string().nullish().describe('Live photo video ID'),
|
livePhotoVideoId: z.string().nullish().describe('Live photo video ID'),
|
||||||
hasMetadata: z.boolean().describe('Whether asset has metadata'),
|
hasMetadata: z.boolean().describe('Whether asset has metadata'),
|
||||||
width: z.int().min(0).nullable().describe('Asset width'),
|
width: z.int().min(0).nullable().describe('Asset width'),
|
||||||
@@ -136,7 +136,7 @@ export type MapAsset = {
|
|||||||
checksum: Buffer<ArrayBufferLike>;
|
checksum: Buffer<ArrayBufferLike>;
|
||||||
checksumAlgorithm: ChecksumAlgorithm;
|
checksumAlgorithm: ChecksumAlgorithm;
|
||||||
duplicateId: string | null;
|
duplicateId: string | null;
|
||||||
duration: string | null;
|
duration: number | null;
|
||||||
edits?: ShallowDehydrateObject<AssetEditActionItem>[];
|
edits?: ShallowDehydrateObject<AssetEditActionItem>[];
|
||||||
exifInfo?: ShallowDehydrateObject<Selectable<Exif>> | null;
|
exifInfo?: ShallowDehydrateObject<Selectable<Exif>> | null;
|
||||||
faces?: ShallowDehydrateObject<AssetFace>[];
|
faces?: ShallowDehydrateObject<AssetFace>[];
|
||||||
|
|||||||
+32
-12
@@ -90,6 +90,30 @@ const SyncAssetV1Schema = z
|
|||||||
})
|
})
|
||||||
.meta({ id: 'SyncAssetV1' });
|
.meta({ id: 'SyncAssetV1' });
|
||||||
|
|
||||||
|
const SyncAssetV2Schema = z
|
||||||
|
.object({
|
||||||
|
id: z.string().describe('Asset ID'),
|
||||||
|
ownerId: z.string().describe('Owner ID'),
|
||||||
|
originalFileName: z.string().describe('Original file name'),
|
||||||
|
thumbhash: z.string().nullable().describe('Thumbhash'),
|
||||||
|
checksum: z.string().describe('Checksum'),
|
||||||
|
fileCreatedAt: isoDatetimeToDate.nullable().describe('File created at'),
|
||||||
|
fileModifiedAt: isoDatetimeToDate.nullable().describe('File modified at'),
|
||||||
|
localDateTime: isoDatetimeToDate.nullable().describe('Local date time'),
|
||||||
|
duration: z.int32().min(0).nullable().describe('Duration'),
|
||||||
|
type: AssetTypeSchema,
|
||||||
|
deletedAt: isoDatetimeToDate.nullable().describe('Deleted at'),
|
||||||
|
isFavorite: z.boolean().describe('Is favorite'),
|
||||||
|
visibility: AssetVisibilitySchema,
|
||||||
|
livePhotoVideoId: z.string().nullable().describe('Live photo video ID'),
|
||||||
|
stackId: z.string().nullable().describe('Stack ID'),
|
||||||
|
libraryId: z.string().nullable().describe('Library ID'),
|
||||||
|
width: z.int().nullable().describe('Asset width'),
|
||||||
|
height: z.int().nullable().describe('Asset height'),
|
||||||
|
isEdited: z.boolean().describe('Is edited'),
|
||||||
|
})
|
||||||
|
.meta({ id: 'SyncAssetV2' });
|
||||||
|
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncUserV1 extends createZodDto(SyncUserV1Schema) {}
|
class SyncUserV1 extends createZodDto(SyncUserV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
@@ -102,6 +126,8 @@ class SyncPartnerV1 extends createZodDto(SyncPartnerV1Schema) {}
|
|||||||
class SyncPartnerDeleteV1 extends createZodDto(SyncPartnerDeleteV1Schema) {}
|
class SyncPartnerDeleteV1 extends createZodDto(SyncPartnerDeleteV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
export class SyncAssetV1 extends createZodDto(SyncAssetV1Schema) {}
|
export class SyncAssetV1 extends createZodDto(SyncAssetV1Schema) {}
|
||||||
|
@ExtraModel()
|
||||||
|
export class SyncAssetV2 extends createZodDto(SyncAssetV2Schema) {}
|
||||||
|
|
||||||
const SyncAssetDeleteV1Schema = z
|
const SyncAssetDeleteV1Schema = z
|
||||||
.object({ assetId: z.string().describe('Asset ID') })
|
.object({ assetId: z.string().describe('Asset ID') })
|
||||||
@@ -394,12 +420,6 @@ class SyncPersonDeleteV1 extends createZodDto(SyncPersonDeleteV1Schema) {}
|
|||||||
class SyncAssetFaceV1 extends createZodDto(SyncAssetFaceV1Schema) {}
|
class SyncAssetFaceV1 extends createZodDto(SyncAssetFaceV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncAssetFaceV2 extends createZodDto(SyncAssetFaceV2Schema) {}
|
class SyncAssetFaceV2 extends createZodDto(SyncAssetFaceV2Schema) {}
|
||||||
|
|
||||||
export function syncAssetFaceV2ToV1(faceV2: SyncAssetFaceV2): SyncAssetFaceV1 {
|
|
||||||
const { deletedAt: _, isVisible: __, ...faceV1 } = faceV2;
|
|
||||||
|
|
||||||
return faceV1;
|
|
||||||
}
|
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
class SyncAssetFaceDeleteV1 extends createZodDto(SyncAssetFaceDeleteV1Schema) {}
|
class SyncAssetFaceDeleteV1 extends createZodDto(SyncAssetFaceDeleteV1Schema) {}
|
||||||
@ExtraModel()
|
@ExtraModel()
|
||||||
@@ -419,15 +439,15 @@ export type SyncItem = {
|
|||||||
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
|
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
|
||||||
[SyncEntityType.PartnerV1]: SyncPartnerV1;
|
[SyncEntityType.PartnerV1]: SyncPartnerV1;
|
||||||
[SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1;
|
[SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1;
|
||||||
[SyncEntityType.AssetV1]: SyncAssetV1;
|
[SyncEntityType.AssetV2]: SyncAssetV2;
|
||||||
[SyncEntityType.AssetDeleteV1]: SyncAssetDeleteV1;
|
[SyncEntityType.AssetDeleteV1]: SyncAssetDeleteV1;
|
||||||
[SyncEntityType.AssetMetadataV1]: SyncAssetMetadataV1;
|
[SyncEntityType.AssetMetadataV1]: SyncAssetMetadataV1;
|
||||||
[SyncEntityType.AssetMetadataDeleteV1]: SyncAssetMetadataDeleteV1;
|
[SyncEntityType.AssetMetadataDeleteV1]: SyncAssetMetadataDeleteV1;
|
||||||
[SyncEntityType.AssetExifV1]: SyncAssetExifV1;
|
[SyncEntityType.AssetExifV1]: SyncAssetExifV1;
|
||||||
[SyncEntityType.AssetEditV1]: SyncAssetEditV1;
|
[SyncEntityType.AssetEditV1]: SyncAssetEditV1;
|
||||||
[SyncEntityType.AssetEditDeleteV1]: SyncAssetEditDeleteV1;
|
[SyncEntityType.AssetEditDeleteV1]: SyncAssetEditDeleteV1;
|
||||||
[SyncEntityType.PartnerAssetV1]: SyncAssetV1;
|
[SyncEntityType.PartnerAssetV2]: SyncAssetV2;
|
||||||
[SyncEntityType.PartnerAssetBackfillV1]: SyncAssetV1;
|
[SyncEntityType.PartnerAssetBackfillV2]: SyncAssetV2;
|
||||||
[SyncEntityType.PartnerAssetDeleteV1]: SyncAssetDeleteV1;
|
[SyncEntityType.PartnerAssetDeleteV1]: SyncAssetDeleteV1;
|
||||||
[SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1;
|
[SyncEntityType.PartnerAssetExifV1]: SyncAssetExifV1;
|
||||||
[SyncEntityType.PartnerAssetExifBackfillV1]: SyncAssetExifV1;
|
[SyncEntityType.PartnerAssetExifBackfillV1]: SyncAssetExifV1;
|
||||||
@@ -437,9 +457,9 @@ export type SyncItem = {
|
|||||||
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
|
[SyncEntityType.AlbumUserV1]: SyncAlbumUserV1;
|
||||||
[SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1;
|
[SyncEntityType.AlbumUserBackfillV1]: SyncAlbumUserV1;
|
||||||
[SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1;
|
[SyncEntityType.AlbumUserDeleteV1]: SyncAlbumUserDeleteV1;
|
||||||
[SyncEntityType.AlbumAssetCreateV1]: SyncAssetV1;
|
[SyncEntityType.AlbumAssetCreateV2]: SyncAssetV2;
|
||||||
[SyncEntityType.AlbumAssetUpdateV1]: SyncAssetV1;
|
[SyncEntityType.AlbumAssetUpdateV2]: SyncAssetV2;
|
||||||
[SyncEntityType.AlbumAssetBackfillV1]: SyncAssetV1;
|
[SyncEntityType.AlbumAssetBackfillV2]: SyncAssetV2;
|
||||||
[SyncEntityType.AlbumAssetExifCreateV1]: SyncAssetExifV1;
|
[SyncEntityType.AlbumAssetExifCreateV1]: SyncAssetExifV1;
|
||||||
[SyncEntityType.AlbumAssetExifUpdateV1]: SyncAssetExifV1;
|
[SyncEntityType.AlbumAssetExifUpdateV1]: SyncAssetExifV1;
|
||||||
[SyncEntityType.AlbumAssetExifBackfillV1]: SyncAssetExifV1;
|
[SyncEntityType.AlbumAssetExifBackfillV1]: SyncAssetExifV1;
|
||||||
|
|||||||
@@ -89,8 +89,8 @@ const TimeBucketAssetResponseSchema = z
|
|||||||
"Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective.",
|
"Array of UTC offset hours at the time each photo was taken. Positive values are east of UTC, negative values are west of UTC. Values may be fractional (e.g., 5.5 for +05:30, -9.75 for -09:45). Applying this offset to 'fileCreatedAt' will give you the time the photo was taken from the photographer's perspective.",
|
||||||
),
|
),
|
||||||
duration: z
|
duration: z
|
||||||
.array(z.string().nullable())
|
.array(z.int32().min(0).nullable())
|
||||||
.describe('Array of video/gif durations in hh:mm:ss.SSS format (null for static images)'),
|
.describe('Array of video/gif durations in milliseconds (null for static images)'),
|
||||||
stack: z
|
stack: z
|
||||||
.array(stackTupleSchema)
|
.array(stackTupleSchema)
|
||||||
.optional()
|
.optional()
|
||||||
|
|||||||
@@ -806,9 +806,13 @@ export enum SyncRequestType {
|
|||||||
AlbumsV2 = 'AlbumsV2',
|
AlbumsV2 = 'AlbumsV2',
|
||||||
AlbumUsersV1 = 'AlbumUsersV1',
|
AlbumUsersV1 = 'AlbumUsersV1',
|
||||||
AlbumToAssetsV1 = 'AlbumToAssetsV1',
|
AlbumToAssetsV1 = 'AlbumToAssetsV1',
|
||||||
|
/** @deprecated */
|
||||||
AlbumAssetsV1 = 'AlbumAssetsV1',
|
AlbumAssetsV1 = 'AlbumAssetsV1',
|
||||||
|
AlbumAssetsV2 = 'AlbumAssetsV2',
|
||||||
AlbumAssetExifsV1 = 'AlbumAssetExifsV1',
|
AlbumAssetExifsV1 = 'AlbumAssetExifsV1',
|
||||||
|
/** @deprecated */
|
||||||
AssetsV1 = 'AssetsV1',
|
AssetsV1 = 'AssetsV1',
|
||||||
|
AssetsV2 = 'AssetsV2',
|
||||||
AssetExifsV1 = 'AssetExifsV1',
|
AssetExifsV1 = 'AssetExifsV1',
|
||||||
AssetEditsV1 = 'AssetEditsV1',
|
AssetEditsV1 = 'AssetEditsV1',
|
||||||
AssetMetadataV1 = 'AssetMetadataV1',
|
AssetMetadataV1 = 'AssetMetadataV1',
|
||||||
@@ -816,12 +820,15 @@ export enum SyncRequestType {
|
|||||||
MemoriesV1 = 'MemoriesV1',
|
MemoriesV1 = 'MemoriesV1',
|
||||||
MemoryToAssetsV1 = 'MemoryToAssetsV1',
|
MemoryToAssetsV1 = 'MemoryToAssetsV1',
|
||||||
PartnersV1 = 'PartnersV1',
|
PartnersV1 = 'PartnersV1',
|
||||||
|
/** @deprecated */
|
||||||
PartnerAssetsV1 = 'PartnerAssetsV1',
|
PartnerAssetsV1 = 'PartnerAssetsV1',
|
||||||
|
PartnerAssetsV2 = 'PartnerAssetsV2',
|
||||||
PartnerAssetExifsV1 = 'PartnerAssetExifsV1',
|
PartnerAssetExifsV1 = 'PartnerAssetExifsV1',
|
||||||
PartnerStacksV1 = 'PartnerStacksV1',
|
PartnerStacksV1 = 'PartnerStacksV1',
|
||||||
StacksV1 = 'StacksV1',
|
StacksV1 = 'StacksV1',
|
||||||
UsersV1 = 'UsersV1',
|
UsersV1 = 'UsersV1',
|
||||||
PeopleV1 = 'PeopleV1',
|
PeopleV1 = 'PeopleV1',
|
||||||
|
/** @deprecated */
|
||||||
AssetFacesV1 = 'AssetFacesV1',
|
AssetFacesV1 = 'AssetFacesV1',
|
||||||
AssetFacesV2 = 'AssetFacesV2',
|
AssetFacesV2 = 'AssetFacesV2',
|
||||||
UserMetadataV1 = 'UserMetadataV1',
|
UserMetadataV1 = 'UserMetadataV1',
|
||||||
@@ -838,7 +845,9 @@ export enum SyncEntityType {
|
|||||||
UserV1 = 'UserV1',
|
UserV1 = 'UserV1',
|
||||||
UserDeleteV1 = 'UserDeleteV1',
|
UserDeleteV1 = 'UserDeleteV1',
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
AssetV1 = 'AssetV1',
|
AssetV1 = 'AssetV1',
|
||||||
|
AssetV2 = 'AssetV2',
|
||||||
AssetDeleteV1 = 'AssetDeleteV1',
|
AssetDeleteV1 = 'AssetDeleteV1',
|
||||||
AssetExifV1 = 'AssetExifV1',
|
AssetExifV1 = 'AssetExifV1',
|
||||||
AssetEditV1 = 'AssetEditV1',
|
AssetEditV1 = 'AssetEditV1',
|
||||||
@@ -849,8 +858,12 @@ export enum SyncEntityType {
|
|||||||
PartnerV1 = 'PartnerV1',
|
PartnerV1 = 'PartnerV1',
|
||||||
PartnerDeleteV1 = 'PartnerDeleteV1',
|
PartnerDeleteV1 = 'PartnerDeleteV1',
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
PartnerAssetV1 = 'PartnerAssetV1',
|
PartnerAssetV1 = 'PartnerAssetV1',
|
||||||
|
PartnerAssetV2 = 'PartnerAssetV2',
|
||||||
|
/** @deprecated */
|
||||||
PartnerAssetBackfillV1 = 'PartnerAssetBackfillV1',
|
PartnerAssetBackfillV1 = 'PartnerAssetBackfillV1',
|
||||||
|
PartnerAssetBackfillV2 = 'PartnerAssetBackfillV2',
|
||||||
PartnerAssetDeleteV1 = 'PartnerAssetDeleteV1',
|
PartnerAssetDeleteV1 = 'PartnerAssetDeleteV1',
|
||||||
PartnerAssetExifV1 = 'PartnerAssetExifV1',
|
PartnerAssetExifV1 = 'PartnerAssetExifV1',
|
||||||
PartnerAssetExifBackfillV1 = 'PartnerAssetExifBackfillV1',
|
PartnerAssetExifBackfillV1 = 'PartnerAssetExifBackfillV1',
|
||||||
@@ -866,9 +879,15 @@ export enum SyncEntityType {
|
|||||||
AlbumUserBackfillV1 = 'AlbumUserBackfillV1',
|
AlbumUserBackfillV1 = 'AlbumUserBackfillV1',
|
||||||
AlbumUserDeleteV1 = 'AlbumUserDeleteV1',
|
AlbumUserDeleteV1 = 'AlbumUserDeleteV1',
|
||||||
|
|
||||||
|
/** @deprecated */
|
||||||
AlbumAssetCreateV1 = 'AlbumAssetCreateV1',
|
AlbumAssetCreateV1 = 'AlbumAssetCreateV1',
|
||||||
|
AlbumAssetCreateV2 = 'AlbumAssetCreateV2',
|
||||||
|
/** @deprecated */
|
||||||
AlbumAssetUpdateV1 = 'AlbumAssetUpdateV1',
|
AlbumAssetUpdateV1 = 'AlbumAssetUpdateV1',
|
||||||
|
AlbumAssetUpdateV2 = 'AlbumAssetUpdateV2',
|
||||||
|
/** @deprecated */
|
||||||
AlbumAssetBackfillV1 = 'AlbumAssetBackfillV1',
|
AlbumAssetBackfillV1 = 'AlbumAssetBackfillV1',
|
||||||
|
AlbumAssetBackfillV2 = 'AlbumAssetBackfillV2',
|
||||||
AlbumAssetExifCreateV1 = 'AlbumAssetExifCreateV1',
|
AlbumAssetExifCreateV1 = 'AlbumAssetExifCreateV1',
|
||||||
AlbumAssetExifUpdateV1 = 'AlbumAssetExifUpdateV1',
|
AlbumAssetExifUpdateV1 = 'AlbumAssetExifUpdateV1',
|
||||||
AlbumAssetExifBackfillV1 = 'AlbumAssetExifBackfillV1',
|
AlbumAssetExifBackfillV1 = 'AlbumAssetExifBackfillV1',
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
|||||||
import { AuthDto } from 'src/dtos/auth.dto';
|
import { AuthDto } from 'src/dtos/auth.dto';
|
||||||
import { NotificationDto } from 'src/dtos/notification.dto';
|
import { NotificationDto } from 'src/dtos/notification.dto';
|
||||||
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
import { ReleaseNotification, ServerVersionResponseDto } from 'src/dtos/server.dto';
|
||||||
import { SyncAssetEditV1, SyncAssetExifV1, SyncAssetV1 } from 'src/dtos/sync.dto';
|
import { SyncAssetEditV1, SyncAssetExifV1, SyncAssetV2 } from 'src/dtos/sync.dto';
|
||||||
import { AppRestartEvent, ArgsOf, EventRepository } from 'src/repositories/event.repository';
|
import { AppRestartEvent, ArgsOf, EventRepository } from 'src/repositories/event.repository';
|
||||||
import { LoggingRepository } from 'src/repositories/logging.repository';
|
import { LoggingRepository } from 'src/repositories/logging.repository';
|
||||||
import { handlePromiseError } from 'src/utils/misc';
|
import { handlePromiseError } from 'src/utils/misc';
|
||||||
@@ -35,9 +35,9 @@ export interface ClientEventMap {
|
|||||||
on_notification: [NotificationDto];
|
on_notification: [NotificationDto];
|
||||||
on_session_delete: [string];
|
on_session_delete: [string];
|
||||||
|
|
||||||
AssetUploadReadyV1: [{ asset: SyncAssetV1; exif: SyncAssetExifV1 }];
|
AssetUploadReadyV2: [{ asset: SyncAssetV2; exif: SyncAssetExifV1 }];
|
||||||
AppRestartV1: [AppRestartEvent];
|
AppRestartV1: [AppRestartEvent];
|
||||||
AssetEditReadyV1: [{ asset: SyncAssetV1; edit: SyncAssetEditV1[] }];
|
AssetEditReadyV2: [{ asset: SyncAssetV2; edit: SyncAssetEditV1[] }];
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AuthFn = (client: Socket) => Promise<AuthDto>;
|
export type AuthFn = (client: Socket) => Promise<AuthDto>;
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
import { Kysely, sql } from 'kysely';
|
||||||
|
|
||||||
|
export async function up(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`
|
||||||
|
ALTER TABLE asset
|
||||||
|
ALTER COLUMN duration TYPE integer
|
||||||
|
USING (
|
||||||
|
CASE
|
||||||
|
WHEN duration ~ '^\\d{2}:\\d{2}:\\d{2}\\.\\d{3}$'
|
||||||
|
THEN substr(duration, 1, 2)::int * 3600000
|
||||||
|
+ substr(duration, 4, 2)::int * 60000
|
||||||
|
+ substr(duration, 7, 2)::int * 1000
|
||||||
|
+ substr(duration, 10, 3)::int
|
||||||
|
END
|
||||||
|
);`.execute(db);
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function down(db: Kysely<any>): Promise<void> {
|
||||||
|
await sql`
|
||||||
|
ALTER TABLE asset
|
||||||
|
ALTER COLUMN duration TYPE varchar
|
||||||
|
USING (
|
||||||
|
CASE
|
||||||
|
WHEN duration IS NULL THEN NULL
|
||||||
|
ELSE lpad((duration / 3600000)::text, 2, '0')
|
||||||
|
|| ':' || lpad(((duration / 60000) % 60)::text, 2, '0')
|
||||||
|
|| ':' || lpad(((duration / 1000) % 60)::text, 2, '0')
|
||||||
|
|| '.' || lpad((duration % 1000)::text, 3, '0')
|
||||||
|
END
|
||||||
|
);`.execute(db);
|
||||||
|
}
|
||||||
@@ -83,8 +83,8 @@ export class AssetTable {
|
|||||||
@Column({ type: 'boolean', default: false })
|
@Column({ type: 'boolean', default: false })
|
||||||
isFavorite!: Generated<boolean>;
|
isFavorite!: Generated<boolean>;
|
||||||
|
|
||||||
@Column({ type: 'character varying', nullable: true })
|
@Column({ type: 'integer', nullable: true })
|
||||||
duration!: string | null;
|
duration!: number | null;
|
||||||
|
|
||||||
@Column({ type: 'bytea', index: true })
|
@Column({ type: 'bytea', index: true })
|
||||||
checksum!: Buffer; // sha1 checksum
|
checksum!: Buffer; // sha1 checksum
|
||||||
|
|||||||
@@ -101,7 +101,7 @@ export class JobService extends BaseService {
|
|||||||
const edits = await this.assetEditRepository.getWithSyncInfo(item.data.id);
|
const edits = await this.assetEditRepository.getWithSyncInfo(item.data.id);
|
||||||
|
|
||||||
if (asset) {
|
if (asset) {
|
||||||
this.websocketRepository.clientSend('AssetEditReadyV1', asset.ownerId, {
|
this.websocketRepository.clientSend('AssetEditReadyV2', asset.ownerId, {
|
||||||
asset: {
|
asset: {
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
ownerId: asset.ownerId,
|
ownerId: asset.ownerId,
|
||||||
@@ -156,7 +156,7 @@ export class JobService extends BaseService {
|
|||||||
this.websocketRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset));
|
this.websocketRepository.clientSend('on_upload_success', asset.ownerId, mapAsset(asset));
|
||||||
if (asset.exifInfo) {
|
if (asset.exifInfo) {
|
||||||
const exif = asset.exifInfo;
|
const exif = asset.exifInfo;
|
||||||
this.websocketRepository.clientSend('AssetUploadReadyV1', asset.ownerId, {
|
this.websocketRepository.clientSend('AssetUploadReadyV2', asset.ownerId, {
|
||||||
// TODO remove `on_upload_success` and then modify the query to select only the required fields)
|
// TODO remove `on_upload_success` and then modify the query to select only the required fields)
|
||||||
asset: {
|
asset: {
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
|
|||||||
@@ -999,7 +999,7 @@ describe(MetadataService.name, () => {
|
|||||||
expect(mocks.asset.update).toHaveBeenCalledWith(
|
expect(mocks.asset.update).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
duration: '00:00:06.210',
|
duration: 6210,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1067,7 +1067,7 @@ describe(MetadataService.name, () => {
|
|||||||
expect(mocks.asset.update).toHaveBeenCalledWith(
|
expect(mocks.asset.update).toHaveBeenCalledWith(
|
||||||
expect.objectContaining({
|
expect.objectContaining({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
duration: '168:00:00.000',
|
duration: 604_800_000,
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@@ -1080,7 +1080,7 @@ describe(MetadataService.name, () => {
|
|||||||
await sut.handleMetadataExtraction({ id: asset.id });
|
await sut.handleMetadataExtraction({ id: asset.id });
|
||||||
|
|
||||||
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1);
|
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:02:03.000' }));
|
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: 123_000 }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should prefer Duration from exif over sidecar', async () => {
|
it('should prefer Duration from exif over sidecar', async () => {
|
||||||
@@ -1092,7 +1092,7 @@ describe(MetadataService.name, () => {
|
|||||||
await sut.handleMetadataExtraction({ id: asset.id });
|
await sut.handleMetadataExtraction({ id: asset.id });
|
||||||
|
|
||||||
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(2);
|
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(2);
|
||||||
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:02:03.000' }));
|
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: 123_000 }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should ignore all Duration tags for definitely static images', async () => {
|
it('should ignore all Duration tags for definitely static images', async () => {
|
||||||
@@ -1121,7 +1121,7 @@ describe(MetadataService.name, () => {
|
|||||||
await sut.handleMetadataExtraction({ id: asset.id });
|
await sut.handleMetadataExtraction({ id: asset.id });
|
||||||
|
|
||||||
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1);
|
expect(mocks.metadata.readTags).toHaveBeenCalledTimes(1);
|
||||||
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: '00:07:36.000' }));
|
expect(mocks.asset.update).toHaveBeenCalledWith(expect.objectContaining({ duration: 456_000 }));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should trim whitespace from description', async () => {
|
it('should trim whitespace from description', async () => {
|
||||||
|
|||||||
@@ -1001,18 +1001,10 @@ export class MetadataService extends BaseService {
|
|||||||
return bitsPerSample;
|
return bitsPerSample;
|
||||||
}
|
}
|
||||||
|
|
||||||
private getDuration(tags: ImmichTags): string | null {
|
private getDuration(tags: ImmichTags): number | null {
|
||||||
const duration = tags.Duration;
|
const duration = tags.Duration;
|
||||||
|
const seconds = typeof duration === 'number' ? duration : Number.parseFloat(duration as string);
|
||||||
if (typeof duration === 'string') {
|
return Number.isFinite(seconds) ? Math.round(Duration.fromObject({ seconds }).toMillis()) : null;
|
||||||
return duration;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof duration === 'number') {
|
|
||||||
return Duration.fromObject({ seconds: duration }).toFormat('hh:mm:ss.SSS');
|
|
||||||
}
|
|
||||||
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getVideoTags(originalPath: string) {
|
private async getVideoTags(originalPath: string) {
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import {
|
|||||||
SyncAckDeleteDto,
|
SyncAckDeleteDto,
|
||||||
SyncAckSetDto,
|
SyncAckSetDto,
|
||||||
syncAlbumV2ToV1,
|
syncAlbumV2ToV1,
|
||||||
syncAssetFaceV2ToV1,
|
SyncAssetV2,
|
||||||
SyncAssetV1,
|
|
||||||
SyncItem,
|
SyncItem,
|
||||||
SyncStreamDto,
|
SyncStreamDto,
|
||||||
} from 'src/dtos/sync.dto';
|
} from 'src/dtos/sync.dto';
|
||||||
@@ -22,7 +21,7 @@ import { hexOrBufferToBase64 } from 'src/utils/bytes';
|
|||||||
import { fromAck, serialize, SerializeOptions, toAck } from 'src/utils/sync';
|
import { fromAck, serialize, SerializeOptions, toAck } from 'src/utils/sync';
|
||||||
|
|
||||||
type CheckpointMap = Partial<Record<SyncEntityType, SyncAck>>;
|
type CheckpointMap = Partial<Record<SyncEntityType, SyncAck>>;
|
||||||
type AssetLike = Omit<SyncAssetV1, 'checksum' | 'thumbhash'> & {
|
type AssetLike = Omit<SyncAssetV2, 'checksum' | 'thumbhash'> & {
|
||||||
checksum: Buffer<ArrayBufferLike>;
|
checksum: Buffer<ArrayBufferLike>;
|
||||||
thumbhash: Buffer<ArrayBufferLike> | null;
|
thumbhash: Buffer<ArrayBufferLike> | null;
|
||||||
};
|
};
|
||||||
@@ -31,7 +30,7 @@ const COMPLETE_ID = 'complete';
|
|||||||
const MAX_DAYS = 30;
|
const MAX_DAYS = 30;
|
||||||
const MAX_DURATION = Duration.fromObject({ days: MAX_DAYS });
|
const MAX_DURATION = Duration.fromObject({ days: MAX_DAYS });
|
||||||
|
|
||||||
const mapSyncAssetV1 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV1 => ({
|
const mapSyncAssetV2 = ({ checksum, thumbhash, ...data }: AssetLike): SyncAssetV2 => ({
|
||||||
...data,
|
...data,
|
||||||
checksum: hexOrBufferToBase64(checksum),
|
checksum: hexOrBufferToBase64(checksum),
|
||||||
thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null,
|
thumbhash: thumbhash ? hexOrBufferToBase64(thumbhash) : null,
|
||||||
@@ -56,10 +55,13 @@ export const SYNC_TYPES_ORDER = [
|
|||||||
SyncRequestType.UsersV1,
|
SyncRequestType.UsersV1,
|
||||||
SyncRequestType.PartnersV1,
|
SyncRequestType.PartnersV1,
|
||||||
SyncRequestType.AssetsV1,
|
SyncRequestType.AssetsV1,
|
||||||
|
SyncRequestType.AssetsV2,
|
||||||
SyncRequestType.StacksV1,
|
SyncRequestType.StacksV1,
|
||||||
SyncRequestType.PartnerAssetsV1,
|
SyncRequestType.PartnerAssetsV1,
|
||||||
|
SyncRequestType.PartnerAssetsV2,
|
||||||
SyncRequestType.PartnerStacksV1,
|
SyncRequestType.PartnerStacksV1,
|
||||||
SyncRequestType.AlbumAssetsV1,
|
SyncRequestType.AlbumAssetsV1,
|
||||||
|
SyncRequestType.AlbumAssetsV2,
|
||||||
SyncRequestType.AlbumsV1,
|
SyncRequestType.AlbumsV1,
|
||||||
SyncRequestType.AlbumsV2,
|
SyncRequestType.AlbumsV2,
|
||||||
SyncRequestType.AlbumUsersV1,
|
SyncRequestType.AlbumUsersV1,
|
||||||
@@ -156,20 +158,26 @@ export class SyncService extends BaseService {
|
|||||||
const options: SyncQueryOptions = { nowId, userId: auth.user.id };
|
const options: SyncQueryOptions = { nowId, userId: auth.user.id };
|
||||||
|
|
||||||
const handlers: Record<SyncRequestType, () => Promise<void>> = {
|
const handlers: Record<SyncRequestType, () => Promise<void>> = {
|
||||||
|
// deprecated handlers
|
||||||
|
[SyncRequestType.AssetsV1]: () => this.syncAssetsV1(),
|
||||||
|
[SyncRequestType.AssetFacesV1]: () => this.syncAssetFacesV1(),
|
||||||
|
[SyncRequestType.PartnerAssetsV1]: () => this.syncPartnerAssetsV1(),
|
||||||
|
[SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(),
|
||||||
|
|
||||||
[SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(options, response, checkpointMap),
|
[SyncRequestType.AuthUsersV1]: () => this.syncAuthUsersV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.UsersV1]: () => this.syncUsersV1(options, response, checkpointMap),
|
[SyncRequestType.UsersV1]: () => this.syncUsersV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.PartnersV1]: () => this.syncPartnersV1(options, response, checkpointMap),
|
[SyncRequestType.PartnersV1]: () => this.syncPartnersV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.AssetsV1]: () => this.syncAssetsV1(options, response, checkpointMap),
|
[SyncRequestType.AssetsV2]: () => this.syncAssetsV2(options, response, checkpointMap),
|
||||||
[SyncRequestType.AssetExifsV1]: () => this.syncAssetExifsV1(options, response, checkpointMap),
|
[SyncRequestType.AssetExifsV1]: () => this.syncAssetExifsV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.AssetEditsV1]: () => this.syncAssetEditsV1(options, response, checkpointMap),
|
[SyncRequestType.AssetEditsV1]: () => this.syncAssetEditsV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.PartnerAssetsV1]: () => this.syncPartnerAssetsV1(options, response, checkpointMap, session.id),
|
[SyncRequestType.PartnerAssetsV2]: () => this.syncPartnerAssetsV2(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.AssetMetadataV1]: () => this.syncAssetMetadataV1(options, response, checkpointMap, auth),
|
[SyncRequestType.AssetMetadataV1]: () => this.syncAssetMetadataV1(options, response, checkpointMap, auth),
|
||||||
[SyncRequestType.PartnerAssetExifsV1]: () =>
|
[SyncRequestType.PartnerAssetExifsV1]: () =>
|
||||||
this.syncPartnerAssetExifsV1(options, response, checkpointMap, session.id),
|
this.syncPartnerAssetExifsV1(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(options, response, checkpointMap),
|
[SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.AlbumsV2]: () => this.syncAlbumsV2(options, response, checkpointMap),
|
[SyncRequestType.AlbumsV2]: () => this.syncAlbumsV2(options, response, checkpointMap),
|
||||||
[SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(options, response, checkpointMap, session.id),
|
[SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(options, response, checkpointMap, session.id),
|
[SyncRequestType.AlbumAssetsV2]: () => this.syncAlbumAssetsV2(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id),
|
[SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.AlbumAssetExifsV1]: () =>
|
[SyncRequestType.AlbumAssetExifsV1]: () =>
|
||||||
this.syncAlbumAssetExifsV1(options, response, checkpointMap, session.id),
|
this.syncAlbumAssetExifsV1(options, response, checkpointMap, session.id),
|
||||||
@@ -178,13 +186,12 @@ export class SyncService extends BaseService {
|
|||||||
[SyncRequestType.StacksV1]: () => this.syncStackV1(options, response, checkpointMap),
|
[SyncRequestType.StacksV1]: () => this.syncStackV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.PartnerStacksV1]: () => this.syncPartnerStackV1(options, response, checkpointMap, session.id),
|
[SyncRequestType.PartnerStacksV1]: () => this.syncPartnerStackV1(options, response, checkpointMap, session.id),
|
||||||
[SyncRequestType.PeopleV1]: () => this.syncPeopleV1(options, response, checkpointMap),
|
[SyncRequestType.PeopleV1]: () => this.syncPeopleV1(options, response, checkpointMap),
|
||||||
[SyncRequestType.AssetFacesV1]: async () => this.syncAssetFacesV1(options, response, checkpointMap),
|
[SyncRequestType.AssetFacesV2]: () => this.syncAssetFacesV2(options, response, checkpointMap),
|
||||||
[SyncRequestType.AssetFacesV2]: async () => this.syncAssetFacesV2(options, response, checkpointMap),
|
|
||||||
[SyncRequestType.UserMetadataV1]: () => this.syncUserMetadataV1(options, response, checkpointMap),
|
[SyncRequestType.UserMetadataV1]: () => this.syncUserMetadataV1(options, response, checkpointMap),
|
||||||
};
|
} as const;
|
||||||
|
|
||||||
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
|
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
|
||||||
const handler = handlers[type];
|
const handler = handlers[type as keyof typeof handlers];
|
||||||
await handler();
|
await handler();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -260,21 +267,31 @@ export class SyncService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncAssetsV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
private syncAssetsV1(): Promise<void> {
|
||||||
|
throw new BadRequestException('SyncRequestType.AssetsV1 is deprecated, use SyncRequestType.AssetsV2 instead');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncAssetsV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
||||||
const deleteType = SyncEntityType.AssetDeleteV1;
|
const deleteType = SyncEntityType.AssetDeleteV1;
|
||||||
const deletes = this.syncRepository.asset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
|
const deletes = this.syncRepository.asset.getDeletes({ ...options, ack: checkpointMap[deleteType] });
|
||||||
for await (const { id, ...data } of deletes) {
|
for await (const { id, ...data } of deletes) {
|
||||||
send(response, { type: deleteType, ids: [id], data });
|
send(response, { type: deleteType, ids: [id], data });
|
||||||
}
|
}
|
||||||
|
|
||||||
const upsertType = SyncEntityType.AssetV1;
|
const upsertType = SyncEntityType.AssetV2;
|
||||||
const upserts = this.syncRepository.asset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
const upserts = this.syncRepository.asset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
||||||
for await (const { updateId, ...data } of upserts) {
|
for await (const { updateId, ...data } of upserts) {
|
||||||
send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) });
|
send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV2(data) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncPartnerAssetsV1(
|
private syncPartnerAssetsV1(): Promise<void> {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'SyncRequestType.PartnerAssetsV1 is deprecated, use SyncRequestType.PartnerAssetsV2 instead',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncPartnerAssetsV2(
|
||||||
options: SyncQueryOptions,
|
options: SyncQueryOptions,
|
||||||
response: Writable,
|
response: Writable,
|
||||||
checkpointMap: CheckpointMap,
|
checkpointMap: CheckpointMap,
|
||||||
@@ -286,13 +303,13 @@ export class SyncService extends BaseService {
|
|||||||
send(response, { type: deleteType, ids: [id], data });
|
send(response, { type: deleteType, ids: [id], data });
|
||||||
}
|
}
|
||||||
|
|
||||||
const backfillType = SyncEntityType.PartnerAssetBackfillV1;
|
const backfillType = SyncEntityType.PartnerAssetBackfillV2;
|
||||||
const backfillCheckpoint = checkpointMap[backfillType];
|
const backfillCheckpoint = checkpointMap[backfillType];
|
||||||
const partners = await this.syncRepository.partner.getCreatedAfter({
|
const partners = await this.syncRepository.partner.getCreatedAfter({
|
||||||
...options,
|
...options,
|
||||||
afterCreateId: backfillCheckpoint?.updateId,
|
afterCreateId: backfillCheckpoint?.updateId,
|
||||||
});
|
});
|
||||||
const upsertType = SyncEntityType.PartnerAssetV1;
|
const upsertType = SyncEntityType.PartnerAssetV2;
|
||||||
const upsertCheckpoint = checkpointMap[upsertType];
|
const upsertCheckpoint = checkpointMap[upsertType];
|
||||||
if (upsertCheckpoint) {
|
if (upsertCheckpoint) {
|
||||||
const endId = upsertCheckpoint.updateId;
|
const endId = upsertCheckpoint.updateId;
|
||||||
@@ -313,7 +330,7 @@ export class SyncService extends BaseService {
|
|||||||
send(response, {
|
send(response, {
|
||||||
type: backfillType,
|
type: backfillType,
|
||||||
ids: [createId, updateId],
|
ids: [createId, updateId],
|
||||||
data: mapSyncAssetV1(data),
|
data: mapSyncAssetV2(data),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -329,7 +346,7 @@ export class SyncService extends BaseService {
|
|||||||
|
|
||||||
const upserts = this.syncRepository.partnerAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
const upserts = this.syncRepository.partnerAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
||||||
for await (const { updateId, ...data } of upserts) {
|
for await (const { updateId, ...data } of upserts) {
|
||||||
send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) });
|
send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV2(data) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -490,20 +507,26 @@ export class SyncService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncAlbumAssetsV1(
|
private syncAlbumAssetsV1(): Promise<void> {
|
||||||
|
throw new BadRequestException(
|
||||||
|
'SyncRequestType.AlbumAssetsV1 is deprecated, use SyncRequestType.AlbumAssetsV2 instead',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async syncAlbumAssetsV2(
|
||||||
options: SyncQueryOptions,
|
options: SyncQueryOptions,
|
||||||
response: Writable,
|
response: Writable,
|
||||||
checkpointMap: CheckpointMap,
|
checkpointMap: CheckpointMap,
|
||||||
sessionId: string,
|
sessionId: string,
|
||||||
) {
|
) {
|
||||||
const backfillType = SyncEntityType.AlbumAssetBackfillV1;
|
const backfillType = SyncEntityType.AlbumAssetBackfillV2;
|
||||||
const backfillCheckpoint = checkpointMap[backfillType];
|
const backfillCheckpoint = checkpointMap[backfillType];
|
||||||
const albums = await this.syncRepository.album.getCreatedAfter({
|
const albums = await this.syncRepository.album.getCreatedAfter({
|
||||||
...options,
|
...options,
|
||||||
afterCreateId: backfillCheckpoint?.updateId,
|
afterCreateId: backfillCheckpoint?.updateId,
|
||||||
});
|
});
|
||||||
const updateType = SyncEntityType.AlbumAssetUpdateV1;
|
const updateType = SyncEntityType.AlbumAssetUpdateV2;
|
||||||
const createType = SyncEntityType.AlbumAssetCreateV1;
|
const createType = SyncEntityType.AlbumAssetCreateV2;
|
||||||
const updateCheckpoint = checkpointMap[updateType];
|
const updateCheckpoint = checkpointMap[updateType];
|
||||||
const createCheckpoint = checkpointMap[createType];
|
const createCheckpoint = checkpointMap[createType];
|
||||||
if (createCheckpoint) {
|
if (createCheckpoint) {
|
||||||
@@ -522,7 +545,7 @@ export class SyncService extends BaseService {
|
|||||||
);
|
);
|
||||||
|
|
||||||
for await (const { updateId, ...data } of backfill) {
|
for await (const { updateId, ...data } of backfill) {
|
||||||
send(response, { type: backfillType, ids: [createId, updateId], data: mapSyncAssetV1(data) });
|
send(response, { type: backfillType, ids: [createId, updateId], data: mapSyncAssetV2(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
sendEntityBackfillCompleteAck(response, backfillType, createId);
|
sendEntityBackfillCompleteAck(response, backfillType, createId);
|
||||||
@@ -541,7 +564,7 @@ export class SyncService extends BaseService {
|
|||||||
createCheckpoint,
|
createCheckpoint,
|
||||||
);
|
);
|
||||||
for await (const { updateId, ...data } of updates) {
|
for await (const { updateId, ...data } of updates) {
|
||||||
send(response, { type: updateType, ids: [updateId], data: mapSyncAssetV1(data) });
|
send(response, { type: updateType, ids: [updateId], data: mapSyncAssetV2(data) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -552,12 +575,12 @@ export class SyncService extends BaseService {
|
|||||||
send(response, {
|
send(response, {
|
||||||
type: SyncEntityType.SyncAckV1,
|
type: SyncEntityType.SyncAckV1,
|
||||||
data: {},
|
data: {},
|
||||||
ackType: SyncEntityType.AlbumAssetUpdateV1,
|
ackType: SyncEntityType.AlbumAssetUpdateV2,
|
||||||
ids: [options.nowId],
|
ids: [options.nowId],
|
||||||
});
|
});
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
send(response, { type: createType, ids: [updateId], data: mapSyncAssetV1(data) });
|
send(response, { type: createType, ids: [updateId], data: mapSyncAssetV2(data) });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -802,19 +825,10 @@ export class SyncService extends BaseService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncAssetFacesV1(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
private syncAssetFacesV1(): Promise<void> {
|
||||||
const deleteType = SyncEntityType.AssetFaceDeleteV1;
|
throw new BadRequestException(
|
||||||
const deletes = this.syncRepository.assetFace.getDeletes({ ...options, ack: checkpointMap[deleteType] });
|
'SyncRequestType.AssetFacesV1 is deprecated, use SyncRequestType.AssetFacesV2 instead',
|
||||||
for await (const { id, ...data } of deletes) {
|
);
|
||||||
send(response, { type: deleteType, ids: [id], data });
|
|
||||||
}
|
|
||||||
|
|
||||||
const upsertType = SyncEntityType.AssetFaceV1;
|
|
||||||
const upserts = this.syncRepository.assetFace.getUpserts({ ...options, ack: checkpointMap[upsertType] });
|
|
||||||
for await (const { updateId, ...data } of upserts) {
|
|
||||||
const v1 = syncAssetFaceV2ToV1(data);
|
|
||||||
send(response, { type: upsertType, ids: [updateId], data: v1 });
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async syncAssetFacesV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
private async syncAssetFacesV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) {
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const createAsset = (
|
|||||||
type: AssetType.Image,
|
type: AssetType.Image,
|
||||||
thumbhash: null,
|
thumbhash: null,
|
||||||
localDateTime: new Date().toISOString(),
|
localDateTime: new Date().toISOString(),
|
||||||
duration: '0:00:00.00000',
|
duration: 0,
|
||||||
hasMetadata: true,
|
hasMetadata: true,
|
||||||
width: 1920,
|
width: 1920,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
|
|||||||
@@ -15,13 +15,13 @@ const setup = async (db?: Kysely<DB>) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const updateSyncAck = {
|
const updateSyncAck = {
|
||||||
ack: expect.stringContaining(SyncEntityType.AlbumAssetUpdateV1),
|
ack: expect.stringContaining(SyncEntityType.AlbumAssetUpdateV2),
|
||||||
data: {},
|
data: {},
|
||||||
type: SyncEntityType.SyncAckV1,
|
type: SyncEntityType.SyncAckV1,
|
||||||
};
|
};
|
||||||
|
|
||||||
const backfillSyncAck = {
|
const backfillSyncAck = {
|
||||||
ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV1),
|
ack: expect.stringContaining(SyncEntityType.AlbumAssetBackfillV2),
|
||||||
data: {},
|
data: {},
|
||||||
type: SyncEntityType.SyncAckV1,
|
type: SyncEntityType.SyncAckV1,
|
||||||
};
|
};
|
||||||
@@ -30,7 +30,7 @@ beforeAll(async () => {
|
|||||||
defaultDatabase = await getKyselyDB();
|
defaultDatabase = await getKyselyDB();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(SyncRequestType.AlbumAssetsV1, () => {
|
describe(SyncRequestType.AlbumAssetsV2, () => {
|
||||||
it('should detect and sync the first album asset', async () => {
|
it('should detect and sync the first album asset', async () => {
|
||||||
const originalFileName = 'firstAsset';
|
const originalFileName = 'firstAsset';
|
||||||
const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
|
const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
|
||||||
@@ -48,7 +48,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
fileModifiedAt: date,
|
fileModifiedAt: date,
|
||||||
localDateTime: date,
|
localDateTime: date,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duration: '0:10:00.00000',
|
duration: 600_000,
|
||||||
livePhotoVideoId: null,
|
livePhotoVideoId: null,
|
||||||
stackId: null,
|
stackId: null,
|
||||||
libraryId: null,
|
libraryId: null,
|
||||||
@@ -59,7 +59,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||||
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
updateSyncAck,
|
updateSyncAck,
|
||||||
{
|
{
|
||||||
@@ -85,13 +85,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
height: asset.height,
|
height: asset.height,
|
||||||
isEdited: asset.isEdited,
|
isEdited: asset.isEdited,
|
||||||
},
|
},
|
||||||
type: SyncEntityType.AlbumAssetCreateV1,
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync album asset for own user', async () => {
|
it('should sync album asset for own user', async () => {
|
||||||
@@ -100,13 +100,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
const { album } = await ctx.newAlbum({ ownerId: auth.user.id });
|
const { album } = await ctx.newAlbum({ ownerId: auth.user.id });
|
||||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncAckV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncAckV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.AlbumAssetCreateV1 }),
|
expect.objectContaining({ type: SyncEntityType.AlbumAssetCreateV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -122,11 +122,11 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
const { session } = await ctx.newSession({ userId: user3.id });
|
const { session } = await ctx.newSession({ userId: user3.id });
|
||||||
const authUser3 = factory.auth({ session, user: user3 });
|
const authUser3 = factory.auth({ session, user: user3 });
|
||||||
|
|
||||||
await expect(ctx.syncStream(authUser3, [SyncRequestType.AssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(authUser3, [SyncRequestType.AssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should backfill album assets when a user shares an album with you', async () => {
|
it('should backfill album assets when a user shares an album with you', async () => {
|
||||||
@@ -147,7 +147,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
await wait(2);
|
await wait(2);
|
||||||
await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
updateSyncAck,
|
updateSyncAck,
|
||||||
{
|
{
|
||||||
@@ -155,7 +155,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: asset2User2.id,
|
id: asset2User2.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetCreateV1,
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
@@ -166,21 +166,21 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||||
|
|
||||||
// should backfill the album user
|
// should backfill the album user
|
||||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(newResponse).toEqual([
|
expect(newResponse).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: asset1User2.id,
|
id: asset1User2.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetBackfillV1,
|
type: SyncEntityType.AlbumAssetBackfillV2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: asset2User2.id,
|
id: asset2User2.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetBackfillV1,
|
type: SyncEntityType.AlbumAssetBackfillV2,
|
||||||
},
|
},
|
||||||
backfillSyncAck,
|
backfillSyncAck,
|
||||||
updateSyncAck,
|
updateSyncAck,
|
||||||
@@ -189,13 +189,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: asset3User2.id,
|
id: asset3User2.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetCreateV1,
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, newResponse);
|
await ctx.syncAckAll(auth, newResponse);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync old assets when a user adds them to an album they share you', async () => {
|
it('should sync old assets when a user adds them to an album they share you', async () => {
|
||||||
@@ -211,7 +211,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
await ctx.newAlbumAsset({ albumId: album1.id, assetId: album1Asset.id });
|
await ctx.newAlbumAsset({ albumId: album1.id, assetId: album1Asset.id });
|
||||||
await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
await ctx.newAlbumUser({ albumId: album1.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||||
|
|
||||||
const firstAlbumResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
const firstAlbumResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(firstAlbumResponse).toEqual([
|
expect(firstAlbumResponse).toEqual([
|
||||||
updateSyncAck,
|
updateSyncAck,
|
||||||
{
|
{
|
||||||
@@ -219,7 +219,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: album1Asset.id,
|
id: album1Asset.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetCreateV1,
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
@@ -228,14 +228,14 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
|
|
||||||
await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
await ctx.newAlbumUser({ albumId: album2.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: firstAsset.id,
|
id: firstAsset.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetBackfillV1,
|
type: SyncEntityType.AlbumAssetBackfillV2,
|
||||||
},
|
},
|
||||||
backfillSyncAck,
|
backfillSyncAck,
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
@@ -248,7 +248,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
await wait(2);
|
await wait(2);
|
||||||
|
|
||||||
// should backfill the new asset even though it's older than the first asset
|
// should backfill the new asset even though it's older than the first asset
|
||||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
const newResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(newResponse).toEqual([
|
expect(newResponse).toEqual([
|
||||||
updateSyncAck,
|
updateSyncAck,
|
||||||
{
|
{
|
||||||
@@ -256,13 +256,13 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: secondAsset.id,
|
id: secondAsset.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetCreateV1,
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, newResponse);
|
await ctx.syncAckAll(auth, newResponse);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should sync asset updates for an album shared with you', async () => {
|
it('should sync asset updates for an album shared with you', async () => {
|
||||||
@@ -274,7 +274,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
await ctx.newAlbumAsset({ albumId: album.id, assetId: asset.id });
|
||||||
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
await ctx.newAlbumUser({ albumId: album.id, userId: auth.user.id, role: AlbumUserRole.Editor });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
updateSyncAck,
|
updateSyncAck,
|
||||||
{
|
{
|
||||||
@@ -282,7 +282,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: asset.id,
|
id: asset.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetCreateV1,
|
type: SyncEntityType.AlbumAssetCreateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
@@ -296,7 +296,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV1]);
|
const updateResponse = await ctx.syncStream(auth, [SyncRequestType.AlbumAssetsV2]);
|
||||||
expect(updateResponse).toEqual([
|
expect(updateResponse).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -304,7 +304,7 @@ describe(SyncRequestType.AlbumAssetsV1, () => {
|
|||||||
id: asset.id,
|
id: asset.id,
|
||||||
isFavorite: true,
|
isFavorite: true,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.AlbumAssetUpdateV1,
|
type: SyncEntityType.AlbumAssetUpdateV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|||||||
@@ -18,14 +18,14 @@ beforeAll(async () => {
|
|||||||
defaultDatabase = await getKyselyDB();
|
defaultDatabase = await getKyselyDB();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(SyncEntityType.AssetFaceV1, () => {
|
describe(SyncEntityType.AssetFaceV2, () => {
|
||||||
it('should detect and sync the first asset face', async () => {
|
it('should detect and sync the first asset face', async () => {
|
||||||
const { auth, ctx } = await setup();
|
const { auth, ctx } = await setup();
|
||||||
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
|
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
|
||||||
const { person } = await ctx.newPerson({ ownerId: auth.user.id });
|
const { person } = await ctx.newPerson({ ownerId: auth.user.id });
|
||||||
const { assetFace } = await ctx.newAssetFace({ assetId: asset.id, personId: person.id });
|
const { assetFace } = await ctx.newAssetFace({ assetId: asset.id, personId: person.id });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetFacesV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetFacesV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -41,13 +41,13 @@ describe(SyncEntityType.AssetFaceV1, () => {
|
|||||||
boundingBoxY2: assetFace.boundingBoxY2,
|
boundingBoxY2: assetFace.boundingBoxY2,
|
||||||
sourceType: assetFace.sourceType,
|
sourceType: assetFace.sourceType,
|
||||||
}),
|
}),
|
||||||
type: 'AssetFaceV1',
|
type: 'AssetFaceV2',
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect and sync a deleted asset face', async () => {
|
it('should detect and sync a deleted asset face', async () => {
|
||||||
@@ -57,7 +57,7 @@ describe(SyncEntityType.AssetFaceV1, () => {
|
|||||||
const { assetFace } = await ctx.newAssetFace({ assetId: asset.id });
|
const { assetFace } = await ctx.newAssetFace({ assetId: asset.id });
|
||||||
await personRepo.deleteAssetFace(assetFace.id);
|
await personRepo.deleteAssetFace(assetFace.id);
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetFacesV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetFacesV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -70,7 +70,7 @@ describe(SyncEntityType.AssetFaceV1, () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not sync an asset face or asset face delete for an unrelated user', async () => {
|
it('should not sync an asset face or asset face delete for an unrelated user', async () => {
|
||||||
@@ -82,19 +82,19 @@ describe(SyncEntityType.AssetFaceV1, () => {
|
|||||||
const { assetFace } = await ctx.newAssetFace({ assetId: asset.id });
|
const { assetFace } = await ctx.newAssetFace({ assetId: asset.id });
|
||||||
const auth2 = factory.auth({ session, user: user2 });
|
const auth2 = factory.auth({ session, user: user2 });
|
||||||
|
|
||||||
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetFacesV1])).toEqual([
|
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetFacesV2])).toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetFaceV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetFaceV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV2]);
|
||||||
|
|
||||||
await personRepo.deleteAssetFace(assetFace.id);
|
await personRepo.deleteAssetFace(assetFace.id);
|
||||||
|
|
||||||
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetFacesV1])).toEqual([
|
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetFacesV2])).toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetFaceDeleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetFaceDeleteV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetFacesV2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ beforeAll(async () => {
|
|||||||
defaultDatabase = await getKyselyDB();
|
defaultDatabase = await getKyselyDB();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(SyncEntityType.AssetV1, () => {
|
describe(SyncEntityType.AssetV2, () => {
|
||||||
it('should detect and sync the first asset', async () => {
|
it('should detect and sync the first asset', async () => {
|
||||||
const originalFileName = 'firstAsset';
|
const originalFileName = 'firstAsset';
|
||||||
const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
|
const checksum = '1115vHcVkZzNp3Q9G+FEA0nu6zUbGb4Tj4UOXkN0wRA=';
|
||||||
@@ -35,13 +35,13 @@ describe(SyncEntityType.AssetV1, () => {
|
|||||||
fileModifiedAt: date,
|
fileModifiedAt: date,
|
||||||
localDateTime: date,
|
localDateTime: date,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duration: '0:10:00.00000',
|
duration: 600_000,
|
||||||
libraryId: null,
|
libraryId: null,
|
||||||
width: 1920,
|
width: 1920,
|
||||||
height: 1080,
|
height: 1080,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -66,13 +66,13 @@ describe(SyncEntityType.AssetV1, () => {
|
|||||||
height: asset.height,
|
height: asset.height,
|
||||||
isEdited: asset.isEdited,
|
isEdited: asset.isEdited,
|
||||||
},
|
},
|
||||||
type: 'AssetV1',
|
type: 'AssetV2',
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect and sync a deleted asset', async () => {
|
it('should detect and sync a deleted asset', async () => {
|
||||||
@@ -81,7 +81,7 @@ describe(SyncEntityType.AssetV1, () => {
|
|||||||
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
|
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
|
||||||
await assetRepo.remove(asset);
|
await assetRepo.remove(asset);
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -94,7 +94,7 @@ describe(SyncEntityType.AssetV1, () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not sync an asset or asset delete for an unrelated user', async () => {
|
it('should not sync an asset or asset delete for an unrelated user', async () => {
|
||||||
@@ -105,17 +105,17 @@ describe(SyncEntityType.AssetV1, () => {
|
|||||||
const { asset } = await ctx.newAsset({ ownerId: user2.id });
|
const { asset } = await ctx.newAsset({ ownerId: user2.id });
|
||||||
const auth2 = factory.auth({ session, user: user2 });
|
const auth2 = factory.auth({ session, user: user2 });
|
||||||
|
|
||||||
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).toEqual([
|
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
|
||||||
|
|
||||||
await assetRepo.remove(asset);
|
await assetRepo.remove(asset);
|
||||||
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).toEqual([
|
expect(await ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ describe(SyncEntityType.SyncCompleteV1, () => {
|
|||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const { auth, ctx } = await setup();
|
const { auth, ctx } = await setup();
|
||||||
|
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect an old checkpoint and send back a reset', async () => {
|
it('should detect an old checkpoint and send back a reset', async () => {
|
||||||
@@ -39,7 +39,7 @@ describe(SyncEntityType.SyncCompleteV1, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
|
||||||
expect(response).toEqual([{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }]);
|
expect(response).toEqual([{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -55,6 +55,6 @@ describe(SyncEntityType.SyncCompleteV1, () => {
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ beforeAll(async () => {
|
|||||||
defaultDatabase = await getKyselyDB();
|
defaultDatabase = await getKyselyDB();
|
||||||
});
|
});
|
||||||
|
|
||||||
describe(SyncRequestType.PartnerAssetsV1, () => {
|
describe(SyncRequestType.PartnerAssetsV2, () => {
|
||||||
it('should detect and sync the first partner asset', async () => {
|
it('should detect and sync the first partner asset', async () => {
|
||||||
const { auth, ctx } = await setup();
|
const { auth, ctx } = await setup();
|
||||||
|
|
||||||
@@ -39,13 +39,13 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
fileModifiedAt: date,
|
fileModifiedAt: date,
|
||||||
localDateTime: date,
|
localDateTime: date,
|
||||||
deletedAt: null,
|
deletedAt: null,
|
||||||
duration: '0:10:00.00000',
|
duration: 600_000,
|
||||||
libraryId: null,
|
libraryId: null,
|
||||||
});
|
});
|
||||||
|
|
||||||
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -70,13 +70,13 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
width: null,
|
width: null,
|
||||||
height: null,
|
height: null,
|
||||||
},
|
},
|
||||||
type: SyncEntityType.PartnerAssetV1,
|
type: SyncEntityType.PartnerAssetV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect and sync a deleted partner asset', async () => {
|
it('should detect and sync a deleted partner asset', async () => {
|
||||||
@@ -88,7 +88,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||||
await assetRepo.remove(asset);
|
await assetRepo.remove(asset);
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
@@ -101,7 +101,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not sync a deleted partner asset due to a user delete', async () => {
|
it('should not sync a deleted partner asset due to a user delete', async () => {
|
||||||
@@ -112,7 +112,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||||
await ctx.newAsset({ ownerId: user2.id });
|
await ctx.newAsset({ ownerId: user2.id });
|
||||||
await userRepo.delete({ id: user2.id }, true);
|
await userRepo.delete({ id: user2.id }, true);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not sync a deleted partner asset due to a partner delete (unshare)', async () => {
|
it('should not sync a deleted partner asset due to a partner delete (unshare)', async () => {
|
||||||
@@ -122,12 +122,12 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
const { user: user2 } = await ctx.newUser();
|
const { user: user2 } = await ctx.newUser();
|
||||||
await ctx.newAsset({ ownerId: user2.id });
|
await ctx.newAsset({ ownerId: user2.id });
|
||||||
const { partner } = await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
const { partner } = await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.PartnerAssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.PartnerAssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await partnerRepo.remove(partner);
|
await partnerRepo.remove(partner);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not sync an asset or asset delete for own user', async () => {
|
it('should not sync an asset or asset delete for own user', async () => {
|
||||||
@@ -138,19 +138,19 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
|
const { asset } = await ctx.newAsset({ ownerId: auth.user.id });
|
||||||
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
|
|
||||||
await assetRepo.remove(asset);
|
await assetRepo.remove(asset);
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not sync an asset or asset delete for unrelated user', async () => {
|
it('should not sync an asset or asset delete for unrelated user', async () => {
|
||||||
@@ -162,19 +162,19 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
const { asset } = await ctx.newAsset({ ownerId: user2.id });
|
const { asset } = await ctx.newAsset({ ownerId: user2.id });
|
||||||
const auth2 = factory.auth({ session, user: user2 });
|
const auth2 = factory.auth({ session, user: user2 });
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
|
|
||||||
await assetRepo.remove(asset);
|
await assetRepo.remove(asset);
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth2, [SyncRequestType.AssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetDeleteV1 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should backfill partner assets when a partner shared their library with you', async () => {
|
it('should backfill partner assets when a partner shared their library with you', async () => {
|
||||||
@@ -187,14 +187,14 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
const { asset: assetUser2 } = await ctx.newAsset({ ownerId: user2.id });
|
const { asset: assetUser2 } = await ctx.newAsset({ ownerId: user2.id });
|
||||||
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: assetUser2.id,
|
id: assetUser2.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.PartnerAssetV1,
|
type: SyncEntityType.PartnerAssetV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
@@ -202,17 +202,17 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id });
|
await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id });
|
||||||
|
|
||||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
|
const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
expect(newResponse).toEqual([
|
expect(newResponse).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: assetUser3.id,
|
id: assetUser3.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.PartnerAssetBackfillV1,
|
type: SyncEntityType.PartnerAssetBackfillV2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV1),
|
ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV2),
|
||||||
data: {},
|
data: {},
|
||||||
type: SyncEntityType.SyncAckV1,
|
type: SyncEntityType.SyncAckV1,
|
||||||
},
|
},
|
||||||
@@ -220,7 +220,7 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, newResponse);
|
await ctx.syncAckAll(auth, newResponse);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should only backfill partner assets created prior to the current partner asset checkpoint', async () => {
|
it('should only backfill partner assets created prior to the current partner asset checkpoint', async () => {
|
||||||
@@ -235,31 +235,31 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
const { asset: asset2User3 } = await ctx.newAsset({ ownerId: user3.id });
|
const { asset: asset2User3 } = await ctx.newAsset({ ownerId: user3.id });
|
||||||
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
await ctx.newPartner({ sharedById: user2.id, sharedWithId: auth.user.id });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
expect(response).toEqual([
|
expect(response).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: assetUser2.id,
|
id: assetUser2.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.PartnerAssetV1,
|
type: SyncEntityType.PartnerAssetV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
|
|
||||||
await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id });
|
await ctx.newPartner({ sharedById: user3.id, sharedWithId: auth.user.id });
|
||||||
const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV1]);
|
const newResponse = await ctx.syncStream(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
expect(newResponse).toEqual([
|
expect(newResponse).toEqual([
|
||||||
{
|
{
|
||||||
ack: expect.any(String),
|
ack: expect.any(String),
|
||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: assetUser3.id,
|
id: assetUser3.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.PartnerAssetBackfillV1,
|
type: SyncEntityType.PartnerAssetBackfillV2,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV1),
|
ack: expect.stringContaining(SyncEntityType.PartnerAssetBackfillV2),
|
||||||
data: {},
|
data: {},
|
||||||
type: SyncEntityType.SyncAckV1,
|
type: SyncEntityType.SyncAckV1,
|
||||||
},
|
},
|
||||||
@@ -268,12 +268,12 @@ describe(SyncRequestType.PartnerAssetsV1, () => {
|
|||||||
data: expect.objectContaining({
|
data: expect.objectContaining({
|
||||||
id: asset2User3.id,
|
id: asset2User3.id,
|
||||||
}),
|
}),
|
||||||
type: SyncEntityType.PartnerAssetV1,
|
type: SyncEntityType.PartnerAssetV2,
|
||||||
},
|
},
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, newResponse);
|
await ctx.syncAckAll(auth, newResponse);
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.PartnerAssetsV2]);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ describe(SyncEntityType.SyncResetV1, () => {
|
|||||||
it('should work', async () => {
|
it('should work', async () => {
|
||||||
const { auth, ctx } = await setup();
|
const { auth, ctx } = await setup();
|
||||||
|
|
||||||
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV1]);
|
await ctx.assertSyncIsComplete(auth, [SyncRequestType.AssetsV2]);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should detect a pending sync reset', async () => {
|
it('should detect a pending sync reset', async () => {
|
||||||
@@ -31,7 +31,7 @@ describe(SyncEntityType.SyncResetV1, () => {
|
|||||||
isPendingSyncReset: true,
|
isPendingSyncReset: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
|
||||||
expect(response).toEqual([{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }]);
|
expect(response).toEqual([{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' }]);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -40,8 +40,8 @@ describe(SyncEntityType.SyncResetV1, () => {
|
|||||||
|
|
||||||
await ctx.newAsset({ ownerId: user.id });
|
await ctx.newAsset({ ownerId: user.id });
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
@@ -49,7 +49,7 @@ describe(SyncEntityType.SyncResetV1, () => {
|
|||||||
isPendingSyncReset: true,
|
isPendingSyncReset: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1])).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2])).resolves.toEqual([
|
||||||
{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' },
|
{ type: SyncEntityType.SyncResetV1, data: {}, ack: 'SyncResetV1|reset' },
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -63,8 +63,8 @@ describe(SyncEntityType.SyncResetV1, () => {
|
|||||||
isPendingSyncReset: true,
|
isPendingSyncReset: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV1], true)).resolves.toEqual([
|
await expect(ctx.syncStream(auth, [SyncRequestType.AssetsV2], true)).resolves.toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
@@ -74,20 +74,20 @@ describe(SyncEntityType.SyncResetV1, () => {
|
|||||||
|
|
||||||
await ctx.newAsset({ ownerId: user.id });
|
await ctx.newAsset({ ownerId: user.id });
|
||||||
|
|
||||||
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
|
const response = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
|
||||||
await ctx.syncAckAll(auth, response);
|
await ctx.syncAckAll(auth, response);
|
||||||
|
|
||||||
await ctx.get(SessionRepository).update(auth.session!.id, {
|
await ctx.get(SessionRepository).update(auth.session!.id, {
|
||||||
isPendingSyncReset: true,
|
isPendingSyncReset: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const resetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
|
const resetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
|
||||||
|
|
||||||
await ctx.syncAckAll(auth, resetResponse);
|
await ctx.syncAckAll(auth, resetResponse);
|
||||||
|
|
||||||
const postResetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV1]);
|
const postResetResponse = await ctx.syncStream(auth, [SyncRequestType.AssetsV2]);
|
||||||
expect(postResetResponse).toEqual([
|
expect(postResetResponse).toEqual([
|
||||||
expect.objectContaining({ type: SyncEntityType.AssetV1 }),
|
expect.objectContaining({ type: SyncEntityType.AssetV2 }),
|
||||||
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
expect.objectContaining({ type: SyncEntityType.SyncCompleteV1 }),
|
||||||
]);
|
]);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -5,7 +5,6 @@
|
|||||||
import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte';
|
import { mediaQueryManager } from '$lib/stores/media-query-manager.svelte';
|
||||||
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
import { locale, playVideoThumbnailOnHover } from '$lib/stores/preferences.store';
|
||||||
import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
|
import { getAssetMediaUrl, getAssetPlaybackUrl } from '$lib/utils';
|
||||||
import { timeToSeconds } from '$lib/utils/date-time';
|
|
||||||
import { moveFocus } from '$lib/utils/focus-util';
|
import { moveFocus } from '$lib/utils/focus-util';
|
||||||
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
import { currentUrlReplaceAssetId } from '$lib/utils/navigation';
|
||||||
import { getAltText } from '$lib/utils/thumbnail-util';
|
import { getAltText } from '$lib/utils/thumbnail-util';
|
||||||
@@ -274,7 +273,7 @@
|
|||||||
url={getAssetPlaybackUrl({ id: asset.id, cacheKey: asset.thumbhash })}
|
url={getAssetPlaybackUrl({ id: asset.id, cacheKey: asset.thumbhash })}
|
||||||
enablePlayback={mouseOver && $playVideoThumbnailOnHover}
|
enablePlayback={mouseOver && $playVideoThumbnailOnHover}
|
||||||
curve={selected}
|
curve={selected}
|
||||||
durationInSeconds={asset.duration ? timeToSeconds(asset.duration) : 0}
|
durationInSeconds={asset.duration ? asset.duration / 1000 : 0}
|
||||||
playbackOnIconHover={!$playVideoThumbnailOnHover}
|
playbackOnIconHover={!$playVideoThumbnailOnHover}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ export type TimelineAsset = {
|
|||||||
isVideo: boolean;
|
isVideo: boolean;
|
||||||
isImage: boolean;
|
isImage: boolean;
|
||||||
stack: AssetStackResponseDto | null;
|
stack: AssetStackResponseDto | null;
|
||||||
duration: string | null;
|
duration: number | null;
|
||||||
projectionType: string | null;
|
projectionType: string | null;
|
||||||
livePhotoVideoId: string | null;
|
livePhotoVideoId: string | null;
|
||||||
city: string | null;
|
city: string | null;
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ describe('utils', () => {
|
|||||||
originalPath: 'image.gif',
|
originalPath: 'image.gif',
|
||||||
originalMimeType: 'image/gif',
|
originalMimeType: 'image/gif',
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
duration: '2.0',
|
duration: 2000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = getAssetUrl({ asset });
|
const url = getAssetUrl({ asset });
|
||||||
@@ -65,7 +65,7 @@ describe('utils', () => {
|
|||||||
originalPath: 'image.webp',
|
originalPath: 'image.webp',
|
||||||
originalMimeType: 'image/webp',
|
originalMimeType: 'image/webp',
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
duration: '2.0',
|
duration: 2000,
|
||||||
});
|
});
|
||||||
|
|
||||||
const url = getAssetUrl({ asset });
|
const url = getAssetUrl({ asset });
|
||||||
@@ -119,7 +119,7 @@ describe('utils', () => {
|
|||||||
originalPath: 'image.gif',
|
originalPath: 'image.gif',
|
||||||
originalMimeType: 'image/gif',
|
originalMimeType: 'image/gif',
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
duration: '2.0',
|
duration: 2000,
|
||||||
});
|
});
|
||||||
const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] });
|
const sharedLink = sharedLinkFactory.build({ allowDownload: true, showMetadata: true, assets: [asset] });
|
||||||
|
|
||||||
@@ -134,7 +134,7 @@ describe('utils', () => {
|
|||||||
originalPath: 'image.gif',
|
originalPath: 'image.gif',
|
||||||
originalMimeType: 'image/gif',
|
originalMimeType: 'image/gif',
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
duration: '2.0',
|
duration: 2000,
|
||||||
});
|
});
|
||||||
const sharedLink = sharedLinkFactory.build({ allowDownload: false, assets: [asset] });
|
const sharedLink = sharedLinkFactory.build({ allowDownload: false, assets: [asset] });
|
||||||
|
|
||||||
@@ -150,7 +150,7 @@ describe('utils', () => {
|
|||||||
originalPath: 'image.gif',
|
originalPath: 'image.gif',
|
||||||
originalMimeType: 'image/gif',
|
originalMimeType: 'image/gif',
|
||||||
type: AssetTypeEnum.Image,
|
type: AssetTypeEnum.Image,
|
||||||
duration: '2.0',
|
duration: 2000,
|
||||||
});
|
});
|
||||||
const sharedLink = sharedLinkFactory.build({ showMetadata: false, assets: [asset] });
|
const sharedLink = sharedLinkFactory.build({ showMetadata: false, assets: [asset] });
|
||||||
|
|
||||||
|
|||||||
@@ -1,53 +1,5 @@
|
|||||||
import { writable } from 'svelte/store';
|
import { writable } from 'svelte/store';
|
||||||
import { getAlbumDateRange, getShortDateRange, timeToSeconds } from './date-time';
|
import { getAlbumDateRange, getShortDateRange } from './date-time';
|
||||||
|
|
||||||
describe('converting time to seconds', () => {
|
|
||||||
it('parses hh:mm:ss correctly', () => {
|
|
||||||
expect(timeToSeconds('01:02:03')).toBeCloseTo(3723);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('parses hh:mm:ss.SSS correctly', () => {
|
|
||||||
expect(timeToSeconds('01:02:03.456')).toBeCloseTo(3723.456);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('parses h:m:s.S correctly', () => {
|
|
||||||
expect(timeToSeconds('1:2:3.4')).toBe(0); // Non-standard format, Luxon returns NaN
|
|
||||||
});
|
|
||||||
|
|
||||||
it('parses hhh:mm:ss.SSS correctly', () => {
|
|
||||||
expect(timeToSeconds('100:02:03.456')).toBe(0); // Non-standard format, Luxon returns NaN
|
|
||||||
});
|
|
||||||
|
|
||||||
it('ignores ignores double milliseconds hh:mm:ss.SSS.SSSSSS', () => {
|
|
||||||
expect(timeToSeconds('01:02:03.456.123456')).toBe(0); // Non-standard format, Luxon returns NaN
|
|
||||||
});
|
|
||||||
|
|
||||||
// Test edge cases that can cause crashes
|
|
||||||
it('handles "0" string input', () => {
|
|
||||||
expect(timeToSeconds('0')).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles empty string input', () => {
|
|
||||||
expect(timeToSeconds('')).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('parses HH:MM format correctly', () => {
|
|
||||||
expect(timeToSeconds('01:02')).toBe(3720); // 1 hour 2 minutes = 3720 seconds
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles malformed time strings', () => {
|
|
||||||
expect(timeToSeconds('invalid')).toBe(0);
|
|
||||||
});
|
|
||||||
|
|
||||||
it('parses single hour format correctly', () => {
|
|
||||||
expect(timeToSeconds('01')).toBe(3600); // Luxon interprets "01" as 1 hour
|
|
||||||
});
|
|
||||||
|
|
||||||
it('handles time strings with invalid numbers', () => {
|
|
||||||
expect(timeToSeconds('aa:bb:cc')).toBe(0);
|
|
||||||
expect(timeToSeconds('01:bb:03')).toBe(0);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('getShortDateRange', () => {
|
describe('getShortDateRange', () => {
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
|
|||||||
@@ -1,20 +1,8 @@
|
|||||||
import { DateTime, Duration } from 'luxon';
|
import { DateTime } from 'luxon';
|
||||||
import { get } from 'svelte/store';
|
import { get } from 'svelte/store';
|
||||||
import { dateFormats } from '$lib/constants';
|
import { dateFormats } from '$lib/constants';
|
||||||
import { locale } from '$lib/stores/preferences.store';
|
import { locale } from '$lib/stores/preferences.store';
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert time like `01:02:03.456` to seconds.
|
|
||||||
*/
|
|
||||||
export function timeToSeconds(time: string) {
|
|
||||||
if (!time || time === '0') {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
const seconds = Duration.fromISOTime(time).as('seconds');
|
|
||||||
|
|
||||||
return Number.isNaN(seconds) ? 0 : seconds;
|
|
||||||
}
|
|
||||||
export function parseUtcDate(date: string) {
|
export function parseUtcDate(date: string) {
|
||||||
return DateTime.fromISO(date, { zone: 'UTC' }).toUTC();
|
return DateTime.fromISO(date, { zone: 'UTC' }).toUTC();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,18 +95,10 @@
|
|||||||
};
|
};
|
||||||
|
|
||||||
const setProgressDuration = (asset: TimelineAsset) => {
|
const setProgressDuration = (asset: TimelineAsset) => {
|
||||||
if (asset.isVideo) {
|
|
||||||
const timeParts = asset.duration!.split(':').map(Number);
|
|
||||||
const durationInMilliseconds = (timeParts[0] * 3600 + timeParts[1] * 60 + timeParts[2]) * 1000;
|
|
||||||
progressBarController = new Tween<number>(0, {
|
|
||||||
duration: (from: number, to: number) => (to ? durationInMilliseconds * (to - from) : 0),
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
progressBarController = new Tween<number>(0, {
|
progressBarController = new Tween<number>(0, {
|
||||||
duration: (from: number, to: number) =>
|
duration: (from: number, to: number) =>
|
||||||
to ? authManager.preferences.memories.duration * 1000 * (to - from) : 0,
|
to ? (asset.isVideo ? asset.duration! : authManager.preferences.memories.duration * 1000) * (to - from) : 0,
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNextAsset = () => handleNavigate(current?.next?.asset);
|
const handleNextAsset = () => handleNavigate(current?.next?.asset);
|
||||||
|
|||||||
Reference in New Issue
Block a user