From 72eaba6ee2c9bfcb9fbb5f0aaf1ced97e7e4daa7 Mon Sep 17 00:00:00 2001 From: Alex Tran Date: Thu, 4 Jun 2026 01:51:36 +0000 Subject: [PATCH] refactor(workflow): map AlbumAssetAdded trigger to AssetV1 Drop the AssetAlbumV1 workflow type and have the AlbumAssetAdded trigger reuse the existing asset-aware AssetV1 type, so all current AssetV1 plugin methods work with it out of the box. --- mobile/openapi/lib/model/workflow_type.dart | 3 - open-api/immich-openapi-specs.json | 3 +- packages/plugin-sdk/src/types.ts | 10 --- packages/sdk/src/fetch-client.ts | 3 +- server/src/enum.ts | 1 - .../src/repositories/workflow.repository.ts | 12 --- .../services/workflow-execution.service.ts | 89 +++++++------------ server/src/types.ts | 2 +- server/src/utils/workflow.spec.ts | 9 +- server/src/utils/workflow.ts | 3 +- 10 files changed, 39 insertions(+), 96 deletions(-) diff --git a/mobile/openapi/lib/model/workflow_type.dart b/mobile/openapi/lib/model/workflow_type.dart index 170c0715bc..c18b07e9fb 100644 --- a/mobile/openapi/lib/model/workflow_type.dart +++ b/mobile/openapi/lib/model/workflow_type.dart @@ -25,13 +25,11 @@ class WorkflowType { static const assetV1 = WorkflowType._(r'AssetV1'); static const assetPersonV1 = WorkflowType._(r'AssetPersonV1'); - static const assetAlbumV1 = WorkflowType._(r'AssetAlbumV1'); /// List of all possible values in this [enum][WorkflowType]. static const values = [ assetV1, assetPersonV1, - assetAlbumV1, ]; static WorkflowType? fromJson(dynamic value) => WorkflowTypeTypeTransformer().decode(value); @@ -72,7 +70,6 @@ class WorkflowTypeTypeTransformer { switch (data) { case r'AssetV1': return WorkflowType.assetV1; case r'AssetPersonV1': return WorkflowType.assetPersonV1; - case r'AssetAlbumV1': return WorkflowType.assetAlbumV1; default: if (!allowNull) { throw ArgumentError('Unknown enum value to decode: $data'); diff --git a/open-api/immich-openapi-specs.json b/open-api/immich-openapi-specs.json index 7f4fff9cf2..e25196bb77 100644 --- a/open-api/immich-openapi-specs.json +++ b/open-api/immich-openapi-specs.json @@ -26840,8 +26840,7 @@ "description": "Workflow type", "enum": [ "AssetV1", - "AssetPersonV1", - "AssetAlbumV1" + "AssetPersonV1" ], "type": "string" }, diff --git a/packages/plugin-sdk/src/types.ts b/packages/plugin-sdk/src/types.ts index 21962fb3ab..438ba3d74d 100644 --- a/packages/plugin-sdk/src/types.ts +++ b/packages/plugin-sdk/src/types.ts @@ -11,7 +11,6 @@ type DeepPartial = T extends Date export type WorkflowEventMap = { [WorkflowType.AssetV1]: AssetV1; [WorkflowType.AssetPersonV1]: AssetPersonV1; - [WorkflowType.AssetAlbumV1]: AssetAlbumV1; }; export type WorkflowEventData = WorkflowEventMap[T]; @@ -130,12 +129,3 @@ export type AssetPersonV1 = AssetV1 & { name: string; }; }; - -export type AssetAlbumV1 = AssetV1 & { - album: { - id: string; - ownerId: string; - albumName: string; - description: string; - }; -}; diff --git a/packages/sdk/src/fetch-client.ts b/packages/sdk/src/fetch-client.ts index 5923539e6f..56dcd6d9b2 100644 --- a/packages/sdk/src/fetch-client.ts +++ b/packages/sdk/src/fetch-client.ts @@ -7179,8 +7179,7 @@ export enum PartnerDirection { } export enum WorkflowType { AssetV1 = "AssetV1", - AssetPersonV1 = "AssetPersonV1", - AssetAlbumV1 = "AssetAlbumV1" + AssetPersonV1 = "AssetPersonV1" } export enum WorkflowTrigger { AssetCreate = "AssetCreate", diff --git a/server/src/enum.ts b/server/src/enum.ts index 79d0c9bfcf..9dee1db313 100644 --- a/server/src/enum.ts +++ b/server/src/enum.ts @@ -1172,7 +1172,6 @@ export const WorkflowTriggerSchema = z export enum WorkflowType { AssetV1 = 'AssetV1', AssetPersonV1 = 'AssetPersonV1', - AssetAlbumV1 = 'AssetAlbumV1', } export const WorkflowTypeSchema = z.enum(WorkflowType).describe('Workflow type').meta({ id: 'WorkflowType' }); diff --git a/server/src/repositories/workflow.repository.ts b/server/src/repositories/workflow.repository.ts index f1669c3e09..9ceef72a50 100644 --- a/server/src/repositories/workflow.repository.ts +++ b/server/src/repositories/workflow.repository.ts @@ -5,7 +5,6 @@ import { InjectKysely } from 'nestjs-kysely'; import { columns } from 'src/database'; import { DummyValue, GenerateSql } from 'src/decorators'; import { WorkflowSearchDto } from 'src/dtos/workflow.dto'; -import { AlbumUserRole } from 'src/enum'; import { DB } from 'src/schema'; import { WorkflowStepTable } from 'src/schema/tables/workflow-step.table'; import { WorkflowTable } from 'src/schema/tables/workflow.table'; @@ -183,15 +182,4 @@ export class WorkflowRepository { .where('id', '=', assetId) .executeTakeFirstOrThrow(); } - - getAlbumForWorkflow(albumId: string) { - return this.db - .selectFrom('album') - .innerJoin('album_user', 'album_user.albumId', 'album.id') - .where('album_user.role', '=', AlbumUserRole.Owner) - .select(['album.id', 'album_user.userId as ownerId', 'album.albumName', 'album.description']) - .where('album.id', '=', albumId) - .where('album.deletedAt', 'is', null) - .executeTakeFirstOrThrow(); - } } diff --git a/server/src/services/workflow-execution.service.ts b/server/src/services/workflow-execution.service.ts index 231ea7039c..ac338ff177 100644 --- a/server/src/services/workflow-execution.service.ts +++ b/server/src/services/workflow-execution.service.ts @@ -40,7 +40,7 @@ type ExecuteOptions = { write: (auth: AuthDto, changes: WorkflowChanges) => Promise; }; -type AssetTrigger = { userId: string; assetId: string; albumId?: string; trigger: WorkflowTrigger }; +type AssetTrigger = { userId: string; assetId: string; trigger: WorkflowTrigger }; export class WorkflowExecutionService extends BaseService { private jwtSecret!: string; @@ -275,56 +275,25 @@ export class WorkflowExecutionService extends BaseService { } @OnEvent({ name: 'AlbumAssetAdd' }) - onAlbumAssetAdd({ userId, assetId, albumId }: ArgOf<'AlbumAssetAdd'>) { - return this.onAssetTrigger({ userId, assetId, albumId, trigger: WorkflowTrigger.AlbumAssetAdded }); + onAlbumAssetAdd({ userId, assetId }: ArgOf<'AlbumAssetAdd'>) { + return this.onAssetTrigger({ userId, assetId, trigger: WorkflowTrigger.AlbumAssetAdded }); } - private async onAssetTrigger({ userId, assetId, albumId, trigger }: AssetTrigger) { + private async onAssetTrigger({ userId, assetId, trigger }: AssetTrigger) { const items = await this.workflowRepository.search({ userId, trigger }); await this.jobRepository.queueAll( items.map((workflow) => ({ name: JobName.WorkflowAssetTrigger, - data: { workflowId: workflow.id, assetId, albumId, trigger }, + data: { workflowId: workflow.id, assetId, trigger }, })), ); } @OnJob({ name: JobName.WorkflowAssetTrigger, queue: QueueName.Workflow }) - handleAssetTrigger({ workflowId, assetId, albumId }: JobOf) { + handleAssetTrigger({ workflowId, assetId }: JobOf) { return this.execute(workflowId, (type) => { const assetService = BaseService.create(AssetService, this); - const writeAsset: ExecuteOptions['write'] = async (auth, changes) => { - const asset = changes.asset; - if (!asset) { - return; - } - - await assetService.update(auth, assetId, { - isFavorite: asset.isFavorite, - visibility: asset.visibility, - dateTimeOriginal: asset.exifInfo?.dateTimeOriginal ?? undefined, - // TODO allow setting to null - longitude: asset.exifInfo?.longitude ?? undefined, - // TODO allow setting to null - latitude: asset.exifInfo?.latitude ?? undefined, - // TODO allow setting to null - description: asset.exifInfo?.description ?? undefined, - rating: asset.exifInfo?.rating, - - // TODO add to update dto - // make: asset.exifInfo?.make, - // model: asset.exifInfo?.model, - // city: asset.exifInfo?.city, - // state: asset.exifInfo?.state, - // country: asset.exifInfo?.country, - // lensModel: asset.exifInfo?.lensModel, - // fNumber: asset.exifInfo?.fNumber, - // fps: asset.exifInfo?.fps, - // iso: asset.exifInfo?.iso, - }); - }; - switch (type) { case WorkflowType.AssetV1: { return { @@ -335,28 +304,36 @@ export class WorkflowExecutionService extends BaseService { authUserId: asset.ownerId, }; }, - write: writeAsset, - } satisfies ExecuteOptions; - } + write: async (auth, changes) => { + const asset = changes.asset; + if (!asset) { + return; + } - case WorkflowType.AssetAlbumV1: { - if (!albumId) { - this.logger.error(`Misconfigured workflow ${workflowId}: missing albumId for type ${type}`); - return; - } + await assetService.update(auth, assetId, { + isFavorite: asset.isFavorite, + visibility: asset.visibility, + dateTimeOriginal: asset.exifInfo?.dateTimeOriginal ?? undefined, + // TODO allow setting to null + longitude: asset.exifInfo?.longitude ?? undefined, + // TODO allow setting to null + latitude: asset.exifInfo?.latitude ?? undefined, + // TODO allow setting to null + description: asset.exifInfo?.description ?? undefined, + rating: asset.exifInfo?.rating, - return { - read: async () => { - const [asset, album] = await Promise.all([ - this.workflowRepository.getForAssetV1(assetId), - this.workflowRepository.getAlbumForWorkflow(albumId), - ]); - return { - data: { asset, album } as any, - authUserId: asset.ownerId, - }; + // TODO add to update dto + // make: asset.exifInfo?.make, + // model: asset.exifInfo?.model, + // city: asset.exifInfo?.city, + // state: asset.exifInfo?.state, + // country: asset.exifInfo?.country, + // lensModel: asset.exifInfo?.lensModel, + // fNumber: asset.exifInfo?.fNumber, + // fps: asset.exifInfo?.fps, + // iso: asset.exifInfo?.iso, + }); }, - write: writeAsset, } satisfies ExecuteOptions; } } diff --git a/server/src/types.ts b/server/src/types.ts index 96110e791a..4e5a383cca 100644 --- a/server/src/types.ts +++ b/server/src/types.ts @@ -420,7 +420,7 @@ export type JobItem = | { name: JobName.Ocr; data: IEntityJob } // Workflow - | { name: JobName.WorkflowAssetTrigger; data: { workflowId: string; assetId: string; albumId?: string } } + | { name: JobName.WorkflowAssetTrigger; data: { workflowId: string; assetId: string } } // Editor | { name: JobName.AssetEditThumbnailGeneration; data: IEntityJob }; diff --git a/server/src/utils/workflow.spec.ts b/server/src/utils/workflow.spec.ts index ff5a645ffe..cd55914126 100644 --- a/server/src/utils/workflow.spec.ts +++ b/server/src/utils/workflow.spec.ts @@ -30,17 +30,12 @@ const tests: Array<{ trigger: WorkflowTrigger; types: WorkflowType[]; expected: }, { trigger: WorkflowTrigger.AlbumAssetAdded, - types: [WorkflowType.AssetAlbumV1], + types: [WorkflowType.AssetV1], expected: true, }, { trigger: WorkflowTrigger.AlbumAssetAdded, - types: [WorkflowType.AssetV1], - expected: false, - }, - { - trigger: WorkflowTrigger.AssetCreate, - types: [WorkflowType.AssetAlbumV1], + types: [WorkflowType.AssetPersonV1], expected: true, }, ]; diff --git a/server/src/utils/workflow.ts b/server/src/utils/workflow.ts index 1f83f6b789..3639507353 100644 --- a/server/src/utils/workflow.ts +++ b/server/src/utils/workflow.ts @@ -6,7 +6,7 @@ export const triggerMap: Record = { [WorkflowTrigger.AssetCreate]: [WorkflowType.AssetV1], [WorkflowTrigger.PersonRecognized]: [WorkflowType.AssetPersonV1], [WorkflowTrigger.AssetMetadataExtraction]: [WorkflowType.AssetV1], - [WorkflowTrigger.AlbumAssetAdded]: [WorkflowType.AssetAlbumV1], + [WorkflowTrigger.AlbumAssetAdded]: [WorkflowType.AssetV1], }; export const getWorkflowTriggers = () => @@ -16,7 +16,6 @@ export const getWorkflowTriggers = () => const inferredMap: Record = { [WorkflowType.AssetV1]: [], [WorkflowType.AssetPersonV1]: [WorkflowType.AssetV1], - [WorkflowType.AssetAlbumV1]: [WorkflowType.AssetV1], }; const withImpliedItems = (type: WorkflowType): WorkflowType[] => {