mirror of
https://github.com/immich-app/immich
synced 2025-11-14 17:36:12 +00:00
parent
869839f642
commit
fe702ba6d7
19 changed files with 614 additions and 8 deletions
9
server/src/db.d.ts
vendored
9
server/src/db.d.ts
vendored
|
|
@ -272,6 +272,13 @@ export interface NaturalearthCountries {
|
|||
type: string;
|
||||
}
|
||||
|
||||
export interface PartnersAudit {
|
||||
deletedAt: Generated<Timestamp>;
|
||||
id: Generated<string>;
|
||||
sharedById: string;
|
||||
sharedWithId: string;
|
||||
}
|
||||
|
||||
export interface Partners {
|
||||
createdAt: Generated<Timestamp>;
|
||||
inTimeline: Generated<boolean>;
|
||||
|
|
@ -316,7 +323,6 @@ export interface SessionSyncCheckpoints {
|
|||
updateId: Generated<string>;
|
||||
}
|
||||
|
||||
|
||||
export interface SharedLinkAsset {
|
||||
assetsId: string;
|
||||
sharedLinksId: string;
|
||||
|
|
@ -462,6 +468,7 @@ export interface DB {
|
|||
migrations: Migrations;
|
||||
move_history: MoveHistory;
|
||||
naturalearth_countries: NaturalearthCountries;
|
||||
partners_audit: PartnersAudit;
|
||||
partners: Partners;
|
||||
person: Person;
|
||||
sessions: Sessions;
|
||||
|
|
|
|||
|
|
@ -45,15 +45,30 @@ export class SyncUserDeleteV1 {
|
|||
userId!: string;
|
||||
}
|
||||
|
||||
export class SyncPartnerV1 {
|
||||
sharedById!: string;
|
||||
sharedWithId!: string;
|
||||
inTimeline!: boolean;
|
||||
}
|
||||
|
||||
export class SyncPartnerDeleteV1 {
|
||||
sharedById!: string;
|
||||
sharedWithId!: string;
|
||||
}
|
||||
|
||||
export type SyncItem = {
|
||||
[SyncEntityType.UserV1]: SyncUserV1;
|
||||
[SyncEntityType.UserDeleteV1]: SyncUserDeleteV1;
|
||||
[SyncEntityType.PartnerV1]: SyncPartnerV1;
|
||||
[SyncEntityType.PartnerDeleteV1]: SyncPartnerDeleteV1;
|
||||
};
|
||||
|
||||
const responseDtos = [
|
||||
//
|
||||
SyncUserV1,
|
||||
SyncUserDeleteV1,
|
||||
SyncPartnerV1,
|
||||
SyncPartnerDeleteV1,
|
||||
];
|
||||
|
||||
export const extraSyncModels = responseDtos;
|
||||
|
|
|
|||
19
server/src/entities/partner-audit.entity.ts
Normal file
19
server/src/entities/partner-audit.entity.ts
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
import { Column, CreateDateColumn, Entity, Index, PrimaryColumn } from 'typeorm';
|
||||
|
||||
@Entity('partners_audit')
|
||||
export class PartnerAuditEntity {
|
||||
@PrimaryColumn({ type: 'uuid', nullable: false, default: () => 'immich_uuid_v7()' })
|
||||
id!: string;
|
||||
|
||||
@Index('IDX_partners_audit_shared_by_id')
|
||||
@Column({ type: 'uuid' })
|
||||
sharedById!: string;
|
||||
|
||||
@Index('IDX_partners_audit_shared_with_id')
|
||||
@Column({ type: 'uuid' })
|
||||
sharedWithId!: string;
|
||||
|
||||
@Index('IDX_partners_audit_deleted_at')
|
||||
@CreateDateColumn({ type: 'timestamptz', default: () => 'clock_timestamp()' })
|
||||
deletedAt!: Date;
|
||||
}
|
||||
|
|
@ -548,9 +548,12 @@ export enum DatabaseLock {
|
|||
|
||||
export enum SyncRequestType {
|
||||
UsersV1 = 'UsersV1',
|
||||
PartnersV1 = 'PartnersV1',
|
||||
}
|
||||
|
||||
export enum SyncEntityType {
|
||||
UserV1 = 'UserV1',
|
||||
UserDeleteV1 = 'UserDeleteV1',
|
||||
PartnerV1 = 'PartnerV1',
|
||||
PartnerDeleteV1 = 'PartnerDeleteV1',
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,38 @@
|
|||
import { MigrationInterface, QueryRunner } from "typeorm";
|
||||
|
||||
export class CreatePartnersAuditTable1740739778549 implements MigrationInterface {
|
||||
name = 'CreatePartnersAuditTable1740739778549'
|
||||
|
||||
public async up(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`CREATE TABLE "partners_audit" ("id" uuid NOT NULL DEFAULT immich_uuid_v7(), "sharedById" uuid NOT NULL, "sharedWithId" uuid NOT NULL, "deletedAt" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT clock_timestamp(), CONSTRAINT "PK_952b50217ff78198a7e380f0359" PRIMARY KEY ("id"))`);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_partners_audit_shared_by_id" ON "partners_audit" ("sharedById") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_partners_audit_shared_with_id" ON "partners_audit" ("sharedWithId") `);
|
||||
await queryRunner.query(`CREATE INDEX "IDX_partners_audit_deleted_at" ON "partners_audit" ("deletedAt") `);
|
||||
await queryRunner.query(`CREATE OR REPLACE FUNCTION partners_delete_audit() RETURNS TRIGGER AS
|
||||
$$
|
||||
BEGIN
|
||||
INSERT INTO partners_audit ("sharedById", "sharedWithId")
|
||||
SELECT "sharedById", "sharedWithId"
|
||||
FROM OLD;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql`
|
||||
);
|
||||
await queryRunner.query(`CREATE OR REPLACE TRIGGER partners_delete_audit
|
||||
AFTER DELETE ON partners
|
||||
REFERENCING OLD TABLE AS OLD
|
||||
FOR EACH STATEMENT
|
||||
EXECUTE FUNCTION partners_delete_audit();
|
||||
`);
|
||||
}
|
||||
|
||||
public async down(queryRunner: QueryRunner): Promise<void> {
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_partners_audit_deleted_at"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_partners_audit_shared_with_id"`);
|
||||
await queryRunner.query(`DROP INDEX "public"."IDX_partners_audit_shared_by_id"`);
|
||||
await queryRunner.query(`DROP TRIGGER partners_delete_audit`);
|
||||
await queryRunner.query(`DROP FUNCTION partners_delete_audit`);
|
||||
await queryRunner.query(`DROP TABLE "partners_audit"`);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -56,4 +56,26 @@ export class SyncRepository {
|
|||
.orderBy(['id asc'])
|
||||
.stream();
|
||||
}
|
||||
|
||||
getPartnerUpserts(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('partners')
|
||||
.select(['sharedById', 'sharedWithId', 'inTimeline', 'updateId'])
|
||||
.$if(!!ack, (qb) => qb.where('updateId', '>', ack!.updateId))
|
||||
.where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)]))
|
||||
.where('updatedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.orderBy(['updateId asc'])
|
||||
.stream();
|
||||
}
|
||||
|
||||
getPartnerDeletes(userId: string, ack?: SyncAck) {
|
||||
return this.db
|
||||
.selectFrom('partners_audit')
|
||||
.select(['id', 'sharedById', 'sharedWithId'])
|
||||
.$if(!!ack, (qb) => qb.where('id', '>', ack!.updateId))
|
||||
.where((eb) => eb.or([eb('sharedById', '=', userId), eb('sharedWithId', '=', userId)]))
|
||||
.where('deletedAt', '<', sql.raw<Date>("now() - interval '1 millisecond'"))
|
||||
.orderBy(['id asc'])
|
||||
.stream();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ const FULL_SYNC = { needsFullSync: true, deleted: [], upserted: [] };
|
|||
const SYNC_TYPES_ORDER = [
|
||||
//
|
||||
SyncRequestType.UsersV1,
|
||||
SyncRequestType.PartnersV1,
|
||||
];
|
||||
|
||||
const throwSessionRequired = () => {
|
||||
|
|
@ -81,8 +82,6 @@ export class SyncService extends BaseService {
|
|||
checkpoints.map(({ type, ack }) => [type, fromAck(ack)]),
|
||||
);
|
||||
|
||||
// TODO pre-filter/sort list based on optimal sync order
|
||||
|
||||
for (const type of SYNC_TYPES_ORDER.filter((type) => dto.types.includes(type))) {
|
||||
switch (type) {
|
||||
case SyncRequestType.UsersV1: {
|
||||
|
|
@ -99,6 +98,23 @@ export class SyncService extends BaseService {
|
|||
break;
|
||||
}
|
||||
|
||||
case SyncRequestType.PartnersV1: {
|
||||
const deletes = this.syncRepository.getPartnerDeletes(
|
||||
auth.user.id,
|
||||
checkpointMap[SyncEntityType.PartnerDeleteV1],
|
||||
);
|
||||
for await (const { id, ...data } of deletes) {
|
||||
response.write(serialize({ type: SyncEntityType.PartnerDeleteV1, updateId: id, data }));
|
||||
}
|
||||
|
||||
const upserts = this.syncRepository.getPartnerUpserts(auth.user.id, checkpointMap[SyncEntityType.PartnerV1]);
|
||||
for await (const { updateId, ...data } of upserts) {
|
||||
response.write(serialize({ type: SyncEntityType.PartnerV1, updateId, data }));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
default: {
|
||||
this.logger.warn(`Unsupported sync type: ${type}`);
|
||||
break;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue