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')
|
@PrimaryGeneratedColumn('uuid')
|
||||||
id!: string;
|
id!: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar' })
|
@Column({ type: 'uuid' })
|
||||||
entityId!: string;
|
entityId!: string;
|
||||||
|
|
||||||
@Column({ type: 'varchar' })
|
@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
|
"id" = $1
|
||||||
returning
|
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 { Injectable } from '@nestjs/common';
|
||||||
import { Insertable, Kysely, Updateable } from 'kysely';
|
import { Insertable, Kysely, sql, Updateable } from 'kysely';
|
||||||
import { InjectKysely } from 'nestjs-kysely';
|
import { InjectKysely } from 'nestjs-kysely';
|
||||||
import { DB, MoveHistory } from 'src/db';
|
import { DB, MoveHistory } from 'src/db';
|
||||||
import { DummyValue, GenerateSql } from 'src/decorators';
|
import { DummyValue, GenerateSql } from 'src/decorators';
|
||||||
import { MoveEntity } from 'src/entities/move.entity';
|
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>;
|
export type MoveCreate = Pick<MoveEntity, 'oldPath' | 'newPath' | 'entityId' | 'pathType'> & Partial<MoveEntity>;
|
||||||
|
|
||||||
|
@ -47,4 +47,28 @@ export class MoveRepository {
|
||||||
.returningAll()
|
.returningAll()
|
||||||
.executeTakeFirstOrThrow() as unknown as Promise<MoveEntity>;
|
.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');
|
this.logger.log('Storage template migration disabled, skipping');
|
||||||
return JobStatus.SKIPPED;
|
return JobStatus.SKIPPED;
|
||||||
}
|
}
|
||||||
|
await this.moveRepository.cleanMoveHistory();
|
||||||
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
|
const assetPagination = usePagination(JOBS_ASSET_PAGINATION_SIZE, (pagination) =>
|
||||||
this.assetRepository.getAll(pagination, { withExif: true, withArchived: true }),
|
this.assetRepository.getAll(pagination, { withExif: true, withArchived: true }),
|
||||||
);
|
);
|
||||||
|
@ -175,6 +176,12 @@ export class StorageTemplateService extends BaseService {
|
||||||
return JobStatus.SUCCESS;
|
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) {
|
async moveAsset(asset: AssetEntity, metadata: MoveAssetMetadata) {
|
||||||
if (asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) {
|
if (asset.isExternal || StorageCore.isAndroidMotionPath(asset.originalPath)) {
|
||||||
// External assets are not affected by storage template
|
// External assets are not affected by storage template
|
||||||
|
|
|
@ -8,5 +8,7 @@ export const newMoveRepositoryMock = (): Mocked<RepositoryInterface<MoveReposito
|
||||||
getByEntity: vitest.fn(),
|
getByEntity: vitest.fn(),
|
||||||
update: vitest.fn(),
|
update: vitest.fn(),
|
||||||
delete: vitest.fn(),
|
delete: vitest.fn(),
|
||||||
|
cleanMoveHistory: vitest.fn(),
|
||||||
|
cleanMoveHistorySingle: vitest.fn(),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
Loading…
Add table
Reference in a new issue