0
Fork 0
mirror of https://github.com/immich-app/immich.git synced 2025-03-11 02:23:09 -05:00

fix: storage template failure after re-upload and previous fail (#16611)

fix: storage template breaks when files are re-uploaded after a move failure
This commit is contained in:
Zack Pollard 2025-03-05 15:00:37 +00:00 committed by GitHub
parent 3f4bbab4eb
commit 9922c8de59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 81 additions and 3 deletions

View file

@ -10,7 +10,7 @@ export class MoveEntity {
@PrimaryGeneratedColumn('uuid')
id!: string;
@Column({ type: 'varchar' })
@Column({ type: 'uuid' })
entityId!: string;
@Column({ type: 'varchar' })

View file

@ -0,0 +1,26 @@
import { MigrationInterface, QueryRunner } from 'typeorm';
export class MoveHistoryUuidEntityId1741179334403 implements MigrationInterface {
name = 'MoveHistoryUuidEntityId1741179334403';
public async up(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "move_history" ALTER COLUMN "entityId" TYPE uuid USING "entityId"::uuid;`);
await queryRunner.query(`delete from "move_history"
where
"move_history"."entityId" not in (
select
"id"
from
"assets"
where
"assets"."id" = "move_history"."entityId"
)
and "move_history"."pathType" = 'original'
`)
}
public async down(queryRunner: QueryRunner): Promise<void> {
await queryRunner.query(`ALTER TABLE "move_history" ALTER COLUMN "entityId" TYPE character varying`);
}
}

View file

@ -15,3 +15,22 @@ where
"id" = $1
returning
*
-- MoveRepository.cleanMoveHistory
delete from "move_history"
where
"move_history"."entityId" not in (
select
"id"
from
"assets"
where
"assets"."id" = "move_history"."entityId"
)
and "move_history"."pathType" = 'original'
-- MoveRepository.cleanMoveHistorySingle
delete from "move_history"
where
"move_history"."pathType" = 'original'
and "entityId" = $1

View file

@ -1,10 +1,10 @@
import { Injectable } from '@nestjs/common';
import { Insertable, Kysely, Updateable } from 'kysely';
import { Insertable, Kysely, sql, Updateable } from 'kysely';
import { InjectKysely } from 'nestjs-kysely';
import { DB, MoveHistory } from 'src/db';
import { DummyValue, GenerateSql } from 'src/decorators';
import { MoveEntity } from 'src/entities/move.entity';
import { PathType } from 'src/enum';
import { AssetPathType, PathType } from 'src/enum';
export type MoveCreate = Pick<MoveEntity, 'oldPath' | 'newPath' | 'entityId' | 'pathType'> & Partial<MoveEntity>;
@ -47,4 +47,28 @@ export class MoveRepository {
.returningAll()
.executeTakeFirstOrThrow() as unknown as Promise<MoveEntity>;
}
@GenerateSql()
async cleanMoveHistory(): Promise<void> {
await this.db
.deleteFrom('move_history')
.where((eb) =>
eb(
'move_history.entityId',
'not in',
eb.selectFrom('assets').select('id').whereRef('assets.id', '=', 'move_history.entityId'),
),
)
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
.execute();
}
@GenerateSql()
async cleanMoveHistorySingle(assetId: string): Promise<void> {
await this.db
.deleteFrom('move_history')
.where('move_history.pathType', '=', sql.lit(AssetPathType.ORIGINAL))
.where('entityId', '=', assetId)
.execute();
}
}

View file

@ -152,6 +152,7 @@ export class StorageTemplateService extends BaseService {
this.logger.log('Storage template migration disabled, skipping');
return JobStatus.SKIPPED;
}
await this.moveRepository.cleanMoveHistory();
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
this.assetRepository.getAll(pagination, { withExif: true, withArchived: true }),
);
@ -175,6 +176,12 @@ export class StorageTemplateService extends BaseService {
return JobStatus.SUCCESS;
}
@OnEvent({ name: 'asset.delete' })
async handleMoveHistoryCleanup({ assetId }: ArgOf<'asset.delete'>) {
this.logger.debug(`Cleaning up move history for asset ${assetId}`);
await this.moveRepository.cleanMoveHistorySingle(assetId);
}
async moveAsset(asset: AssetEntity, metadata: MoveAssetMetadata) {
if (asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) {
// External assets are not affected by storage template

View file

@ -8,5 +8,7 @@ export const newMoveRepositoryMock = (): Mocked<RepositoryInterface<MoveReposito
getByEntity: vitest.fn(),
update: vitest.fn(),
delete: vitest.fn(),
cleanMoveHistory: vitest.fn(),
cleanMoveHistorySingle: vitest.fn(),
};
};