mirror of
https://github.com/immich-app/immich.git
synced 2025-12-20 01:11:46 +03:00
pr feedback
This commit is contained in:
@@ -2,9 +2,11 @@ import {
|
||||
PluginTriggerType,
|
||||
updateWorkflow as updateWorkflowApi,
|
||||
type PluginActionResponseDto,
|
||||
type PluginContext,
|
||||
type PluginContextType,
|
||||
type PluginFilterResponseDto,
|
||||
type PluginTriggerResponseDto,
|
||||
type WorkflowActionItemDto,
|
||||
type WorkflowFilterItemDto,
|
||||
type WorkflowResponseDto,
|
||||
type WorkflowUpdateDto,
|
||||
} from '@immich/sdk';
|
||||
@@ -18,316 +20,280 @@ export interface WorkflowPayload {
|
||||
actions: Record<string, unknown>[];
|
||||
}
|
||||
|
||||
export class WorkflowService {
|
||||
private availableTriggers: PluginTriggerResponseDto[];
|
||||
private availableFilters: PluginFilterResponseDto[];
|
||||
private availableActions: PluginActionResponseDto[];
|
||||
/**
|
||||
* Get filters that support the given context
|
||||
*/
|
||||
export const getFiltersByContext = (
|
||||
availableFilters: PluginFilterResponseDto[],
|
||||
context: PluginContextType,
|
||||
): PluginFilterResponseDto[] => {
|
||||
return availableFilters.filter((filter) => filter.supportedContexts.includes(context));
|
||||
};
|
||||
|
||||
constructor(
|
||||
triggers: PluginTriggerResponseDto[],
|
||||
filters: PluginFilterResponseDto[],
|
||||
actions: PluginActionResponseDto[],
|
||||
) {
|
||||
this.availableTriggers = triggers;
|
||||
this.availableFilters = filters;
|
||||
this.availableActions = actions;
|
||||
/**
|
||||
* Get actions that support the given context
|
||||
*/
|
||||
export const getActionsByContext = (
|
||||
availableActions: PluginActionResponseDto[],
|
||||
context: PluginContextType,
|
||||
): PluginActionResponseDto[] => {
|
||||
return availableActions.filter((action) => action.supportedContexts.includes(context));
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize filter configurations from existing workflow
|
||||
*/
|
||||
export const initializeFilterConfigs = (
|
||||
workflow: WorkflowResponseDto,
|
||||
availableFilters: PluginFilterResponseDto[],
|
||||
): Record<string, unknown> => {
|
||||
const configs: Record<string, unknown> = {};
|
||||
|
||||
if (workflow.filters) {
|
||||
for (const workflowFilter of workflow.filters) {
|
||||
const filterDef = availableFilters.find((f) => f.id === workflowFilter.pluginFilterId);
|
||||
if (filterDef) {
|
||||
configs[filterDef.methodName] = workflowFilter.filterConfig ?? {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get filters that support the given context
|
||||
*/
|
||||
getFiltersByContext(context: PluginContext): PluginFilterResponseDto[] {
|
||||
return this.availableFilters.filter((filter) => filter.supportedContexts.includes(context));
|
||||
return configs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize action configurations from existing workflow
|
||||
*/
|
||||
export const initializeActionConfigs = (
|
||||
workflow: WorkflowResponseDto,
|
||||
availableActions: PluginActionResponseDto[],
|
||||
): Record<string, unknown> => {
|
||||
const configs: Record<string, unknown> = {};
|
||||
|
||||
if (workflow.actions) {
|
||||
for (const workflowAction of workflow.actions) {
|
||||
const actionDef = availableActions.find((a) => a.id === workflowAction.pluginActionId);
|
||||
if (actionDef) {
|
||||
configs[actionDef.methodName] = workflowAction.actionConfig ?? {};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get actions that support the given context
|
||||
*/
|
||||
getActionsByContext(context: PluginContext): PluginActionResponseDto[] {
|
||||
return this.availableActions.filter((action) => action.supportedContexts.includes(context));
|
||||
}
|
||||
return configs;
|
||||
};
|
||||
|
||||
/**
|
||||
* Initialize filter configurations from existing workflow
|
||||
*/
|
||||
initializeFilterConfigs(
|
||||
workflow: WorkflowResponseDto,
|
||||
contextFilters?: PluginFilterResponseDto[],
|
||||
): Record<string, unknown> {
|
||||
const filters = contextFilters ?? this.availableFilters;
|
||||
const configs: Record<string, unknown> = {};
|
||||
/**
|
||||
* Build workflow payload from current state
|
||||
*/
|
||||
export const buildWorkflowPayload = (
|
||||
name: string,
|
||||
description: string,
|
||||
enabled: boolean,
|
||||
triggerType: string,
|
||||
orderedFilters: PluginFilterResponseDto[],
|
||||
orderedActions: PluginActionResponseDto[],
|
||||
filterConfigs: Record<string, unknown>,
|
||||
actionConfigs: Record<string, unknown>,
|
||||
): WorkflowPayload => {
|
||||
const filters = orderedFilters.map((filter) => ({
|
||||
[filter.methodName]: filterConfigs[filter.methodName] ?? {},
|
||||
}));
|
||||
|
||||
if (workflow.filters) {
|
||||
for (const workflowFilter of workflow.filters) {
|
||||
const filterDef = filters.find((f) => f.id === workflowFilter.filterId);
|
||||
if (filterDef) {
|
||||
configs[filterDef.methodName] = workflowFilter.filterConfig ?? {};
|
||||
const actions = orderedActions.map((action) => ({
|
||||
[action.methodName]: actionConfigs[action.methodName] ?? {},
|
||||
}));
|
||||
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
enabled,
|
||||
triggerType,
|
||||
filters,
|
||||
actions,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse JSON workflow and update state
|
||||
*/
|
||||
export const parseWorkflowJson = (
|
||||
jsonString: string,
|
||||
availableTriggers: PluginTriggerResponseDto[],
|
||||
availableFilters: PluginFilterResponseDto[],
|
||||
availableActions: PluginActionResponseDto[],
|
||||
): {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
data?: {
|
||||
name: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
trigger?: PluginTriggerResponseDto;
|
||||
filters: PluginFilterResponseDto[];
|
||||
actions: PluginActionResponseDto[];
|
||||
filterConfigs: Record<string, unknown>;
|
||||
actionConfigs: Record<string, unknown>;
|
||||
};
|
||||
} => {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonString);
|
||||
|
||||
// Find trigger
|
||||
const trigger = availableTriggers.find((t) => t.type === parsed.triggerType);
|
||||
|
||||
// Parse filters
|
||||
const filters: PluginFilterResponseDto[] = [];
|
||||
const filterConfigs: Record<string, unknown> = {};
|
||||
if (Array.isArray(parsed.filters)) {
|
||||
for (const filterObj of parsed.filters) {
|
||||
const methodName = Object.keys(filterObj)[0];
|
||||
const filter = availableFilters.find((f) => f.methodName === methodName);
|
||||
if (filter) {
|
||||
filters.push(filter);
|
||||
filterConfigs[methodName] = (filterObj as Record<string, unknown>)[methodName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize action configurations from existing workflow
|
||||
*/
|
||||
initializeActionConfigs(
|
||||
workflow: WorkflowResponseDto,
|
||||
contextActions?: PluginActionResponseDto[],
|
||||
): Record<string, unknown> {
|
||||
const actions = contextActions ?? this.availableActions;
|
||||
const configs: Record<string, unknown> = {};
|
||||
|
||||
if (workflow.actions) {
|
||||
for (const workflowAction of workflow.actions) {
|
||||
const actionDef = actions.find((a) => a.id === workflowAction.actionId);
|
||||
if (actionDef) {
|
||||
configs[actionDef.methodName] = workflowAction.actionConfig ?? {};
|
||||
// Parse actions
|
||||
const actions: PluginActionResponseDto[] = [];
|
||||
const actionConfigs: Record<string, unknown> = {};
|
||||
if (Array.isArray(parsed.actions)) {
|
||||
for (const actionObj of parsed.actions) {
|
||||
const methodName = Object.keys(actionObj)[0];
|
||||
const action = availableActions.find((a) => a.methodName === methodName);
|
||||
if (action) {
|
||||
actions.push(action);
|
||||
actionConfigs[methodName] = (actionObj as Record<string, unknown>)[methodName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize ordered filters from existing workflow
|
||||
*/
|
||||
initializeOrderedFilters(
|
||||
workflow: WorkflowResponseDto,
|
||||
contextFilters?: PluginFilterResponseDto[],
|
||||
): PluginFilterResponseDto[] {
|
||||
if (!workflow.filters) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const filters = contextFilters ?? this.availableFilters;
|
||||
return workflow.filters
|
||||
.map((wf) => filters.find((f) => f.id === wf.filterId))
|
||||
.filter(Boolean) as PluginFilterResponseDto[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize ordered actions from existing workflow
|
||||
*/
|
||||
initializeOrderedActions(
|
||||
workflow: WorkflowResponseDto,
|
||||
contextActions?: PluginActionResponseDto[],
|
||||
): PluginActionResponseDto[] {
|
||||
if (!workflow.actions) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const actions = contextActions ?? this.availableActions;
|
||||
return workflow.actions
|
||||
.map((wa) => actions.find((a) => a.id === wa.actionId))
|
||||
.filter(Boolean) as PluginActionResponseDto[];
|
||||
}
|
||||
|
||||
/**
|
||||
* Build workflow payload from current state
|
||||
*/
|
||||
buildWorkflowPayload(
|
||||
name: string,
|
||||
description: string,
|
||||
enabled: boolean,
|
||||
triggerType: string,
|
||||
orderedFilters: PluginFilterResponseDto[],
|
||||
orderedActions: PluginActionResponseDto[],
|
||||
filterConfigs: Record<string, unknown>,
|
||||
actionConfigs: Record<string, unknown>,
|
||||
): WorkflowPayload {
|
||||
const filters = orderedFilters.map((filter) => ({
|
||||
[filter.methodName]: filterConfigs[filter.methodName] ?? {},
|
||||
}));
|
||||
|
||||
const actions = orderedActions.map((action) => ({
|
||||
[action.methodName]: actionConfigs[action.methodName] ?? {},
|
||||
}));
|
||||
|
||||
return {
|
||||
name,
|
||||
description,
|
||||
enabled,
|
||||
triggerType,
|
||||
filters,
|
||||
actions,
|
||||
success: true,
|
||||
data: {
|
||||
name: parsed.name ?? '',
|
||||
description: parsed.description ?? '',
|
||||
enabled: parsed.enabled ?? false,
|
||||
trigger,
|
||||
filters,
|
||||
actions,
|
||||
filterConfigs,
|
||||
actionConfigs,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Invalid JSON',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse JSON workflow and update state
|
||||
*/
|
||||
parseWorkflowJson(jsonString: string): {
|
||||
success: boolean;
|
||||
error?: string;
|
||||
data?: {
|
||||
name: string;
|
||||
description: string;
|
||||
enabled: boolean;
|
||||
trigger?: PluginTriggerResponseDto;
|
||||
filters: PluginFilterResponseDto[];
|
||||
actions: PluginActionResponseDto[];
|
||||
filterConfigs: Record<string, unknown>;
|
||||
actionConfigs: Record<string, unknown>;
|
||||
};
|
||||
} {
|
||||
try {
|
||||
const parsed = JSON.parse(jsonString);
|
||||
|
||||
// Find trigger
|
||||
const trigger = this.availableTriggers.find((t) => t.triggerType === parsed.triggerType);
|
||||
|
||||
// Parse filters
|
||||
const filters: PluginFilterResponseDto[] = [];
|
||||
const filterConfigs: Record<string, unknown> = {};
|
||||
if (Array.isArray(parsed.filters)) {
|
||||
for (const filterObj of parsed.filters) {
|
||||
const methodName = Object.keys(filterObj)[0];
|
||||
const filter = this.availableFilters.find((f) => f.methodName === methodName);
|
||||
if (filter) {
|
||||
filters.push(filter);
|
||||
filterConfigs[methodName] = (filterObj as Record<string, unknown>)[methodName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Parse actions
|
||||
const actions: PluginActionResponseDto[] = [];
|
||||
const actionConfigs: Record<string, unknown> = {};
|
||||
if (Array.isArray(parsed.actions)) {
|
||||
for (const actionObj of parsed.actions) {
|
||||
const methodName = Object.keys(actionObj)[0];
|
||||
const action = this.availableActions.find((a) => a.methodName === methodName);
|
||||
if (action) {
|
||||
actions.push(action);
|
||||
actionConfigs[methodName] = (actionObj as Record<string, unknown>)[methodName];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: {
|
||||
name: parsed.name ?? '',
|
||||
description: parsed.description ?? '',
|
||||
enabled: parsed.enabled ?? false,
|
||||
trigger,
|
||||
filters,
|
||||
actions,
|
||||
filterConfigs,
|
||||
actionConfigs,
|
||||
},
|
||||
};
|
||||
} catch (error) {
|
||||
return {
|
||||
success: false,
|
||||
error: error instanceof Error ? error.message : 'Invalid JSON',
|
||||
};
|
||||
}
|
||||
/**
|
||||
* Check if workflow has changes compared to previous version
|
||||
*/
|
||||
export const hasWorkflowChanged = (
|
||||
previousWorkflow: WorkflowResponseDto,
|
||||
enabled: boolean,
|
||||
name: string,
|
||||
description: string,
|
||||
triggerType: string,
|
||||
orderedFilters: PluginFilterResponseDto[],
|
||||
orderedActions: PluginActionResponseDto[],
|
||||
filterConfigs: Record<string, unknown>,
|
||||
actionConfigs: Record<string, unknown>,
|
||||
availableFilters: PluginFilterResponseDto[],
|
||||
availableActions: PluginActionResponseDto[],
|
||||
): boolean => {
|
||||
// Check enabled state
|
||||
if (enabled !== previousWorkflow.enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if workflow has changes compared to previous version
|
||||
*/
|
||||
hasWorkflowChanged(
|
||||
previousWorkflow: WorkflowResponseDto,
|
||||
enabled: boolean,
|
||||
name: string,
|
||||
description: string,
|
||||
triggerType: string,
|
||||
orderedFilters: PluginFilterResponseDto[],
|
||||
orderedActions: PluginActionResponseDto[],
|
||||
filterConfigs: Record<string, unknown>,
|
||||
actionConfigs: Record<string, unknown>,
|
||||
): boolean {
|
||||
// Check enabled state
|
||||
if (enabled !== previousWorkflow.enabled) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check name or description
|
||||
if (name !== (previousWorkflow.name ?? '') || description !== (previousWorkflow.description ?? '')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check trigger
|
||||
if (triggerType !== previousWorkflow.triggerType) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check filters order/items
|
||||
const previousFilterIds = previousWorkflow.filters?.map((f) => f.filterId) ?? [];
|
||||
const currentFilterIds = orderedFilters.map((f) => f.id);
|
||||
if (JSON.stringify(previousFilterIds) !== JSON.stringify(currentFilterIds)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check actions order/items
|
||||
const previousActionIds = previousWorkflow.actions?.map((a) => a.actionId) ?? [];
|
||||
const currentActionIds = orderedActions.map((a) => a.id);
|
||||
if (JSON.stringify(previousActionIds) !== JSON.stringify(currentActionIds)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check filter configs
|
||||
const previousFilterConfigs: Record<string, unknown> = {};
|
||||
for (const wf of previousWorkflow.filters ?? []) {
|
||||
const filterDef = this.availableFilters.find((f) => f.id === wf.filterId);
|
||||
if (filterDef) {
|
||||
previousFilterConfigs[filterDef.methodName] = wf.filterConfig ?? {};
|
||||
}
|
||||
}
|
||||
if (JSON.stringify(previousFilterConfigs) !== JSON.stringify(filterConfigs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check action configs
|
||||
const previousActionConfigs: Record<string, unknown> = {};
|
||||
for (const wa of previousWorkflow.actions ?? []) {
|
||||
const actionDef = this.availableActions.find((a) => a.id === wa.actionId);
|
||||
if (actionDef) {
|
||||
previousActionConfigs[actionDef.methodName] = wa.actionConfig ?? {};
|
||||
}
|
||||
}
|
||||
if (JSON.stringify(previousActionConfigs) !== JSON.stringify(actionConfigs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
// Check name or description
|
||||
if (name !== (previousWorkflow.name ?? '') || description !== (previousWorkflow.description ?? '')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
async updateWorkflow(
|
||||
workflowId: string,
|
||||
name: string,
|
||||
description: string,
|
||||
enabled: boolean,
|
||||
triggerType: PluginTriggerType,
|
||||
orderedFilters: PluginFilterResponseDto[],
|
||||
orderedActions: PluginActionResponseDto[],
|
||||
filterConfigs: Record<string, unknown>,
|
||||
actionConfigs: Record<string, unknown>,
|
||||
): Promise<WorkflowResponseDto> {
|
||||
const filters = orderedFilters.map((filter) => ({
|
||||
filterId: filter.id,
|
||||
filterConfig: filterConfigs[filter.methodName] ?? {},
|
||||
}));
|
||||
|
||||
const actions = orderedActions.map((action) => ({
|
||||
actionId: action.id,
|
||||
actionConfig: actionConfigs[action.methodName] ?? {},
|
||||
}));
|
||||
|
||||
const updateDto: WorkflowUpdateDto = {
|
||||
name,
|
||||
description,
|
||||
enabled,
|
||||
filters,
|
||||
actions,
|
||||
triggerType,
|
||||
};
|
||||
|
||||
return updateWorkflowApi({ id: workflowId, workflowUpdateDto: updateDto });
|
||||
// Check trigger
|
||||
if (triggerType !== previousWorkflow.triggerType) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Check filters order/items
|
||||
const previousFilterIds = previousWorkflow.filters?.map((f) => f.pluginFilterId) ?? [];
|
||||
const currentFilterIds = orderedFilters.map((f) => f.id);
|
||||
if (JSON.stringify(previousFilterIds) !== JSON.stringify(currentFilterIds)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check actions order/items
|
||||
const previousActionIds = previousWorkflow.actions?.map((a) => a.pluginActionId) ?? [];
|
||||
const currentActionIds = orderedActions.map((a) => a.id);
|
||||
if (JSON.stringify(previousActionIds) !== JSON.stringify(currentActionIds)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check filter configs
|
||||
const previousFilterConfigs: Record<string, unknown> = {};
|
||||
for (const wf of previousWorkflow.filters ?? []) {
|
||||
const filterDef = availableFilters.find((f) => f.id === wf.pluginFilterId);
|
||||
if (filterDef) {
|
||||
previousFilterConfigs[filterDef.methodName] = wf.filterConfig ?? {};
|
||||
}
|
||||
}
|
||||
if (JSON.stringify(previousFilterConfigs) !== JSON.stringify(filterConfigs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check action configs
|
||||
const previousActionConfigs: Record<string, unknown> = {};
|
||||
for (const wa of previousWorkflow.actions ?? []) {
|
||||
const actionDef = availableActions.find((a) => a.id === wa.pluginActionId);
|
||||
if (actionDef) {
|
||||
previousActionConfigs[actionDef.methodName] = wa.actionConfig ?? {};
|
||||
}
|
||||
}
|
||||
if (JSON.stringify(previousActionConfigs) !== JSON.stringify(actionConfigs)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
/**
|
||||
* Update a workflow via API
|
||||
*/
|
||||
export const handleUpdateWorkflow = async (
|
||||
workflowId: string,
|
||||
name: string,
|
||||
description: string,
|
||||
enabled: boolean,
|
||||
triggerType: PluginTriggerType,
|
||||
orderedFilters: PluginFilterResponseDto[],
|
||||
orderedActions: PluginActionResponseDto[],
|
||||
filterConfigs: Record<string, unknown>,
|
||||
actionConfigs: Record<string, unknown>,
|
||||
): Promise<WorkflowResponseDto> => {
|
||||
const filters = orderedFilters.map((filter) => ({
|
||||
pluginFilterId: filter.id,
|
||||
filterConfig: filterConfigs[filter.methodName] ?? {},
|
||||
})) as WorkflowFilterItemDto[];
|
||||
|
||||
const actions = orderedActions.map((action) => ({
|
||||
pluginActionId: action.id,
|
||||
actionConfig: actionConfigs[action.methodName] ?? {},
|
||||
})) as WorkflowActionItemDto[];
|
||||
|
||||
const updateDto: WorkflowUpdateDto = {
|
||||
name,
|
||||
description,
|
||||
enabled,
|
||||
filters,
|
||||
actions,
|
||||
triggerType,
|
||||
};
|
||||
|
||||
return updateWorkflowApi({ id: workflowId, workflowUpdateDto: updateDto });
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user