From 21becbf1b0be867d0e303a93364f37dec86405a0 Mon Sep 17 00:00:00 2001 From: Daniel Dietzler <36593685+danieldietzler@users.noreply.github.com> Date: Tue, 15 Apr 2025 21:49:15 +0200 Subject: [PATCH] refactor: dedicated query for asset migration job (#17631) --- server/src/cores/storage.core.ts | 16 ++++++------ server/src/database.ts | 7 ++++++ server/src/queries/asset.job.repository.sql | 25 +++++++++++++++++++ .../src/repositories/asset-job.repository.ts | 10 ++++++++ server/src/services/media.service.spec.ts | 3 ++- server/src/services/media.service.ts | 2 +- 6 files changed, 54 insertions(+), 9 deletions(-) diff --git a/server/src/cores/storage.core.ts b/server/src/cores/storage.core.ts index 9985506f24..dd3edd3ab1 100644 --- a/server/src/cores/storage.core.ts +++ b/server/src/cores/storage.core.ts @@ -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, diff --git a/server/src/database.ts b/server/src/database.ts index bb8189ddd4..7766a13f02 100644 --- a/server/src/database.ts +++ b/server/src/database.ts @@ -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; diff --git a/server/src/queries/asset.job.repository.sql b/server/src/queries/asset.job.repository.sql index 8fd6d1daf5..15172c55eb 100644 --- a/server/src/queries/asset.job.repository.sql +++ b/server/src/queries/asset.job.repository.sql @@ -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", diff --git a/server/src/repositories/asset-job.repository.ts b/server/src/repositories/asset-job.repository.ts index b0ae001242..3824c13519 100644 --- a/server/src/repositories/asset-job.repository.ts +++ b/server/src/repositories/asset-job.repository.ts @@ -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') diff --git a/server/src/services/media.service.spec.ts b/server/src/services/media.service.spec.ts index b6a13bc83b..6fe14b954b 100644 --- a/server/src/services/media.service.spec.ts +++ b/server/src/services/media.service.spec.ts @@ -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', diff --git a/server/src/services/media.service.ts b/server/src/services/media.service.ts index 5fdee7c346..f855168898 100644 --- a/server/src/services/media.service.ts +++ b/server/src/services/media.service.ts @@ -121,7 +121,7 @@ export class MediaService extends BaseService { @OnJob({ name: JobName.MIGRATE_ASSET, queue: QueueName.MIGRATION }) async handleAssetMigration({ id }: JobOf): Promise { 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; }