mirror of
https://github.com/immich-app/immich.git
synced 2025-05-24 01:12:58 -04:00
refactor: dedicated query for asset migration job (#17631)
This commit is contained in:
parent
26f0ea4cb5
commit
21becbf1b0
@ -1,7 +1,7 @@
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { dirname, join, resolve } from 'node:path';
|
||||
import { APP_MEDIA_LOCATION } from 'src/constants';
|
||||
import { AssetEntity } from 'src/entities/asset.entity';
|
||||
import { StorageAsset } from 'src/database';
|
||||
import { AssetFileType, AssetPathType, ImageFormat, PathType, PersonPathType, StorageFolder } from 'src/enum';
|
||||
import { AssetRepository } from 'src/repositories/asset.repository';
|
||||
import { ConfigRepository } from 'src/repositories/config.repository';
|
||||
@ -28,6 +28,8 @@ export interface MoveRequest {
|
||||
export type GeneratedImageType = AssetPathType.PREVIEW | AssetPathType.THUMBNAIL | AssetPathType.FULLSIZE;
|
||||
export type GeneratedAssetType = GeneratedImageType | AssetPathType.ENCODED_VIDEO;
|
||||
|
||||
type ThumbnailPathEntity = { id: string; ownerId: string };
|
||||
|
||||
let instance: StorageCore | null;
|
||||
|
||||
export class StorageCore {
|
||||
@ -84,19 +86,19 @@ export class StorageCore {
|
||||
return join(APP_MEDIA_LOCATION, folder);
|
||||
}
|
||||
|
||||
static getPersonThumbnailPath(person: { id: string; ownerId: string }) {
|
||||
static getPersonThumbnailPath(person: ThumbnailPathEntity) {
|
||||
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, person.ownerId, `${person.id}.jpeg`);
|
||||
}
|
||||
|
||||
static getImagePath(asset: AssetEntity, type: GeneratedImageType, format: ImageFormat) {
|
||||
static getImagePath(asset: ThumbnailPathEntity, type: GeneratedImageType, format: ImageFormat) {
|
||||
return StorageCore.getNestedPath(StorageFolder.THUMBNAILS, asset.ownerId, `${asset.id}-${type}.${format}`);
|
||||
}
|
||||
|
||||
static getEncodedVideoPath(asset: AssetEntity) {
|
||||
static getEncodedVideoPath(asset: ThumbnailPathEntity) {
|
||||
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${asset.id}.mp4`);
|
||||
}
|
||||
|
||||
static getAndroidMotionPath(asset: AssetEntity, uuid: string) {
|
||||
static getAndroidMotionPath(asset: ThumbnailPathEntity, uuid: string) {
|
||||
return StorageCore.getNestedPath(StorageFolder.ENCODED_VIDEO, asset.ownerId, `${uuid}-MP.mp4`);
|
||||
}
|
||||
|
||||
@ -114,7 +116,7 @@ export class StorageCore {
|
||||
return normalizedPath.startsWith(normalizedAppMediaLocation);
|
||||
}
|
||||
|
||||
async moveAssetImage(asset: AssetEntity, pathType: GeneratedImageType, format: ImageFormat) {
|
||||
async moveAssetImage(asset: StorageAsset, pathType: GeneratedImageType, format: ImageFormat) {
|
||||
const { id: entityId, files } = asset;
|
||||
const oldFile = getAssetFile(files, pathType);
|
||||
return this.moveFile({
|
||||
@ -125,7 +127,7 @@ export class StorageCore {
|
||||
});
|
||||
}
|
||||
|
||||
async moveAssetVideo(asset: AssetEntity) {
|
||||
async moveAssetVideo(asset: StorageAsset) {
|
||||
return this.moveFile({
|
||||
entityId: asset.id,
|
||||
pathType: AssetPathType.ENCODED_VIDEO,
|
||||
|
@ -121,6 +121,13 @@ export type UserAdmin = User & {
|
||||
metadata: UserMetadataItem[];
|
||||
};
|
||||
|
||||
export type StorageAsset = {
|
||||
id: string;
|
||||
ownerId: string;
|
||||
files: AssetFile[];
|
||||
encodedVideoPath: string | null;
|
||||
};
|
||||
|
||||
export type Asset = {
|
||||
createdAt: Date;
|
||||
updatedAt: Date;
|
||||
|
@ -90,6 +90,31 @@ where
|
||||
or "assets"."thumbhash" is null
|
||||
)
|
||||
|
||||
-- AssetJobRepository.getForMigrationJob
|
||||
select
|
||||
"assets"."id",
|
||||
"assets"."ownerId",
|
||||
"assets"."encodedVideoPath",
|
||||
(
|
||||
select
|
||||
coalesce(json_agg(agg), '[]')
|
||||
from
|
||||
(
|
||||
select
|
||||
"asset_files"."id",
|
||||
"asset_files"."path",
|
||||
"asset_files"."type"
|
||||
from
|
||||
"asset_files"
|
||||
where
|
||||
"asset_files"."assetId" = "assets"."id"
|
||||
) as agg
|
||||
) as "files"
|
||||
from
|
||||
"assets"
|
||||
where
|
||||
"assets"."id" = $1
|
||||
|
||||
-- AssetJobRepository.getForStorageTemplateJob
|
||||
select
|
||||
"assets"."id",
|
||||
|
@ -77,6 +77,16 @@ export class AssetJobRepository {
|
||||
.stream();
|
||||
}
|
||||
|
||||
@GenerateSql({ params: [DummyValue.UUID] })
|
||||
getForMigrationJob(id: string) {
|
||||
return this.db
|
||||
.selectFrom('assets')
|
||||
.select(['assets.id', 'assets.ownerId', 'assets.encodedVideoPath'])
|
||||
.select(withFiles)
|
||||
.where('assets.id', '=', id)
|
||||
.executeTakeFirst();
|
||||
}
|
||||
|
||||
private storageTemplateAssetQuery() {
|
||||
return this.db
|
||||
.selectFrom('assets')
|
||||
|
@ -191,13 +191,14 @@ describe(MediaService.name, () => {
|
||||
|
||||
describe('handleAssetMigration', () => {
|
||||
it('should fail if asset does not exist', async () => {
|
||||
mocks.assetJob.getForMigrationJob.mockResolvedValue(void 0);
|
||||
await expect(sut.handleAssetMigration({ id: assetStub.image.id })).resolves.toBe(JobStatus.FAILED);
|
||||
|
||||
expect(mocks.move.getByEntity).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
it('should move asset files', async () => {
|
||||
mocks.asset.getByIds.mockResolvedValue([assetStub.image]);
|
||||
mocks.assetJob.getForMigrationJob.mockResolvedValue(assetStub.image);
|
||||
mocks.move.create.mockResolvedValue({
|
||||
entityId: assetStub.image.id,
|
||||
id: 'move-id',
|
||||
|
@ -121,7 +121,7 @@ export class MediaService extends BaseService {
|
||||
@OnJob({ name: JobName.MIGRATE_ASSET, queue: QueueName.MIGRATION })
|
||||
async handleAssetMigration({ id }: JobOf<JobName.MIGRATE_ASSET>): Promise<JobStatus> {
|
||||
const { image } = await this.getConfig({ withCache: true });
|
||||
const [asset] = await this.assetRepository.getByIds([id], { files: true });
|
||||
const asset = await this.assetJobRepository.getForMigrationJob(id);
|
||||
if (!asset) {
|
||||
return JobStatus.FAILED;
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user