refactor: more job query stuff (#17658)

This commit is contained in:
Daniel Dietzler 2025-04-16 22:10:20 +02:00 committed by GitHub
parent 85c2d36d99
commit 1bbfacfc09
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 76 additions and 35 deletions

View File

@ -183,7 +183,7 @@ from
where
"assets"."id" = $1
-- AssetJobRepository.getAlbumThumbnailFile
-- AssetJobRepository.getAlbumThumbnailFiles
select
"asset_files"."id",
"asset_files"."path",
@ -192,6 +192,32 @@ from
"asset_files"
where
"asset_files"."assetId" = $1
and "asset_files"."type" = $2
-- AssetJobRepository.getForClipEncoding
select
"assets"."id",
"assets"."isVisible",
(
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"
and "asset_files"."type" = $1
) as agg
) as "files"
from
"assets"
where
"assets"."id" = $2
-- AssetJobRepository.getForStorageTemplateJob
select

View File

@ -117,9 +117,24 @@ export class AssetJobRepository {
.executeTakeFirst();
}
@GenerateSql({ params: [DummyValue.UUID, AssetFileType.THUMBNAIL] })
getAlbumThumbnailFiles(id: string, fileType?: AssetFileType) {
return this.db
.selectFrom('asset_files')
.select(columns.assetFiles)
.where('asset_files.assetId', '=', id)
.$if(!!fileType, (qb) => qb.where('asset_files.type', '=', fileType!))
.execute();
}
@GenerateSql({ params: [DummyValue.UUID] })
getAlbumThumbnailFile(id: string) {
return this.db.selectFrom('asset_files').select(columns.assetFiles).where('asset_files.assetId', '=', id).execute();
getForClipEncoding(id: string) {
return this.db
.selectFrom('assets')
.select(['assets.id', 'assets.isVisible'])
.select((eb) => withFiles(eb, AssetFileType.PREVIEW))
.where('assets.id', '=', id)
.executeTakeFirst();
}
private storageTemplateAssetQuery() {

View File

@ -412,11 +412,12 @@ describe(NotificationService.name, () => {
});
mocks.systemMetadata.get.mockResolvedValue({ server: {} });
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.assetJob.getAlbumThumbnailFile.mockResolvedValue([]);
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.SUCCESS);
expect(mocks.assetJob.getAlbumThumbnailFile).toHaveBeenCalledWith(
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
albumStub.emptyWithValidThumbnail.albumThumbnailAssetId,
AssetFileType.THUMBNAIL,
);
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.SEND_EMAIL,
@ -440,13 +441,14 @@ describe(NotificationService.name, () => {
});
mocks.systemMetadata.get.mockResolvedValue({ server: {} });
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.assetJob.getAlbumThumbnailFile.mockResolvedValue([
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([
{ id: '1', type: AssetFileType.THUMBNAIL, path: 'path-to-thumb.jpg' },
]);
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.SUCCESS);
expect(mocks.assetJob.getAlbumThumbnailFile).toHaveBeenCalledWith(
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
albumStub.emptyWithValidThumbnail.albumThumbnailAssetId,
AssetFileType.THUMBNAIL,
);
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.SEND_EMAIL,
@ -470,11 +472,12 @@ describe(NotificationService.name, () => {
});
mocks.systemMetadata.get.mockResolvedValue({ server: {} });
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.assetJob.getAlbumThumbnailFile.mockResolvedValue(assetStub.image.files);
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([assetStub.image.files[2]]);
await expect(sut.handleAlbumInvite({ id: '', recipientId: '' })).resolves.toBe(JobStatus.SUCCESS);
expect(mocks.assetJob.getAlbumThumbnailFile).toHaveBeenCalledWith(
expect(mocks.assetJob.getAlbumThumbnailFiles).toHaveBeenCalledWith(
albumStub.emptyWithValidThumbnail.albumThumbnailAssetId,
AssetFileType.THUMBNAIL,
);
expect(mocks.job.queue).toHaveBeenCalledWith({
name: JobName.SEND_EMAIL,
@ -506,7 +509,7 @@ describe(NotificationService.name, () => {
});
mocks.user.get.mockResolvedValueOnce(userStub.user1);
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.assetJob.getAlbumThumbnailFile.mockResolvedValue([]);
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
await sut.handleAlbumUpdate({ id: '', recipientIds: [userStub.user1.id] });
expect(mocks.user.get).toHaveBeenCalledWith(userStub.user1.id, { withDeleted: false });
@ -528,7 +531,7 @@ describe(NotificationService.name, () => {
],
});
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.assetJob.getAlbumThumbnailFile.mockResolvedValue([]);
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
await sut.handleAlbumUpdate({ id: '', recipientIds: [userStub.user1.id] });
expect(mocks.user.get).toHaveBeenCalledWith(userStub.user1.id, { withDeleted: false });
@ -550,7 +553,7 @@ describe(NotificationService.name, () => {
],
});
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.assetJob.getAlbumThumbnailFile.mockResolvedValue([]);
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
await sut.handleAlbumUpdate({ id: '', recipientIds: [userStub.user1.id] });
expect(mocks.user.get).toHaveBeenCalledWith(userStub.user1.id, { withDeleted: false });
@ -564,7 +567,7 @@ describe(NotificationService.name, () => {
});
mocks.user.get.mockResolvedValue(userStub.user1);
mocks.notification.renderEmail.mockResolvedValue({ html: '', text: '' });
mocks.assetJob.getAlbumThumbnailFile.mockResolvedValue([]);
mocks.assetJob.getAlbumThumbnailFiles.mockResolvedValue([]);
await sut.handleAlbumUpdate({ id: '', recipientIds: [userStub.user1.id] });
expect(mocks.user.get).toHaveBeenCalledWith(userStub.user1.id, { withDeleted: false });

View File

@ -6,7 +6,6 @@ import { ArgOf } from 'src/repositories/event.repository';
import { EmailTemplate } from 'src/repositories/notification.repository';
import { BaseService } from 'src/services/base.service';
import { EmailImageAttachment, IEntityJob, INotifyAlbumUpdateJob, JobItem, JobOf } from 'src/types';
import { getAssetFile } from 'src/utils/asset.util';
import { getFilenameExtension } from 'src/utils/file';
import { getExternalDomain } from 'src/utils/misc';
import { isEqualObject } from 'src/utils/object';
@ -398,19 +397,18 @@ export class NotificationService extends BaseService {
return;
}
const albumThumbnailFiles = await this.assetJobRepository.getAlbumThumbnailFile(album.albumThumbnailAssetId);
if (albumThumbnailFiles.length === 0) {
return;
}
const albumThumbnailFiles = await this.assetJobRepository.getAlbumThumbnailFiles(
album.albumThumbnailAssetId,
AssetFileType.THUMBNAIL,
);
const thumbnailFile = getAssetFile(albumThumbnailFiles, AssetFileType.THUMBNAIL);
if (!thumbnailFile) {
if (albumThumbnailFiles.length !== 1) {
return;
}
return {
filename: `album-thumbnail${getFilenameExtension(thumbnailFile.path)}`,
path: thumbnailFile.path,
filename: `album-thumbnail${getFilenameExtension(albumThumbnailFiles[0].path)}`,
path: albumThumbnailFiles[0].path,
cid: 'album-thumbnail',
};
}

View File

@ -264,7 +264,7 @@ describe(SmartInfoService.name, () => {
});
it('should skip assets without a resize path', async () => {
mocks.asset.getByIds.mockResolvedValue([assetStub.noResizePath]);
mocks.assetJob.getForClipEncoding.mockResolvedValue({ ...assetStub.noResizePath, files: [] });
expect(await sut.handleEncodeClip({ id: assetStub.noResizePath.id })).toEqual(JobStatus.FAILED);
@ -274,6 +274,7 @@ describe(SmartInfoService.name, () => {
it('should save the returned objects', async () => {
mocks.machineLearning.encodeImage.mockResolvedValue('[0.01, 0.02, 0.03]');
mocks.assetJob.getForClipEncoding.mockResolvedValue({ ...assetStub.image, files: [assetStub.image.files[1]] });
expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.SUCCESS);
@ -286,7 +287,10 @@ describe(SmartInfoService.name, () => {
});
it('should skip invisible assets', async () => {
mocks.asset.getByIds.mockResolvedValue([assetStub.livePhotoMotionAsset]);
mocks.assetJob.getForClipEncoding.mockResolvedValue({
...assetStub.livePhotoMotionAsset,
files: [assetStub.image.files[1]],
});
expect(await sut.handleEncodeClip({ id: assetStub.livePhotoMotionAsset.id })).toEqual(JobStatus.SKIPPED);
@ -295,7 +299,7 @@ describe(SmartInfoService.name, () => {
});
it('should fail if asset could not be found', async () => {
mocks.asset.getByIds.mockResolvedValue([]);
mocks.assetJob.getForClipEncoding.mockResolvedValue(void 0);
expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.FAILED);
@ -306,6 +310,7 @@ describe(SmartInfoService.name, () => {
it('should wait for database', async () => {
mocks.machineLearning.encodeImage.mockResolvedValue('[0.01, 0.02, 0.03]');
mocks.database.isBusy.mockReturnValue(true);
mocks.assetJob.getForClipEncoding.mockResolvedValue({ ...assetStub.image, files: [assetStub.image.files[1]] });
expect(await sut.handleEncodeClip({ id: assetStub.image.id })).toEqual(JobStatus.SUCCESS);

View File

@ -2,12 +2,11 @@ import { Injectable } from '@nestjs/common';
import { SystemConfig } from 'src/config';
import { JOBS_ASSET_PAGINATION_SIZE } from 'src/constants';
import { OnEvent, OnJob } from 'src/decorators';
import { AssetFileType, DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum';
import { DatabaseLock, ImmichWorker, JobName, JobStatus, QueueName } from 'src/enum';
import { WithoutProperty } from 'src/repositories/asset.repository';
import { ArgOf } from 'src/repositories/event.repository';
import { BaseService } from 'src/services/base.service';
import { JobOf } from 'src/types';
import { getAssetFile } from 'src/utils/asset.util';
import { getCLIPModelInfo, isSmartSearchEnabled } from 'src/utils/misc';
import { usePagination } from 'src/utils/pagination';
@ -107,8 +106,8 @@ export class SmartInfoService extends BaseService {
return JobStatus.SKIPPED;
}
const [asset] = await this.assetRepository.getByIds([id], { files: true });
if (!asset) {
const asset = await this.assetJobRepository.getForClipEncoding(id);
if (!asset || asset.files.length !== 1) {
return JobStatus.FAILED;
}
@ -116,14 +115,9 @@ export class SmartInfoService extends BaseService {
return JobStatus.SKIPPED;
}
const previewFile = getAssetFile(asset.files, AssetFileType.PREVIEW);
if (!previewFile) {
return JobStatus.FAILED;
}
const embedding = await this.machineLearningRepository.encodeImage(
machineLearning.urls,
previewFile.path,
asset.files[0].path,
machineLearning.clip,
);