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:
parent
3f4bbab4eb
commit
9922c8de59
6 changed files with 81 additions and 3 deletions
|
@ -10,7 +10,7 @@ export class MoveEntity {
|
|||
@PrimaryGeneratedColumn('uuid')
|
||||
id!: string;
|
||||
|
||||
@Column({ type: 'varchar' })
|
||||
@Column({ type: 'uuid' })
|
||||
entityId!: string;
|
||||
|
||||
@Column({ type: 'varchar' })
|
||||
|
|
|
@ -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`);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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(),
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Add table
Reference in a new issue