diff --git a/mobile/lib/domain/models/server_capability.model.dart b/mobile/lib/domain/models/server_capability.model.dart index 01118ed4dd..7a2e44fe83 100644 --- a/mobile/lib/domain/models/server_capability.model.dart +++ b/mobile/lib/domain/models/server_capability.model.dart @@ -13,6 +13,7 @@ enum ServerCapability { syncAssetFacesV3(SemVer(major: 3, minor: 3, patch: 0)), syncAssetOcrV1(SemVer(major: 3, minor: 0, patch: 0)), syncAuthUsersV2(SemVer(major: 3, minor: 3, patch: 0)), + syncMemoriesV2(SemVer(major: 3, minor: 3, patch: 0)), // Migrations assetPayloadChange20260128(SemVer(major: 2, minor: 5, patch: 0)), diff --git a/mobile/lib/domain/services/sync_stream.service.dart b/mobile/lib/domain/services/sync_stream.service.dart index df2bb5d0d6..ccf72980c1 100644 --- a/mobile/lib/domain/services/sync_stream.service.dart +++ b/mobile/lib/domain/services/sync_stream.service.dart @@ -296,10 +296,12 @@ class SyncStreamService { case SyncEntityType.syncResetV1: return _syncStreamRepository.reset(); case SyncEntityType.memoryV1: - return _syncStreamRepository.updateMemoriesV1(data.cast()); + case SyncEntityType.memoryV2: + return _syncStreamRepository.updateMemoriesV2(data.cast()); case SyncEntityType.memoryDeleteV1: return _syncStreamRepository.deleteMemoriesV1(data.cast()); case SyncEntityType.memoryToAssetV1: + case SyncEntityType.memoryToAssetV2: return _syncStreamRepository.updateMemoryAssetsV1(data.cast()); case SyncEntityType.memoryToAssetDeleteV1: return _syncStreamRepository.deleteMemoryAssetsV1(data.cast()); diff --git a/mobile/lib/infrastructure/repositories/sync_api.repository.dart b/mobile/lib/infrastructure/repositories/sync_api.repository.dart index 046b3f6f73..dfdf943a12 100644 --- a/mobile/lib/infrastructure/repositories/sync_api.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_api.repository.dart @@ -57,8 +57,8 @@ class SyncApiRepository { serverVersion.supports(.syncV2) ? SyncRequestType.albumAssetsV2 : SyncRequestType.albumAssetsV1, SyncRequestType.albumAssetExifsV1, SyncRequestType.albumToAssetsV1, - SyncRequestType.memoriesV1, - SyncRequestType.memoryToAssetsV1, + serverVersion.supports(.syncMemoriesV2) ? SyncRequestType.memoriesV2 : SyncRequestType.memoriesV1, + serverVersion.supports(.syncMemoriesV2) ? SyncRequestType.memoryToAssetsV2 : SyncRequestType.memoryToAssetsV1, SyncRequestType.stacksV1, SyncRequestType.partnerStacksV1, SyncRequestType.userMetadataV1, @@ -184,9 +184,11 @@ const _kResponseMap = { SyncEntityType.albumToAssetDeleteV1: SyncAlbumToAssetDeleteV1.fromJson, SyncEntityType.syncAckV1: _SyncEmptyDto.fromJson, SyncEntityType.syncResetV1: _SyncEmptyDto.fromJson, - SyncEntityType.memoryV1: SyncMemoryV1.fromJson, + SyncEntityType.memoryV1: SyncMemoryV2.fromJson, + SyncEntityType.memoryV2: SyncMemoryV2.fromJson, SyncEntityType.memoryDeleteV1: SyncMemoryDeleteV1.fromJson, SyncEntityType.memoryToAssetV1: SyncMemoryAssetV1.fromJson, + SyncEntityType.memoryToAssetV2: SyncMemoryAssetV1.fromJson, SyncEntityType.memoryToAssetDeleteV1: SyncMemoryAssetDeleteV1.fromJson, SyncEntityType.stackV1: SyncStackV1.fromJson, SyncEntityType.stackDeleteV1: SyncStackDeleteV1.fromJson, diff --git a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart index 0bcee44837..9740651fa5 100644 --- a/mobile/lib/infrastructure/repositories/sync_stream.repository.dart +++ b/mobile/lib/infrastructure/repositories/sync_stream.repository.dart @@ -640,7 +640,7 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos } } - Future updateMemoriesV1(Iterable data) async { + Future updateMemoriesV2(Iterable data) async { try { await _db.batch((batch) { for (final memory in data) { @@ -665,7 +665,7 @@ class SyncStreamRepository extends DatabaseAccessor with $SyncStreamRepos } }); } catch (error, stack) { - _logger.severe('Error: updateMemoriesV1', error, stack); + _logger.severe('Error: updateMemoriesV2', error, stack); rethrow; } } diff --git a/mobile/test/domain/services/sync_stream_service_test.dart b/mobile/test/domain/services/sync_stream_service_test.dart index 6c57345119..aa10e73598 100644 --- a/mobile/test/domain/services/sync_stream_service_test.dart +++ b/mobile/test/domain/services/sync_stream_service_test.dart @@ -131,7 +131,7 @@ void main() { when( () => mockSyncStreamRepo.updateAssetsExifV1(any(), debugLabel: any(named: 'debugLabel')), ).thenAnswer(successHandler); - when(() => mockSyncStreamRepo.updateMemoriesV1(any())).thenAnswer(successHandler); + when(() => mockSyncStreamRepo.updateMemoriesV2(any())).thenAnswer(successHandler); when(() => mockSyncStreamRepo.deleteMemoriesV1(any())).thenAnswer(successHandler); when(() => mockSyncStreamRepo.updateMemoryAssetsV1(any())).thenAnswer(successHandler); when(() => mockSyncStreamRepo.deleteMemoryAssetsV1(any())).thenAnswer(successHandler); @@ -320,7 +320,7 @@ void main() { await simulateEvents(events); verifyInOrder([ - () => mockSyncStreamRepo.updateMemoriesV1(any()), + () => mockSyncStreamRepo.updateMemoriesV2(any()), () => mockSyncApiRepo.ack(["5"]), () => mockSyncStreamRepo.deleteMemoriesV1(any()), () => mockSyncApiRepo.ack(["6"]), @@ -349,14 +349,14 @@ void main() { () => mockSyncApiRepo.ack(["1"]), () => mockSyncStreamRepo.updateMemoryAssetsV1(any()), () => mockSyncApiRepo.ack(["7"]), - () => mockSyncStreamRepo.updateMemoriesV1(any()), + () => mockSyncStreamRepo.updateMemoriesV2(any()), () => mockSyncApiRepo.ack(["5"]), ]); verifyNever(() => mockAbortCallbackWrapper()); }); test("handles memory sync failure gracefully", () async { - when(() => mockSyncStreamRepo.updateMemoriesV1(any())).thenThrow(Exception("Memory sync failed")); + when(() => mockSyncStreamRepo.updateMemoriesV2(any())).thenThrow(Exception("Memory sync failed")); final events = [SyncStreamStub.memoryV1, SyncStreamStub.userV1Admin]; @@ -386,9 +386,22 @@ void main() { await simulateEvents(events); - verify(() => mockSyncStreamRepo.updateMemoriesV1(any())).called(1); + verify(() => mockSyncStreamRepo.updateMemoriesV2(any())).called(1); verify(() => mockSyncApiRepo.ack(["5"])).called(1); }); + + test("processes memory v2 events", () async { + final events = [SyncStreamStub.memoryV2, SyncStreamStub.memoryToAssetV2]; + + await simulateEvents(events); + + verifyInOrder([ + () => mockSyncStreamRepo.updateMemoriesV2(any()), + () => mockSyncApiRepo.ack(["9"]), + () => mockSyncStreamRepo.updateMemoryAssetsV1(any()), + () => mockSyncApiRepo.ack(["10"]), + ]); + }); }); group("SyncStreamService - remote trash & restore", () { diff --git a/mobile/test/fixtures/sync_stream.stub.dart b/mobile/test/fixtures/sync_stream.stub.dart index 1691fbdc49..c4c6e278ca 100644 --- a/mobile/test/fixtures/sync_stream.stub.dart +++ b/mobile/test/fixtures/sync_stream.stub.dart @@ -47,7 +47,7 @@ abstract final class SyncStreamStub { static final memoryV1 = SyncEvent( type: SyncEntityType.memoryV1, - data: SyncMemoryV1( + data: SyncMemoryV2( createdAt: DateTime(2023, 1, 1), data: {"year": 2023, "title": "Test Memory"}, deletedAt: null, @@ -64,6 +64,25 @@ abstract final class SyncStreamStub { ack: "5", ); + static final memoryV2 = SyncEvent( + type: SyncEntityType.memoryV2, + data: SyncMemoryV2( + createdAt: DateTime(2023, 1, 1), + data: {"year": 2023}, + deletedAt: null, + hideAt: null, + id: "memory-3", + isSaved: false, + memoryAt: DateTime(2023, 1, 1), + ownerId: "user-1", + seenAt: null, + showAt: DateTime(2023, 1, 1), + type: MemoryType.birthday, + updatedAt: DateTime(2023, 1, 1), + ), + ack: "9", + ); + static final memoryDeleteV1 = SyncEvent( type: SyncEntityType.memoryDeleteV1, data: SyncMemoryDeleteV1(memoryId: "memory-2"), @@ -76,6 +95,12 @@ abstract final class SyncStreamStub { ack: "7", ); + static final memoryToAssetV2 = SyncEvent( + type: SyncEntityType.memoryToAssetV2, + data: SyncMemoryAssetV1(assetId: "asset-1", memoryId: "memory-3"), + ack: "10", + ); + static final memoryToAssetDeleteV1 = SyncEvent( type: SyncEntityType.memoryToAssetDeleteV1, data: SyncMemoryAssetDeleteV1(assetId: "asset-2", memoryId: "memory-1"), diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 629802af0c..e86bc1333a 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -23504,6 +23504,12 @@ ], "type": "string" }, + "MemoryTypeV1": { + "enum": [ + "on_this_day" + ], + "type": "string" + }, "MemoryUpdateDto": { "properties": { "isSaved": { @@ -31400,8 +31406,10 @@ "AlbumToAssetDeleteV1", "AlbumToAssetBackfillV1", "MemoryV1", + "MemoryV2", "MemoryDeleteV1", "MemoryToAssetV1", + "MemoryToAssetV2", "MemoryToAssetDeleteV1", "StackV1", "StackDeleteV1", @@ -31476,6 +31484,102 @@ "type": "object" }, "SyncMemoryV1": { + "properties": { + "createdAt": { + "description": "Created at", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "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|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" + }, + "data": { + "additionalProperties": {}, + "description": "Data", + "type": "object" + }, + "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|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" + }, + "hideAt": { + "description": "Hide 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|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" + }, + "id": { + "description": "Memory ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", + "type": "string" + }, + "isSaved": { + "description": "Is saved", + "type": "boolean" + }, + "memoryAt": { + "description": "Memory at", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "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|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" + }, + "ownerId": { + "description": "Owner ID", + "format": "uuid", + "pattern": "^([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-4[0-9a-fA-F]{3}-[89abAB][0-9a-fA-F]{3}-[0-9a-fA-F]{12})$", + "type": "string" + }, + "seenAt": { + "description": "Seen 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|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" + }, + "showAt": { + "description": "Show 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|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" + }, + "type": { + "$ref": "#/components/schemas/MemoryTypeV1" + }, + "updatedAt": { + "description": "Updated at", + "example": "2024-01-01T00:00:00.000Z", + "format": "date-time", + "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|([+-](?:[01]\\d|2[0-3]):[0-5]\\d)))$", + "type": "string" + } + }, + "required": [ + "createdAt", + "data", + "deletedAt", + "hideAt", + "id", + "isSaved", + "memoryAt", + "ownerId", + "seenAt", + "showAt", + "type", + "updatedAt" + ], + "type": "object" + }, + "SyncMemoryV2": { "properties": { "createdAt": { "description": "Created at", @@ -31724,7 +31828,9 @@ "AuthUsersV1", "AuthUsersV2", "MemoriesV1", + "MemoriesV2", "MemoryToAssetsV1", + "MemoryToAssetsV2", "PartnersV1", "PartnerAssetsV1", "PartnerAssetsV2", diff --git a/packages/sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts index b5fc56d7a1..6500628d94 100644 --- a/packages/sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -3718,6 +3718,33 @@ export type SyncMemoryDeleteV1 = { memoryId: string; }; export type SyncMemoryV1 = { + /** Created at */ + createdAt: string; + /** Data */ + data: { + [key: string]: any; + }; + /** Deleted at */ + deletedAt: string | null; + /** Hide at */ + hideAt: string | null; + /** Memory ID */ + id: string; + /** Is saved */ + isSaved: boolean; + /** Memory at */ + memoryAt: string; + /** Owner ID */ + ownerId: string; + /** Seen at */ + seenAt: string | null; + /** Show at */ + showAt: string | null; + "type": MemoryTypeV1; + /** Updated at */ + updatedAt: string; +}; +export type SyncMemoryV2 = { /** Created at */ createdAt: string; /** Data */ @@ -8532,8 +8559,10 @@ export enum SyncEntityType { AlbumToAssetDeleteV1 = "AlbumToAssetDeleteV1", AlbumToAssetBackfillV1 = "AlbumToAssetBackfillV1", MemoryV1 = "MemoryV1", + MemoryV2 = "MemoryV2", MemoryDeleteV1 = "MemoryDeleteV1", MemoryToAssetV1 = "MemoryToAssetV1", + MemoryToAssetV2 = "MemoryToAssetV2", MemoryToAssetDeleteV1 = "MemoryToAssetDeleteV1", StackV1 = "StackV1", StackDeleteV1 = "StackDeleteV1", @@ -8566,7 +8595,9 @@ export enum SyncRequestType { AuthUsersV1 = "AuthUsersV1", AuthUsersV2 = "AuthUsersV2", MemoriesV1 = "MemoriesV1", + MemoriesV2 = "MemoriesV2", MemoryToAssetsV1 = "MemoryToAssetsV1", + MemoryToAssetsV2 = "MemoryToAssetsV2", PartnersV1 = "PartnersV1", PartnerAssetsV1 = "PartnerAssetsV1", PartnerAssetsV2 = "PartnerAssetsV2", @@ -8589,6 +8620,9 @@ export enum WorkflowResult { Halted = "halted", Error = "error" } +export enum MemoryTypeV1 { + OnThisDay = "on_this_day" +} export enum ReleaseType { Major = "major", Premajor = "premajor", diff --git a/server/src/dtos/sync.dto.ts b/server/src/dtos/sync.dto.ts index 1b4495d372..117bfef6d2 100644 --- a/server/src/dtos/sync.dto.ts +++ b/server/src/dtos/sync.dto.ts @@ -295,7 +295,7 @@ const SyncMemoryV1Schema = z updatedAt: isoDatetimeToDate.describe('Updated at'), deletedAt: isoDatetimeToDate.nullable().describe('Deleted at'), ownerId: z.uuidv4().describe('Owner ID'), - type: MemoryTypeSchema, + type: MemoryTypeSchema.extract(['OnThisDay']).meta({ id: 'MemoryTypeV1' }), data: z.record(z.string(), z.unknown()).describe('Data'), isSaved: z.boolean().describe('Is saved'), memoryAt: isoDatetimeToDate.describe('Memory at'), @@ -305,6 +305,10 @@ const SyncMemoryV1Schema = z }) .meta({ id: 'SyncMemoryV1' }); +const SyncMemoryV2Schema = SyncMemoryV1Schema.extend({ + type: MemoryTypeSchema, +}).meta({ id: 'SyncMemoryV2' }); + const SyncMemoryDeleteV1Schema = z .object({ memoryId: z.uuidv4().describe('Memory ID') }) .meta({ id: 'SyncMemoryDeleteV1' }); @@ -403,6 +407,8 @@ const SyncCompleteV1Schema = z.object({}).meta({ id: 'SyncCompleteV1' }); @ExtraModel() class SyncMemoryV1 extends createZodDto(SyncMemoryV1Schema) {} @ExtraModel() +class SyncMemoryV2 extends createZodDto(SyncMemoryV2Schema) {} +@ExtraModel() class SyncMemoryDeleteV1 extends createZodDto(SyncMemoryDeleteV1Schema) {} @ExtraModel() class SyncMemoryAssetV1 extends createZodDto(SyncMemoryAssetV1Schema) {} @@ -505,8 +511,10 @@ export type SyncItem = { [SyncEntityType.AlbumToAssetBackfillV1]: SyncAlbumToAssetV1; [SyncEntityType.AlbumToAssetDeleteV1]: SyncAlbumToAssetDeleteV1; [SyncEntityType.MemoryV1]: SyncMemoryV1; + [SyncEntityType.MemoryV2]: SyncMemoryV2; [SyncEntityType.MemoryDeleteV1]: SyncMemoryDeleteV1; [SyncEntityType.MemoryToAssetV1]: SyncMemoryAssetV1; + [SyncEntityType.MemoryToAssetV2]: SyncMemoryAssetV1; [SyncEntityType.MemoryToAssetDeleteV1]: SyncMemoryAssetDeleteV1; [SyncEntityType.StackV1]: SyncStackV1; [SyncEntityType.StackDeleteV1]: SyncStackDeleteV1; diff --git a/server/src/enum.ts b/server/src/enum.ts index 41178f4e40..70efdc100d 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -1055,7 +1055,9 @@ export enum SyncRequestType { AuthUsersV1 = 'AuthUsersV1', AuthUsersV2 = 'AuthUsersV2', MemoriesV1 = 'MemoriesV1', + MemoriesV2 = 'MemoriesV2', MemoryToAssetsV1 = 'MemoryToAssetsV1', + MemoryToAssetsV2 = 'MemoryToAssetsV2', PartnersV1 = 'PartnersV1', /** @deprecated */ PartnerAssetsV1 = 'PartnerAssetsV1', @@ -1139,9 +1141,11 @@ export enum SyncEntityType { AlbumToAssetBackfillV1 = 'AlbumToAssetBackfillV1', MemoryV1 = 'MemoryV1', + MemoryV2 = 'MemoryV2', MemoryDeleteV1 = 'MemoryDeleteV1', MemoryToAssetV1 = 'MemoryToAssetV1', + MemoryToAssetV2 = 'MemoryToAssetV2', MemoryToAssetDeleteV1 = 'MemoryToAssetDeleteV1', StackV1 = 'StackV1', diff --git a/server/src/queries/sync.repository.sql b/server/src/queries/sync.repository.sql index 3014704f10..856f8a4b82 100644 --- a/server/src/queries/sync.repository.sql +++ b/server/src/queries/sync.repository.sql @@ -768,6 +768,28 @@ where order by "memory_asset_audit"."id" asc +-- SyncRepository.memoryToAsset.getUpsertsV1 +select + "memoriesId" as "memoryId", + "assetId" as "assetId", + "updateId" +from + "memory_asset" as "memory_asset" +where + "memory_asset"."updateId" < $1 + and "memory_asset"."updateId" > $2 + and "memoriesId" in ( + select + "id" + from + "memory" + where + "ownerId" = $3 + and "type" = 'on_this_day' + ) +order by + "memory_asset"."updateId" asc + -- SyncRepository.memoryToAsset.getUpserts select "memoriesId" as "memoryId", diff --git a/server/src/repositories/sync.repository.ts b/server/src/repositories/sync.repository.ts index 0c84d21a07..a8985a628a 100644 --- a/server/src/repositories/sync.repository.ts +++ b/server/src/repositories/sync.repository.ts @@ -4,6 +4,7 @@ import { InjectKysely } from 'nestjs-kysely'; import type { SyncAck } from 'src/types.js'; import { columns } from 'src/database.js'; import { DummyValue, GenerateSql } from 'src/decorators.js'; +import { MemoryType } from 'src/enum.js'; import { DB } from 'src/schema/index.js'; export type SyncBackfillOptions = { @@ -596,6 +597,21 @@ class MemoryToAssetSync extends BaseSync { return this.auditCleanup('memory_asset_audit', daysAgo); } + @GenerateSql({ params: [dummyQueryOptions], stream: true }) + getUpsertsV1(options: SyncQueryOptions) { + return this.upsertQuery('memory_asset', options) + .select(['memoriesId as memoryId', 'assetId as assetId']) + .select('updateId') + .where('memoriesId', 'in', (eb) => + eb + .selectFrom('memory') + .select('id') + .where('ownerId', '=', options.userId) + .where('type', '=', sql.lit(MemoryType.OnThisDay)), + ) + .stream(); + } + @GenerateSql({ params: [dummyQueryOptions], stream: true }) getUpserts(options: SyncQueryOptions) { return this.upsertQuery('memory_asset', options) diff --git a/server/src/services/sync.service.ts b/server/src/services/sync.service.ts index 1dc75d994d..df6694004b 100644 --- a/server/src/services/sync.service.ts +++ b/server/src/services/sync.service.ts @@ -13,7 +13,7 @@ import { SyncStreamDto, syncAlbumV2ToV1, } from 'src/dtos/sync.dto.js'; -import { JobName, QueueName, SyncEntityType, SyncRequestType } from 'src/enum.js'; +import { JobName, MemoryType, QueueName, SyncEntityType, SyncRequestType } from 'src/enum.js'; import { SyncQueryOptions } from 'src/repositories/sync.repository.js'; import { SessionSyncCheckpointTable } from 'src/schema/tables/sync-checkpoint.table.js'; import { BaseService } from 'src/services/base.service.js'; @@ -83,7 +83,9 @@ export const SYNC_TYPES_ORDER = [ SyncRequestType.AssetOcrV1, SyncRequestType.PartnerAssetExifsV1, SyncRequestType.MemoriesV1, + SyncRequestType.MemoriesV2, SyncRequestType.MemoryToAssetsV1, + SyncRequestType.MemoryToAssetsV2, SyncRequestType.PeopleV1, SyncRequestType.AssetFacesV1, SyncRequestType.AssetFacesV2, @@ -210,7 +212,9 @@ export class SyncService extends BaseService { [SyncRequestType.AlbumAssetExifsV1]: () => this.syncAlbumAssetExifsV1(options, response, checkpointMap, session.id), [SyncRequestType.MemoriesV1]: () => this.syncMemoriesV1(options, response, checkpointMap), + [SyncRequestType.MemoriesV2]: () => this.syncMemoriesV2(options, response, checkpointMap), [SyncRequestType.MemoryToAssetsV1]: () => this.syncMemoryAssetsV1(options, response, checkpointMap), + [SyncRequestType.MemoryToAssetsV2]: () => this.syncMemoryAssetsV2(options, response, checkpointMap), [SyncRequestType.StacksV1]: () => this.syncStackV1(options, response, checkpointMap), [SyncRequestType.PartnerStacksV1]: () => this.syncPartnerStackV1(options, response, checkpointMap, session.id), [SyncRequestType.PeopleV1]: () => this.syncPeopleV1(options, response, checkpointMap), @@ -786,6 +790,22 @@ export class SyncService extends BaseService { const upsertType = SyncEntityType.MemoryV1; const upserts = this.syncRepository.memory.getUpserts({ ...options, ack: checkpointMap[upsertType] }); + for await (const { updateId, ...data } of upserts) { + if (data.type === MemoryType.OnThisDay) { + await send(response, { type: upsertType, ids: [updateId], data: { ...data, type: data.type } }); + } + } + } + + private async syncMemoriesV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { + const deleteType = SyncEntityType.MemoryDeleteV1; + const deletes = this.syncRepository.memory.getDeletes({ ...options, ack: checkpointMap[deleteType] }); + for await (const { id, ...data } of deletes) { + await send(response, { type: deleteType, ids: [id], data }); + } + + const upsertType = SyncEntityType.MemoryV2; + const upserts = this.syncRepository.memory.getUpserts({ ...options, ack: checkpointMap[upsertType] }); for await (const { updateId, ...data } of upserts) { await send(response, { type: upsertType, ids: [updateId], data }); } @@ -799,6 +819,20 @@ export class SyncService extends BaseService { } const upsertType = SyncEntityType.MemoryToAssetV1; + const upserts = this.syncRepository.memoryToAsset.getUpsertsV1({ ...options, ack: checkpointMap[upsertType] }); + for await (const { updateId, ...data } of upserts) { + await send(response, { type: upsertType, ids: [updateId], data }); + } + } + + private async syncMemoryAssetsV2(options: SyncQueryOptions, response: Writable, checkpointMap: CheckpointMap) { + const deleteType = SyncEntityType.MemoryToAssetDeleteV1; + const deletes = this.syncRepository.memoryToAsset.getDeletes({ ...options, ack: checkpointMap[deleteType] }); + for await (const { id, ...data } of deletes) { + await send(response, { type: deleteType, ids: [id], data }); + } + + const upsertType = SyncEntityType.MemoryToAssetV2; const upserts = this.syncRepository.memoryToAsset.getUpserts({ ...options, ack: checkpointMap[upsertType] }); for await (const { updateId, ...data } of upserts) { await send(response, { type: upsertType, ids: [updateId], data });