mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
feat: sync memories (#19579)
This commit is contained in:
parent
97aabe466e
commit
6feca56da8
31 changed files with 1482 additions and 203 deletions
|
|
@ -13,7 +13,7 @@ import {
|
|||
UserAvatarColor,
|
||||
UserStatus,
|
||||
} from 'src/enum';
|
||||
import { OnThisDayData, UserMetadataItem } from 'src/types';
|
||||
import { UserMetadataItem } from 'src/types';
|
||||
|
||||
export type AuthUser = {
|
||||
id: string;
|
||||
|
|
@ -95,7 +95,7 @@ export type Memory = {
|
|||
showAt: Date | null;
|
||||
hideAt: Date | null;
|
||||
type: MemoryType;
|
||||
data: OnThisDayData;
|
||||
data: object;
|
||||
ownerId: string;
|
||||
isSaved: boolean;
|
||||
assets: MapAsset[];
|
||||
|
|
|
|||
16
server/src/db.d.ts
vendored
16
server/src/db.d.ts
vendored
|
|
@ -19,8 +19,11 @@ import {
|
|||
SourceType,
|
||||
SyncEntityType,
|
||||
} from 'src/enum';
|
||||
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
||||
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
|
||||
import { MemoryAuditTable } from 'src/schema/tables/memory-audit.table';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import { OnThisDayData, UserMetadataItem } from 'src/types';
|
||||
import { UserMetadataItem } from 'src/types';
|
||||
|
||||
export type ArrayType<T> = ArrayTypeImpl<T> extends (infer U)[] ? U[] : ArrayTypeImpl<T>;
|
||||
|
||||
|
|
@ -278,7 +281,7 @@ export interface Libraries {
|
|||
|
||||
export interface Memories {
|
||||
createdAt: Generated<Timestamp>;
|
||||
data: OnThisDayData;
|
||||
data: object;
|
||||
deletedAt: Timestamp | null;
|
||||
hideAt: Timestamp | null;
|
||||
id: Generated<string>;
|
||||
|
|
@ -307,11 +310,6 @@ export interface Notifications {
|
|||
readAt: Timestamp | null;
|
||||
}
|
||||
|
||||
export interface MemoriesAssetsAssets {
|
||||
assetsId: string;
|
||||
memoriesId: string;
|
||||
}
|
||||
|
||||
export interface Migrations {
|
||||
id: Generated<number>;
|
||||
name: string;
|
||||
|
|
@ -512,7 +510,9 @@ export interface DB {
|
|||
geodata_places: GeodataPlaces;
|
||||
libraries: Libraries;
|
||||
memories: Memories;
|
||||
memories_assets_assets: MemoriesAssetsAssets;
|
||||
memories_audit: MemoryAuditTable;
|
||||
memories_assets_assets: MemoryAssetTable;
|
||||
memory_assets_audit: MemoryAssetAuditTable;
|
||||
migrations: Migrations;
|
||||
notifications: Notifications;
|
||||
move_history: MoveHistory;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,16 @@
|
|||
/* eslint-disable @typescript-eslint/no-unsafe-function-type */
|
||||
import { ApiProperty } from '@nestjs/swagger';
|
||||
import { ArrayMaxSize, IsEnum, IsInt, IsPositive, IsString } from 'class-validator';
|
||||
import { AssetResponseDto } from 'src/dtos/asset-response.dto';
|
||||
import { AlbumUserRole, AssetOrder, AssetType, AssetVisibility, SyncEntityType, SyncRequestType } from 'src/enum';
|
||||
import {
|
||||
AlbumUserRole,
|
||||
AssetOrder,
|
||||
AssetType,
|
||||
AssetVisibility,
|
||||
MemoryType,
|
||||
SyncEntityType,
|
||||
SyncRequestType,
|
||||
} from 'src/enum';
|
||||
import { Optional, ValidateDate, ValidateUUID } from 'src/validation';
|
||||
|
||||
export class AssetFullSyncDto {
|
||||
|
|
@ -34,6 +43,15 @@ export class AssetDeltaSyncResponseDto {
|
|||
deleted!: string[];
|
||||
}
|
||||
|
||||
export const extraSyncModels: Function[] = [];
|
||||
|
||||
export const ExtraModel = (): ClassDecorator => {
|
||||
return (object: Function) => {
|
||||
extraSyncModels.push(object);
|
||||
};
|
||||
};
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncUserV1 {
|
||||
id!: string;
|
||||
name!: string;
|
||||
|
|
@ -41,21 +59,25 @@ export class SyncUserV1 {
|
|||
deletedAt!: Date | null;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncUserDeleteV1 {
|
||||
userId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncPartnerV1 {
|
||||
sharedById!: string;
|
||||
sharedWithId!: string;
|
||||
inTimeline!: boolean;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncPartnerDeleteV1 {
|
||||
sharedById!: string;
|
||||
sharedWithId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAssetV1 {
|
||||
id!: string;
|
||||
ownerId!: string;
|
||||
|
|
@ -74,10 +96,12 @@ export class SyncAssetV1 {
|
|||
visibility!: AssetVisibility;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAssetDeleteV1 {
|
||||
assetId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAssetExifV1 {
|
||||
assetId!: string;
|
||||
description!: string | null;
|
||||
|
|
@ -116,15 +140,18 @@ export class SyncAssetExifV1 {
|
|||
fps!: number | null;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAlbumDeleteV1 {
|
||||
albumId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAlbumUserDeleteV1 {
|
||||
albumId!: string;
|
||||
userId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAlbumUserV1 {
|
||||
albumId!: string;
|
||||
userId!: string;
|
||||
|
|
@ -132,6 +159,7 @@ export class SyncAlbumUserV1 {
|
|||
role!: AlbumUserRole;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAlbumV1 {
|
||||
id!: string;
|
||||
ownerId!: string;
|
||||
|
|
@ -145,16 +173,53 @@ export class SyncAlbumV1 {
|
|||
order!: AssetOrder;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAlbumToAssetV1 {
|
||||
albumId!: string;
|
||||
assetId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAlbumToAssetDeleteV1 {
|
||||
albumId!: string;
|
||||
assetId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncMemoryV1 {
|
||||
id!: string;
|
||||
createdAt!: Date;
|
||||
updatedAt!: Date;
|
||||
deletedAt!: Date | null;
|
||||
ownerId!: string;
|
||||
@ApiProperty({ enumName: 'MemoryType', enum: MemoryType })
|
||||
type!: MemoryType;
|
||||
data!: object;
|
||||
isSaved!: boolean;
|
||||
memoryAt!: Date;
|
||||
seenAt!: Date | null;
|
||||
showAt!: Date | null;
|
||||
hideAt!: Date | null;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncMemoryDeleteV1 {
|
||||
memoryId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncMemoryAssetV1 {
|
||||
memoryId!: string;
|
||||
assetId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncMemoryAssetDeleteV1 {
|
||||
memoryId!: string;
|
||||
assetId!: string;
|
||||
}
|
||||
|
||||
@ExtraModel()
|
||||
export class SyncAckV1 {}
|
||||
|
||||
export type SyncItem = {
|
||||
|
|
@ -182,28 +247,13 @@ export type SyncItem = {
|
|||
[SyncEntityType.AlbumToAssetV1]: SyncAlbumToAssetV1;
|
||||
[SyncEntityType.AlbumToAssetBackfillV1]: SyncAlbumToAssetV1;
|
||||
[SyncEntityType.AlbumToAssetDeleteV1]: SyncAlbumToAssetDeleteV1;
|
||||
[SyncEntityType.MemoryV1]: SyncMemoryV1;
|
||||
[SyncEntityType.MemoryDeleteV1]: SyncMemoryDeleteV1;
|
||||
[SyncEntityType.MemoryToAssetV1]: SyncMemoryAssetV1;
|
||||
[SyncEntityType.MemoryToAssetDeleteV1]: SyncMemoryAssetDeleteV1;
|
||||
[SyncEntityType.SyncAckV1]: SyncAckV1;
|
||||
};
|
||||
|
||||
const responseDtos = [
|
||||
SyncUserV1,
|
||||
SyncUserDeleteV1,
|
||||
SyncPartnerV1,
|
||||
SyncPartnerDeleteV1,
|
||||
SyncAssetV1,
|
||||
SyncAssetDeleteV1,
|
||||
SyncAssetExifV1,
|
||||
SyncAlbumV1,
|
||||
SyncAlbumDeleteV1,
|
||||
SyncAlbumUserV1,
|
||||
SyncAlbumUserDeleteV1,
|
||||
SyncAlbumToAssetV1,
|
||||
SyncAlbumToAssetDeleteV1,
|
||||
SyncAckV1,
|
||||
];
|
||||
|
||||
export const extraSyncModels = responseDtos;
|
||||
|
||||
export class SyncStreamDto {
|
||||
@IsEnum(SyncRequestType, { each: true })
|
||||
@ApiProperty({ enumName: 'SyncRequestType', enum: SyncRequestType, isArray: true })
|
||||
|
|
|
|||
|
|
@ -584,19 +584,21 @@ export enum SyncRequestType {
|
|||
AlbumToAssetsV1 = 'AlbumToAssetsV1',
|
||||
AlbumAssetsV1 = 'AlbumAssetsV1',
|
||||
AlbumAssetExifsV1 = 'AlbumAssetExifsV1',
|
||||
MemoriesV1 = 'MemoriesV1',
|
||||
MemoryToAssetsV1 = 'MemoryToAssetsV1',
|
||||
}
|
||||
|
||||
export enum SyncEntityType {
|
||||
UserV1 = 'UserV1',
|
||||
UserDeleteV1 = 'UserDeleteV1',
|
||||
|
||||
PartnerV1 = 'PartnerV1',
|
||||
PartnerDeleteV1 = 'PartnerDeleteV1',
|
||||
|
||||
AssetV1 = 'AssetV1',
|
||||
AssetDeleteV1 = 'AssetDeleteV1',
|
||||
AssetExifV1 = 'AssetExifV1',
|
||||
|
||||
PartnerV1 = 'PartnerV1',
|
||||
PartnerDeleteV1 = 'PartnerDeleteV1',
|
||||
|
||||
PartnerAssetV1 = 'PartnerAssetV1',
|
||||
PartnerAssetBackfillV1 = 'PartnerAssetBackfillV1',
|
||||
PartnerAssetDeleteV1 = 'PartnerAssetDeleteV1',
|
||||
|
|
@ -605,17 +607,26 @@ export enum SyncEntityType {
|
|||
|
||||
AlbumV1 = 'AlbumV1',
|
||||
AlbumDeleteV1 = 'AlbumDeleteV1',
|
||||
|
||||
AlbumUserV1 = 'AlbumUserV1',
|
||||
AlbumUserBackfillV1 = 'AlbumUserBackfillV1',
|
||||
AlbumUserDeleteV1 = 'AlbumUserDeleteV1',
|
||||
|
||||
AlbumAssetV1 = 'AlbumAssetV1',
|
||||
AlbumAssetBackfillV1 = 'AlbumAssetBackfillV1',
|
||||
AlbumAssetExifV1 = 'AlbumAssetExifV1',
|
||||
AlbumAssetExifBackfillV1 = 'AlbumAssetExifBackfillV1',
|
||||
|
||||
AlbumToAssetV1 = 'AlbumToAssetV1',
|
||||
AlbumToAssetDeleteV1 = 'AlbumToAssetDeleteV1',
|
||||
AlbumToAssetBackfillV1 = 'AlbumToAssetBackfillV1',
|
||||
|
||||
MemoryV1 = 'MemoryV1',
|
||||
MemoryDeleteV1 = 'MemoryDeleteV1',
|
||||
|
||||
MemoryToAssetV1 = 'MemoryToAssetV1',
|
||||
MemoryToAssetDeleteV1 = 'MemoryToAssetDeleteV1',
|
||||
|
||||
SyncAckV1 = 'SyncAckV1',
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -652,3 +652,78 @@ where
|
|||
)
|
||||
order by
|
||||
"exif"."updateId" asc
|
||||
|
||||
-- SyncRepository.getMemoryUpserts
|
||||
select
|
||||
"id",
|
||||
"createdAt",
|
||||
"updatedAt",
|
||||
"deletedAt",
|
||||
"ownerId",
|
||||
"type",
|
||||
"data",
|
||||
"isSaved",
|
||||
"memoryAt",
|
||||
"seenAt",
|
||||
"showAt",
|
||||
"hideAt",
|
||||
"updateId"
|
||||
from
|
||||
"memories"
|
||||
where
|
||||
"ownerId" = $1
|
||||
and "updatedAt" < now() - interval '1 millisecond'
|
||||
order by
|
||||
"updateId" asc
|
||||
|
||||
-- SyncRepository.getMemoryDeletes
|
||||
select
|
||||
"id",
|
||||
"memoryId"
|
||||
from
|
||||
"memories_audit"
|
||||
where
|
||||
"userId" = $1
|
||||
and "deletedAt" < now() - interval '1 millisecond'
|
||||
order by
|
||||
"id" asc
|
||||
|
||||
-- SyncRepository.getMemoryAssetUpserts
|
||||
select
|
||||
"memoriesId" as "memoryId",
|
||||
"assetsId" as "assetId",
|
||||
"updateId"
|
||||
from
|
||||
"memories_assets_assets"
|
||||
where
|
||||
"memoriesId" in (
|
||||
select
|
||||
"id"
|
||||
from
|
||||
"memories"
|
||||
where
|
||||
"ownerId" = $1
|
||||
)
|
||||
and "updatedAt" < now() - interval '1 millisecond'
|
||||
order by
|
||||
"updateId" asc
|
||||
|
||||
-- SyncRepository.getMemoryAssetDeletes
|
||||
select
|
||||
"id",
|
||||
"memoryId",
|
||||
"assetId"
|
||||
from
|
||||
"memory_assets_audit"
|
||||
where
|
||||
"memoryId" in (
|
||||
select
|
||||
"id"
|
||||
from
|
||||
"memories"
|
||||
where
|
||||
"ownerId" = $1
|
||||
)
|
||||
and "deletedAt" < now() - interval '1 millisecond'
|
||||
order by
|
||||
"id" asc
|
||||
|
|
|
|||
|
|
@ -13,8 +13,18 @@ type AuditTables =
|
|||
| 'assets_audit'
|
||||
| 'albums_audit'
|
||||
| 'album_users_audit'
|
||||
| 'album_assets_audit';
|
||||
type UpsertTables = 'users' | 'partners' | 'assets' | 'exif' | 'albums' | 'albums_shared_users_users';
|
||||
| 'album_assets_audit'
|
||||
| 'memories_audit'
|
||||
| 'memory_assets_audit';
|
||||
type UpsertTables =
|
||||
| 'users'
|
||||
| 'partners'
|
||||
| 'assets'
|
||||
| 'exif'
|
||||
| 'albums'
|
||||
| 'albums_shared_users_users'
|
||||
| 'memories'
|
||||
| 'memories_assets_assets';
|
||||
|
||||
@Injectable()
|
||||
export class SyncRepository {
|
||||
|
|
@ -438,6 +448,61 @@ export class SyncRepository {
|
|||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getMemoryUpserts(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('memories')
|
||||
.select([
|
||||
'id',
|
||||
'createdAt',
|
||||
'updatedAt',
|
||||
'deletedAt',
|
||||
'ownerId',
|
||||
'type',
|
||||
'data',
|
||||
'isSaved',
|
||||
'memoryAt',
|
||||
'seenAt',
|
||||
'showAt',
|
||||
'hideAt',
|
||||
])
|
||||
.select('updateId')
|
||||
.where('ownerId', '=', userId)
|
||||
.$call((qb) => this.upsertTableFilters(qb, ack))
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getMemoryDeletes(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('memories_audit')
|
||||
.select(['id', 'memoryId'])
|
||||
.where('userId', '=', userId)
|
||||
.$call((qb) => this.auditTableFilters(qb, ack))
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getMemoryAssetUpserts(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('memories_assets_assets')
|
||||
.select(['memoriesId as memoryId', 'assetsId as assetId'])
|
||||
.select('updateId')
|
||||
.where('memoriesId', 'in', (eb) => eb.selectFrom('memories').select('id').where('ownerId', '=', userId))
|
||||
.$call((qb) => this.upsertTableFilters(qb, ack))
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID], stream: true })
|
||||
getMemoryAssetDeletes(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('memory_assets_audit')
|
||||
.select(['id', 'memoryId', 'assetId'])
|
||||
.where('memoryId', 'in', (eb) => eb.selectFrom('memories').select('id').where('ownerId', '=', userId))
|
||||
.$call((qb) => this.auditTableFilters(qb, ack))
|
||||
.stream();
|
||||
}
|
||||
|
||||
private auditTableFilters<T extends keyof Pick<DB, AuditTables>, D>(qb: SelectQueryBuilder<DB, T, D>, ack?: SyncAck) {
|
||||
const builder = qb as SelectQueryBuilder<DB, AuditTables, D>;
|
||||
return builder
|
||||
|
|
|
|||
|
|
@ -176,3 +176,31 @@ export const album_users_delete_audit = registerFunction({
|
|||
END`,
|
||||
synchronize: false,
|
||||
});
|
||||
|
||||
export const memories_delete_audit = registerFunction({
|
||||
name: 'memories_delete_audit',
|
||||
returnType: 'TRIGGER',
|
||||
language: 'PLPGSQL',
|
||||
body: `
|
||||
BEGIN
|
||||
INSERT INTO memories_audit ("memoryId", "userId")
|
||||
SELECT "id", "ownerId"
|
||||
FROM OLD;
|
||||
RETURN NULL;
|
||||
END`,
|
||||
synchronize: false,
|
||||
});
|
||||
|
||||
export const memory_assets_delete_audit = registerFunction({
|
||||
name: 'memory_assets_delete_audit',
|
||||
returnType: 'TRIGGER',
|
||||
language: 'PLPGSQL',
|
||||
body: `
|
||||
BEGIN
|
||||
INSERT INTO memory_assets_audit ("memoryId", "assetId")
|
||||
SELECT "memoriesId", "assetsId" FROM OLD
|
||||
WHERE "memoriesId" IN (SELECT "id" FROM memories WHERE "id" IN (SELECT "memoriesId" FROM OLD));
|
||||
RETURN NULL;
|
||||
END`,
|
||||
synchronize: false,
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ import {
|
|||
f_unaccent,
|
||||
immich_uuid_v7,
|
||||
ll_to_earth_public,
|
||||
memories_delete_audit,
|
||||
memory_assets_delete_audit,
|
||||
partners_delete_audit,
|
||||
updated_at,
|
||||
users_delete_audit,
|
||||
|
|
@ -30,8 +32,10 @@ import { ExifTable } from 'src/schema/tables/exif.table';
|
|||
import { FaceSearchTable } from 'src/schema/tables/face-search.table';
|
||||
import { GeodataPlacesTable } from 'src/schema/tables/geodata-places.table';
|
||||
import { LibraryTable } from 'src/schema/tables/library.table';
|
||||
import { MemoryAssetAuditTable } from 'src/schema/tables/memory-asset-audit.table';
|
||||
import { MemoryAssetTable } from 'src/schema/tables/memory-asset.table';
|
||||
import { MemoryAuditTable } from 'src/schema/tables/memory-audit.table';
|
||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||
import { MemoryAssetTable } from 'src/schema/tables/memory_asset.table';
|
||||
import { MoveTable } from 'src/schema/tables/move.table';
|
||||
import { NaturalEarthCountriesTable } from 'src/schema/tables/natural-earth-countries.table';
|
||||
import { NotificationTable } from 'src/schema/tables/notification.table';
|
||||
|
|
@ -75,8 +79,10 @@ export class ImmichDatabase {
|
|||
FaceSearchTable,
|
||||
GeodataPlacesTable,
|
||||
LibraryTable,
|
||||
MemoryAssetTable,
|
||||
MemoryTable,
|
||||
MemoryAuditTable,
|
||||
MemoryAssetTable,
|
||||
MemoryAssetAuditTable,
|
||||
MoveTable,
|
||||
NaturalEarthCountriesTable,
|
||||
NotificationTable,
|
||||
|
|
@ -110,6 +116,8 @@ export class ImmichDatabase {
|
|||
albums_delete_audit,
|
||||
album_user_after_insert,
|
||||
album_users_delete_audit,
|
||||
memories_delete_audit,
|
||||
memory_assets_delete_audit,
|
||||
];
|
||||
|
||||
enum = [assets_status_enum, asset_face_source_type, asset_visibility_enum];
|
||||
|
|
|
|||
|
|
@ -0,0 +1,80 @@
|
|||
import { Kysely, sql } from 'kysely';
|
||||
|
||||
export async function up(db: Kysely<any>): Promise<void> {
|
||||
await sql`CREATE TABLE "memory_assets_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "memoryId" uuid NOT NULL, "assetId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db);
|
||||
await sql`CREATE TABLE "memories_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "memoryId" uuid NOT NULL, "userId" uuid NOT NULL, "deletedAt" timestamp with time zone NOT NULL DEFAULT clock_timestamp());`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD "createdAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD "updatedAt" timestamp with time zone NOT NULL DEFAULT now();`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" ADD "updateId" uuid NOT NULL DEFAULT immich_uuid_v7();`.execute(db);
|
||||
await sql`ALTER TABLE "memory_assets_audit" ADD CONSTRAINT "PK_35ef16910228f980e0766dcc59b" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "memories_audit" ADD CONSTRAINT "PK_19de798c033a710dcfa5c72f81b" PRIMARY KEY ("id");`.execute(db);
|
||||
await sql`ALTER TABLE "memory_assets_audit" ADD CONSTRAINT "FK_225a204afcb0bd6de015080fb03" FOREIGN KEY ("memoryId") REFERENCES "memories" ("id") ON UPDATE CASCADE ON DELETE CASCADE;`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_memory_assets_audit_memory_id" ON "memory_assets_audit" ("memoryId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_memory_assets_audit_asset_id" ON "memory_assets_audit" ("assetId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_memory_assets_audit_deleted_at" ON "memory_assets_audit" ("deletedAt")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_memory_assets_update_id" ON "memories_assets_assets" ("updateId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_memories_audit_memory_id" ON "memories_audit" ("memoryId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_memories_audit_user_id" ON "memories_audit" ("userId")`.execute(db);
|
||||
await sql`CREATE INDEX "IDX_memories_audit_deleted_at" ON "memories_audit" ("deletedAt")`.execute(db);
|
||||
await sql`CREATE OR REPLACE FUNCTION memories_delete_audit()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO memories_audit ("memoryId", "userId")
|
||||
SELECT "id", "ownerId"
|
||||
FROM OLD;
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE OR REPLACE FUNCTION memory_assets_delete_audit()
|
||||
RETURNS TRIGGER
|
||||
LANGUAGE PLPGSQL
|
||||
AS $$
|
||||
BEGIN
|
||||
INSERT INTO memory_assets_audit ("memoryId", "assetId")
|
||||
SELECT "memoriesId", "assetsId" FROM OLD
|
||||
WHERE "memoriesId" IN (SELECT "id" FROM memories WHERE "id" IN (SELECT "memoriesId" FROM OLD));
|
||||
RETURN NULL;
|
||||
END
|
||||
$$;`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "memories_delete_audit"
|
||||
AFTER DELETE ON "memories"
|
||||
REFERENCING OLD TABLE AS "old"
|
||||
FOR EACH STATEMENT
|
||||
WHEN (pg_trigger_depth() = 0)
|
||||
EXECUTE FUNCTION memories_delete_audit();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "memory_assets_delete_audit"
|
||||
AFTER DELETE ON "memories_assets_assets"
|
||||
REFERENCING OLD TABLE AS "old"
|
||||
FOR EACH STATEMENT
|
||||
WHEN (pg_trigger_depth() <= 1)
|
||||
EXECUTE FUNCTION memory_assets_delete_audit();`.execute(db);
|
||||
await sql`CREATE OR REPLACE TRIGGER "memory_assets_updated_at"
|
||||
BEFORE UPDATE ON "memories_assets_assets"
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION updated_at();`.execute(db);
|
||||
}
|
||||
|
||||
export async function down(db: Kysely<any>): Promise<void> {
|
||||
await sql`DROP TRIGGER "memories_delete_audit" ON "memories";`.execute(db);
|
||||
await sql`DROP TRIGGER "memory_assets_delete_audit" ON "memories_assets_assets";`.execute(db);
|
||||
await sql`DROP TRIGGER "memory_assets_updated_at" ON "memories_assets_assets";`.execute(db);
|
||||
await sql`DROP INDEX "IDX_memory_assets_update_id";`.execute(db);
|
||||
await sql`DROP INDEX "IDX_memory_assets_audit_memory_id";`.execute(db);
|
||||
await sql`DROP INDEX "IDX_memory_assets_audit_asset_id";`.execute(db);
|
||||
await sql`DROP INDEX "IDX_memory_assets_audit_deleted_at";`.execute(db);
|
||||
await sql`DROP INDEX "IDX_memories_audit_memory_id";`.execute(db);
|
||||
await sql`DROP INDEX "IDX_memories_audit_user_id";`.execute(db);
|
||||
await sql`DROP INDEX "IDX_memories_audit_deleted_at";`.execute(db);
|
||||
await sql`ALTER TABLE "memory_assets_audit" DROP CONSTRAINT "FK_225a204afcb0bd6de015080fb03";`.execute(db);
|
||||
await sql`ALTER TABLE "memory_assets_audit" DROP CONSTRAINT "PK_35ef16910228f980e0766dcc59b";`.execute(db);
|
||||
await sql`ALTER TABLE "memories_audit" DROP CONSTRAINT "PK_19de798c033a710dcfa5c72f81b";`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" DROP COLUMN "createdAt";`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" DROP COLUMN "updatedAt";`.execute(db);
|
||||
await sql`ALTER TABLE "memories_assets_assets" DROP COLUMN "updateId";`.execute(db);
|
||||
await sql`DROP TABLE "memory_assets_audit";`.execute(db);
|
||||
await sql`DROP TABLE "memories_audit";`.execute(db);
|
||||
await sql`DROP FUNCTION memories_delete_audit;`.execute(db);
|
||||
await sql`DROP FUNCTION memory_assets_delete_audit;`.execute(db);
|
||||
}
|
||||
|
|
@ -13,8 +13,8 @@ import 'src/schema/tables/exif.table';
|
|||
import 'src/schema/tables/face-search.table';
|
||||
import 'src/schema/tables/geodata-places.table';
|
||||
import 'src/schema/tables/library.table';
|
||||
import 'src/schema/tables/memory-asset.table';
|
||||
import 'src/schema/tables/memory.table';
|
||||
import 'src/schema/tables/memory_asset.table';
|
||||
import 'src/schema/tables/move.table';
|
||||
import 'src/schema/tables/natural-earth-countries.table';
|
||||
import 'src/schema/tables/partner-audit.table';
|
||||
|
|
|
|||
23
server/src/schema/tables/memory-asset-audit.table.ts
Normal file
23
server/src/schema/tables/memory-asset-audit.table.ts
Normal file
|
|
@ -0,0 +1,23 @@
|
|||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||
import { Column, CreateDateColumn, ForeignKeyColumn, Table } from 'src/sql-tools';
|
||||
|
||||
@Table('memory_assets_audit')
|
||||
export class MemoryAssetAuditTable {
|
||||
@PrimaryGeneratedUuidV7Column()
|
||||
id!: string;
|
||||
|
||||
@ForeignKeyColumn(() => MemoryTable, {
|
||||
type: 'uuid',
|
||||
indexName: 'IDX_memory_assets_audit_memory_id',
|
||||
onDelete: 'CASCADE',
|
||||
onUpdate: 'CASCADE',
|
||||
})
|
||||
memoryId!: string;
|
||||
|
||||
@Column({ type: 'uuid', indexName: 'IDX_memory_assets_audit_asset_id' })
|
||||
assetId!: string;
|
||||
|
||||
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_memory_assets_audit_deleted_at' })
|
||||
deletedAt!: Date;
|
||||
}
|
||||
36
server/src/schema/tables/memory-asset.table.ts
Normal file
36
server/src/schema/tables/memory-asset.table.ts
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import { ColumnType } from 'kysely';
|
||||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { memory_assets_delete_audit } from 'src/schema/functions';
|
||||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||
import { AfterDeleteTrigger, CreateDateColumn, ForeignKeyColumn, Table, UpdateDateColumn } from 'src/sql-tools';
|
||||
|
||||
type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||
type Generated<T> =
|
||||
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
|
||||
|
||||
@Table('memories_assets_assets')
|
||||
@UpdatedAtTrigger('memory_assets_updated_at')
|
||||
@AfterDeleteTrigger({
|
||||
name: 'memory_assets_delete_audit',
|
||||
scope: 'statement',
|
||||
function: memory_assets_delete_audit,
|
||||
referencingOldTableAs: 'old',
|
||||
when: 'pg_trigger_depth() <= 1',
|
||||
})
|
||||
export class MemoryAssetTable {
|
||||
@ForeignKeyColumn(() => MemoryTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
|
||||
memoriesId!: string;
|
||||
|
||||
@ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
|
||||
assetsId!: string;
|
||||
|
||||
@CreateDateColumn()
|
||||
createdAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateDateColumn()
|
||||
updatedAt!: Generated<Timestamp>;
|
||||
|
||||
@UpdateIdColumn({ indexName: 'IDX_memory_assets_update_id' })
|
||||
updateId!: Generated<string>;
|
||||
}
|
||||
22
server/src/schema/tables/memory-audit.table.ts
Normal file
22
server/src/schema/tables/memory-audit.table.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { ColumnType } from 'kysely';
|
||||
import { PrimaryGeneratedUuidV7Column } from 'src/decorators';
|
||||
import { Column, CreateDateColumn, Table } from 'src/sql-tools';
|
||||
|
||||
type Timestamp = ColumnType<Date, Date | string, Date | string>;
|
||||
type Generated<T> =
|
||||
T extends ColumnType<infer S, infer I, infer U> ? ColumnType<S, I | undefined, U> : ColumnType<T, T | undefined, T>;
|
||||
|
||||
@Table('memories_audit')
|
||||
export class MemoryAuditTable {
|
||||
@PrimaryGeneratedUuidV7Column()
|
||||
id!: Generated<string>;
|
||||
|
||||
@Column({ type: 'uuid', indexName: 'IDX_memories_audit_memory_id' })
|
||||
memoryId!: string;
|
||||
|
||||
@Column({ type: 'uuid', indexName: 'IDX_memories_audit_user_id' })
|
||||
userId!: string;
|
||||
|
||||
@CreateDateColumn({ default: () => 'clock_timestamp()', indexName: 'IDX_memories_audit_deleted_at' })
|
||||
deletedAt!: Timestamp;
|
||||
}
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
import { UpdatedAtTrigger, UpdateIdColumn } from 'src/decorators';
|
||||
import { MemoryType } from 'src/enum';
|
||||
import { memories_delete_audit } from 'src/schema/functions';
|
||||
import { UserTable } from 'src/schema/tables/user.table';
|
||||
import {
|
||||
AfterDeleteTrigger,
|
||||
Column,
|
||||
CreateDateColumn,
|
||||
DeleteDateColumn,
|
||||
|
|
@ -10,11 +12,17 @@ import {
|
|||
Table,
|
||||
UpdateDateColumn,
|
||||
} from 'src/sql-tools';
|
||||
import { MemoryData } from 'src/types';
|
||||
|
||||
@Table('memories')
|
||||
@UpdatedAtTrigger('memories_updated_at')
|
||||
export class MemoryTable<T extends MemoryType = MemoryType> {
|
||||
@AfterDeleteTrigger({
|
||||
name: 'memories_delete_audit',
|
||||
scope: 'statement',
|
||||
function: memories_delete_audit,
|
||||
referencingOldTableAs: 'old',
|
||||
when: 'pg_trigger_depth() = 0',
|
||||
})
|
||||
export class MemoryTable {
|
||||
@PrimaryGeneratedColumn()
|
||||
id!: string;
|
||||
|
||||
|
|
@ -31,10 +39,10 @@ export class MemoryTable<T extends MemoryType = MemoryType> {
|
|||
ownerId!: string;
|
||||
|
||||
@Column()
|
||||
type!: T;
|
||||
type!: MemoryType;
|
||||
|
||||
@Column({ type: 'jsonb' })
|
||||
data!: MemoryData[T];
|
||||
data!: object;
|
||||
|
||||
/** unless set to true, will be automatically deleted in the future */
|
||||
@Column({ type: 'boolean', default: false })
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
import { AssetTable } from 'src/schema/tables/asset.table';
|
||||
import { MemoryTable } from 'src/schema/tables/memory.table';
|
||||
import { ForeignKeyColumn, Table } from 'src/sql-tools';
|
||||
|
||||
@Table('memories_assets_assets')
|
||||
export class MemoryAssetTable {
|
||||
@ForeignKeyColumn(() => MemoryTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
|
||||
memoriesId!: string;
|
||||
|
||||
@ForeignKeyColumn(() => AssetTable, { onUpdate: 'CASCADE', onDelete: 'CASCADE', primary: true })
|
||||
assetsId!: string;
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
import { BadRequestException } from '@nestjs/common';
|
||||
import { MemoryService } from 'src/services/memory.service';
|
||||
import { OnThisDayData } from 'src/types';
|
||||
import { factory, newUuid, newUuids } from 'test/small.factory';
|
||||
import { newTestService, ServiceMocks } from 'test/utils';
|
||||
|
||||
|
|
@ -87,7 +88,7 @@ describe(MemoryService.name, () => {
|
|||
await expect(
|
||||
sut.create(factory.auth({ user: { id: userId } }), {
|
||||
type: memory.type,
|
||||
data: memory.data,
|
||||
data: memory.data as OnThisDayData,
|
||||
memoryAt: memory.memoryAt,
|
||||
isSaved: memory.isSaved,
|
||||
assetIds: [assetId],
|
||||
|
|
@ -117,7 +118,7 @@ describe(MemoryService.name, () => {
|
|||
await expect(
|
||||
sut.create(factory.auth({ user: { id: userId } }), {
|
||||
type: memory.type,
|
||||
data: memory.data,
|
||||
data: memory.data as OnThisDayData,
|
||||
assetIds: memory.assets.map((asset) => asset.id),
|
||||
memoryAt: memory.memoryAt,
|
||||
}),
|
||||
|
|
@ -135,7 +136,11 @@ describe(MemoryService.name, () => {
|
|||
mocks.memory.create.mockResolvedValue(memory);
|
||||
|
||||
await expect(
|
||||
sut.create(factory.auth(), { type: memory.type, data: memory.data, memoryAt: memory.memoryAt }),
|
||||
sut.create(factory.auth(), {
|
||||
type: memory.type,
|
||||
data: memory.data as OnThisDayData,
|
||||
memoryAt: memory.memoryAt,
|
||||
}),
|
||||
).resolves.toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -65,6 +65,8 @@ export const SYNC_TYPES_ORDER = [
|
|||
SyncRequestType.AssetExifsV1,
|
||||
SyncRequestType.AlbumAssetExifsV1,
|
||||
SyncRequestType.PartnerAssetExifsV1,
|
||||
SyncRequestType.MemoriesV1,
|
||||
SyncRequestType.MemoryToAssetsV1,
|
||||
];
|
||||
|
||||
const throwSessionRequired = () => {
|
||||
|
|
@ -120,108 +122,70 @@ export class SyncService extends BaseService {
|
|||
|
||||
const checkpoints = await this.syncRepository.getCheckpoints(sessionId);
|
||||
const checkpointMap: CheckpointMap = Object.fromEntries(checkpoints.map(({ type, ack }) => [type, fromAck(ack)]));
|
||||
const handlers: Record<SyncRequestType, () => Promise<void>> = {
|
||||
[SyncRequestType.UsersV1]: () => this.syncUsersV1(response, checkpointMap),
|
||||
[SyncRequestType.PartnersV1]: () => this.syncPartnersV1(response, checkpointMap, auth),
|
||||
[SyncRequestType.AssetsV1]: () => this.syncAssetsV1(response, checkpointMap, auth),
|
||||
[SyncRequestType.AssetExifsV1]: () => this.syncAssetExifsV1(response, checkpointMap, auth),
|
||||
[SyncRequestType.PartnerAssetsV1]: () => this.syncPartnerAssetsV1(response, checkpointMap, auth, sessionId),
|
||||
[SyncRequestType.PartnerAssetExifsV1]: () =>
|
||||
this.syncPartnerAssetExifsV1(response, checkpointMap, auth, sessionId),
|
||||
[SyncRequestType.AlbumsV1]: () => this.syncAlbumsV1(response, checkpointMap, auth),
|
||||
[SyncRequestType.AlbumUsersV1]: () => this.syncAlbumUsersV1(response, checkpointMap, auth, sessionId),
|
||||
[SyncRequestType.AlbumAssetsV1]: () => this.syncAlbumAssetsV1(response, checkpointMap, auth, sessionId),
|
||||
[SyncRequestType.AlbumToAssetsV1]: () => this.syncAlbumToAssetsV1(response, checkpointMap, auth, sessionId),
|
||||
[SyncRequestType.AlbumAssetExifsV1]: () => this.syncAlbumAssetExifsV1(response, checkpointMap, auth, sessionId),
|
||||
[SyncRequestType.MemoriesV1]: () => this.syncMemoriesV1(response, checkpointMap, auth),
|
||||
[SyncRequestType.MemoryToAssetsV1]: () => this.syncMemoryAssetsV1(response, checkpointMap, auth),
|
||||
};
|
||||
|
||||
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
|
||||
switch (type) {
|
||||
case SyncRequestType.UsersV1: {
|
||||
await this.syncUsersV1(response, checkpointMap);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.PartnersV1: {
|
||||
await this.syncPartnersV1(response, checkpointMap, auth);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.AssetsV1: {
|
||||
await this.syncAssetsV1(response, checkpointMap, auth);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.AssetExifsV1: {
|
||||
await this.syncAssetExifsV1(response, checkpointMap, auth);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.PartnerAssetsV1: {
|
||||
await this.syncPartnerAssetsV1(response, checkpointMap, auth, sessionId);
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.PartnerAssetExifsV1: {
|
||||
await this.syncPartnerAssetExifsV1(response, checkpointMap, auth, sessionId);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.AlbumsV1: {
|
||||
await this.syncAlbumsV1(response, checkpointMap, auth);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.AlbumUsersV1: {
|
||||
await this.syncAlbumUsersV1(response, checkpointMap, auth, sessionId);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.AlbumAssetsV1: {
|
||||
await this.syncAlbumAssetsV1(response, checkpointMap, auth, sessionId);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.AlbumToAssetsV1: {
|
||||
await this.syncAlbumToAssetsV1(response, checkpointMap, auth, sessionId);
|
||||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.AlbumAssetExifsV1: {
|
||||
await this.syncAlbumAssetExifsV1(response, checkpointMap, auth, sessionId);
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
this.logger.warn(`Unsupported sync type: ${type}`);
|
||||
break;
|
||||
}
|
||||
}
|
||||
const handler = handlers[type];
|
||||
await handler();
|
||||
}
|
||||
|
||||
response.end();
|
||||
}
|
||||
|
||||
private async syncUsersV1(response: Writable, checkpointMap: CheckpointMap) {
|
||||
const deletes = this.syncRepository.getUserDeletes(checkpointMap[SyncEntityType.UserDeleteV1]);
|
||||
const deleteType = SyncEntityType.UserDeleteV1;
|
||||
const deletes = this.syncRepository.getUserDeletes(checkpointMap[deleteType]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: SyncEntityType.UserDeleteV1, ids: [id], data });
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const upserts = this.syncRepository.getUserUpserts(checkpointMap[SyncEntityType.UserV1]);
|
||||
const upsertType = SyncEntityType.UserV1;
|
||||
const upserts = this.syncRepository.getUserUpserts(checkpointMap[upsertType]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: SyncEntityType.UserV1, ids: [updateId], data });
|
||||
send(response, { type: upsertType, ids: [updateId], data });
|
||||
}
|
||||
}
|
||||
|
||||
private async syncPartnersV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto) {
|
||||
const deletes = this.syncRepository.getPartnerDeletes(auth.user.id, checkpointMap[SyncEntityType.PartnerDeleteV1]);
|
||||
const deleteType = SyncEntityType.PartnerDeleteV1;
|
||||
const deletes = this.syncRepository.getPartnerDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: SyncEntityType.PartnerDeleteV1, ids: [id], data });
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const upserts = this.syncRepository.getPartnerUpserts(auth.user.id, checkpointMap[SyncEntityType.PartnerV1]);
|
||||
const upsertType = SyncEntityType.PartnerV1;
|
||||
const upserts = this.syncRepository.getPartnerUpserts(auth.user.id, checkpointMap[upsertType]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: SyncEntityType.PartnerV1, ids: [updateId], data });
|
||||
send(response, { type: upsertType, ids: [updateId], data });
|
||||
}
|
||||
}
|
||||
|
||||
private async syncAssetsV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto) {
|
||||
const deletes = this.syncRepository.getAssetDeletes(auth.user.id, checkpointMap[SyncEntityType.AssetDeleteV1]);
|
||||
const deleteType = SyncEntityType.AssetDeleteV1;
|
||||
const deletes = this.syncRepository.getAssetDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: SyncEntityType.AssetDeleteV1, ids: [id], data });
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const upserts = this.syncRepository.getAssetUpserts(auth.user.id, checkpointMap[SyncEntityType.AssetV1]);
|
||||
const upsertType = SyncEntityType.AssetV1;
|
||||
const upserts = this.syncRepository.getAssetUpserts(auth.user.id, checkpointMap[upsertType]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: SyncEntityType.AssetV1, ids: [updateId], data: mapSyncAssetV1(data) });
|
||||
send(response, { type: upsertType, ids: [updateId], data: mapSyncAssetV1(data) });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -231,21 +195,17 @@ export class SyncService extends BaseService {
|
|||
auth: AuthDto,
|
||||
sessionId: string,
|
||||
) {
|
||||
const backfillType = SyncEntityType.PartnerAssetBackfillV1;
|
||||
const upsertType = SyncEntityType.PartnerAssetV1;
|
||||
const deleteType = SyncEntityType.PartnerAssetDeleteV1;
|
||||
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
|
||||
const deletes = this.syncRepository.getPartnerAssetDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const backfillType = SyncEntityType.PartnerAssetBackfillV1;
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const partners = await this.syncRepository.getPartnerBackfill(auth.user.id, backfillCheckpoint?.updateId);
|
||||
|
||||
const upsertType = SyncEntityType.PartnerAssetV1;
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
if (upsertCheckpoint) {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
|
|
@ -283,9 +243,10 @@ export class SyncService extends BaseService {
|
|||
}
|
||||
|
||||
private async syncAssetExifsV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto) {
|
||||
const upserts = this.syncRepository.getAssetExifsUpserts(auth.user.id, checkpointMap[SyncEntityType.AssetExifV1]);
|
||||
const upsertType = SyncEntityType.AssetExifV1;
|
||||
const upserts = this.syncRepository.getAssetExifsUpserts(auth.user.id, checkpointMap[upsertType]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: SyncEntityType.AssetExifV1, ids: [updateId], data });
|
||||
send(response, { type: upsertType, ids: [updateId], data });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -296,13 +257,11 @@ export class SyncService extends BaseService {
|
|||
sessionId: string,
|
||||
) {
|
||||
const backfillType = SyncEntityType.PartnerAssetExifBackfillV1;
|
||||
const upsertType = SyncEntityType.PartnerAssetExifV1;
|
||||
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
|
||||
const partners = await this.syncRepository.getPartnerBackfill(auth.user.id, backfillCheckpoint?.updateId);
|
||||
|
||||
const upsertType = SyncEntityType.PartnerAssetExifV1;
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
if (upsertCheckpoint) {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
|
|
@ -336,33 +295,31 @@ export class SyncService extends BaseService {
|
|||
}
|
||||
|
||||
private async syncAlbumsV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto) {
|
||||
const deletes = this.syncRepository.getAlbumDeletes(auth.user.id, checkpointMap[SyncEntityType.AlbumDeleteV1]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: SyncEntityType.AlbumDeleteV1, ids: [id], data });
|
||||
}
|
||||
|
||||
const upserts = this.syncRepository.getAlbumUpserts(auth.user.id, checkpointMap[SyncEntityType.AlbumV1]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: SyncEntityType.AlbumV1, ids: [updateId], data });
|
||||
}
|
||||
}
|
||||
|
||||
private async syncAlbumUsersV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto, sessionId: string) {
|
||||
const backfillType = SyncEntityType.AlbumUserBackfillV1;
|
||||
const upsertType = SyncEntityType.AlbumUserV1;
|
||||
const deleteType = SyncEntityType.AlbumUserDeleteV1;
|
||||
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
|
||||
const deletes = this.syncRepository.getAlbumUserDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||
|
||||
const deleteType = SyncEntityType.AlbumDeleteV1;
|
||||
const deletes = this.syncRepository.getAlbumDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const albums = await this.syncRepository.getAlbumBackfill(auth.user.id, backfillCheckpoint?.updateId);
|
||||
const upsertType = SyncEntityType.AlbumV1;
|
||||
const upserts = this.syncRepository.getAlbumUpserts(auth.user.id, checkpointMap[upsertType]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: upsertType, ids: [updateId], data });
|
||||
}
|
||||
}
|
||||
|
||||
private async syncAlbumUsersV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto, sessionId: string) {
|
||||
const deleteType = SyncEntityType.AlbumUserDeleteV1;
|
||||
const deletes = this.syncRepository.getAlbumUserDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const backfillType = SyncEntityType.AlbumUserBackfillV1;
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const albums = await this.syncRepository.getAlbumBackfill(auth.user.id, backfillCheckpoint?.updateId);
|
||||
const upsertType = SyncEntityType.AlbumUserV1;
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
if (upsertCheckpoint) {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
|
|
@ -397,13 +354,10 @@ export class SyncService extends BaseService {
|
|||
|
||||
private async syncAlbumAssetsV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto, sessionId: string) {
|
||||
const backfillType = SyncEntityType.AlbumAssetBackfillV1;
|
||||
const upsertType = SyncEntityType.AlbumAssetV1;
|
||||
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
|
||||
const albums = await this.syncRepository.getAlbumBackfill(auth.user.id, backfillCheckpoint?.updateId);
|
||||
|
||||
const upsertType = SyncEntityType.AlbumAssetV1;
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
if (upsertCheckpoint) {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
|
|
@ -443,13 +397,10 @@ export class SyncService extends BaseService {
|
|||
sessionId: string,
|
||||
) {
|
||||
const backfillType = SyncEntityType.AlbumAssetExifBackfillV1;
|
||||
const upsertType = SyncEntityType.AlbumAssetExifV1;
|
||||
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
|
||||
const albums = await this.syncRepository.getAlbumBackfill(auth.user.id, backfillCheckpoint?.updateId);
|
||||
|
||||
const upsertType = SyncEntityType.AlbumAssetExifV1;
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
if (upsertCheckpoint) {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
|
|
@ -488,22 +439,17 @@ export class SyncService extends BaseService {
|
|||
auth: AuthDto,
|
||||
sessionId: string,
|
||||
) {
|
||||
const backfillType = SyncEntityType.AlbumToAssetBackfillV1;
|
||||
const upsertType = SyncEntityType.AlbumToAssetV1;
|
||||
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
|
||||
const deletes = this.syncRepository.getAlbumToAssetDeletes(
|
||||
auth.user.id,
|
||||
checkpointMap[SyncEntityType.AlbumToAssetDeleteV1],
|
||||
);
|
||||
const deleteType = SyncEntityType.AlbumToAssetDeleteV1;
|
||||
const deletes = this.syncRepository.getAlbumToAssetDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: SyncEntityType.AlbumToAssetDeleteV1, ids: [id], data });
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const backfillType = SyncEntityType.AlbumToAssetBackfillV1;
|
||||
const backfillCheckpoint = checkpointMap[backfillType];
|
||||
const albums = await this.syncRepository.getAlbumBackfill(auth.user.id, backfillCheckpoint?.updateId);
|
||||
|
||||
const upsertType = SyncEntityType.AlbumToAssetV1;
|
||||
const upsertCheckpoint = checkpointMap[upsertType];
|
||||
if (upsertCheckpoint) {
|
||||
const endId = upsertCheckpoint.updateId;
|
||||
|
||||
|
|
@ -536,6 +482,34 @@ export class SyncService extends BaseService {
|
|||
}
|
||||
}
|
||||
|
||||
private async syncMemoriesV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto) {
|
||||
const deleteType = SyncEntityType.MemoryDeleteV1;
|
||||
const deletes = this.syncRepository.getMemoryDeletes(auth.user.id, checkpointMap[SyncEntityType.MemoryDeleteV1]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const upsertType = SyncEntityType.MemoryV1;
|
||||
const upserts = this.syncRepository.getMemoryUpserts(auth.user.id, checkpointMap[upsertType]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: upsertType, ids: [updateId], data });
|
||||
}
|
||||
}
|
||||
|
||||
private async syncMemoryAssetsV1(response: Writable, checkpointMap: CheckpointMap, auth: AuthDto) {
|
||||
const deleteType = SyncEntityType.MemoryToAssetDeleteV1;
|
||||
const deletes = this.syncRepository.getMemoryAssetDeletes(auth.user.id, checkpointMap[deleteType]);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
send(response, { type: deleteType, ids: [id], data });
|
||||
}
|
||||
|
||||
const upsertType = SyncEntityType.MemoryToAssetV1;
|
||||
const upserts = this.syncRepository.getMemoryAssetUpserts(auth.user.id, checkpointMap[upsertType]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
send(response, { type: upsertType, ids: [updateId], data });
|
||||
}
|
||||
}
|
||||
|
||||
private async upsertBackfillCheckpoint(item: { type: SyncEntityType; sessionId: string; createId: string }) {
|
||||
const { type, sessionId, createId } = item;
|
||||
await this.syncRepository.upsertCheckpoints([
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue