fix: memories mobile sync for older clients (#32167)

fix: memories mobile sync for older clients (#32150)


(cherry picked from commit 8d377bfef5a0c9d840bf2c19d155f9779387baeb)

Co-authored-by: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com>
Co-authored-by: Santo Shakil <shakil.mezbah@gmail.com>
This commit is contained in:
immich-push-o-matic[bot]
2026-10-06 14:10:38 +00:00
committed by GitHub
co-authored by Daniel Dietzler Santo Shakil
parent 1aede8d8c3
commit ed3073624b
13 changed files with 281 additions and 14 deletions
+9 -1
View File
@@ -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;
+4
View File
@@ -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',
+22
View File
@@ -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",
@@ -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)
+35 -1
View File
@@ -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 });