immich/server/src/services/workflow.service.ts
Jason Rasmussen 97bc4cab62
WIP
2026-03-17 13:57:20 -04:00

114 lines
3.9 KiB
TypeScript

import { BadRequestException, Injectable } from '@nestjs/common';
import { AuthDto } from 'src/dtos/auth.dto';
import {
mapWorkflow,
mapWorkflowShare,
WorkflowCreateDto,
WorkflowResponseDto,
WorkflowSearchDto,
WorkflowShareResponseDto,
WorkflowTriggerResponseDto,
WorkflowUpdateDto,
} from 'src/dtos/workflow.dto';
import { Permission, WorkflowTrigger } from 'src/enum';
import { PluginMethodSearchResponse } from 'src/repositories/plugin.repository';
import { BaseService } from 'src/services/base.service';
import { getWorkflowTriggers, isMethodCompatible, resolveMethod } from 'src/utils/workflow';
@Injectable()
export class WorkflowService extends BaseService {
getTriggers(): WorkflowTriggerResponseDto[] {
return getWorkflowTriggers();
}
async search(auth: AuthDto, dto: WorkflowSearchDto): Promise<WorkflowResponseDto[]> {
const workflows = await this.workflowRepository.search({ ...dto, ownerId: auth.user.id });
return workflows.map((workflow) => mapWorkflow(workflow));
}
async get(auth: AuthDto, id: string): Promise<WorkflowResponseDto> {
await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] });
const workflow = await this.findOrFail(id);
return mapWorkflow(workflow);
}
async share(auth: AuthDto, id: string): Promise<WorkflowShareResponseDto> {
await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] });
const workflow = await this.findOrFail(id);
return mapWorkflowShare(workflow);
}
async create(auth: AuthDto, dto: WorkflowCreateDto): Promise<WorkflowResponseDto> {
const { steps: stepsDto, ...workflowDto } = dto;
const steps = await this.resolveAndValidateSteps(stepsDto ?? [], workflowDto.trigger);
const workflow = await this.workflowRepository.create(
{
...workflowDto,
ownerId: auth.user.id,
},
steps.map((step) => ({
enabled: step.enabled ?? true,
config: step.config,
pluginMethodId: step.pluginMethod.id,
})),
);
return mapWorkflow({ ...workflow, steps: [] });
}
async update(auth: AuthDto, id: string, dto: WorkflowUpdateDto): Promise<WorkflowResponseDto> {
await this.requireAccess({ auth, permission: Permission.WorkflowUpdate, ids: [id] });
const { steps: stepsDto, ...workflowDto } = dto;
const current = await this.findOrFail(id);
const steps = stepsDto ? await this.resolveAndValidateSteps(stepsDto, dto.trigger ?? current.trigger) : undefined;
const workflow = await this.workflowRepository.update(
id,
workflowDto,
steps?.map((step) => ({
enabled: step.enabled ?? true,
config: step.config,
pluginMethodId: step.pluginMethod.id,
})),
);
return mapWorkflow(workflow);
}
async delete(auth: AuthDto, id: string): Promise<void> {
await this.requireAccess({ auth, permission: Permission.WorkflowDelete, ids: [id] });
await this.workflowRepository.delete(id);
}
private async resolveAndValidateSteps<T extends { method: string }>(steps: T[], trigger: WorkflowTrigger) {
const methods = await this.pluginRepository.getForValidation();
const results: Array<T & { pluginMethod: PluginMethodSearchResponse }> = [];
for (const step of steps) {
const pluginMethod = resolveMethod(methods, step.method);
if (!pluginMethod) {
throw new BadRequestException(`Unknown method ${step.method}`);
}
if (!isMethodCompatible(pluginMethod, trigger)) {
throw new BadRequestException(`Method "${step.method}" is incompatible with workflow trigger: "${trigger}"`);
}
results.push({ ...step, pluginMethod });
}
// TODO make sure all steps can use a common WorkflowType
return results;
}
private async findOrFail(id: string) {
const workflow = await this.workflowRepository.get(id);
if (!workflow) {
throw new BadRequestException('Workflow not found');
}
return workflow;
}
}