import { BadRequestException, Injectable } from '@nestjs/common'; import { Workflow } from 'src/database'; import { AuthDto } from 'src/dtos/auth.dto'; import { mapWorkflowAction, mapWorkflowFilter, WorkflowCreateDto, WorkflowResponseDto, WorkflowUpdateDto, } from 'src/dtos/workflow.dto'; import { Permission, PluginContext, PluginTriggerType } from 'src/enum'; import { pluginTriggers } from 'src/plugins'; import { BaseService } from 'src/services/base.service'; @Injectable() export class WorkflowService extends BaseService { async create(auth: AuthDto, dto: WorkflowCreateDto): Promise { const trigger = this.getTriggerOrFail(dto.triggerType); const filterInserts = await this.validateAndMapFilters(dto.filters, trigger.context); const actionInserts = await this.validateAndMapActions(dto.actions, trigger.context); const workflow = await this.workflowRepository.createWorkflow( { ownerId: auth.user.id, triggerType: dto.triggerType, name: dto.name, description: dto.description || '', enabled: dto.enabled ?? true, }, filterInserts, actionInserts, ); return this.mapWorkflow(workflow); } async getAll(auth: AuthDto): Promise { const workflows = await this.workflowRepository.getWorkflowsByOwner(auth.user.id); return Promise.all(workflows.map((workflow) => this.mapWorkflow(workflow))); } async get(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowRead, ids: [id] }); const workflow = await this.findOrFail(id); return this.mapWorkflow(workflow); } async update(auth: AuthDto, id: string, dto: WorkflowUpdateDto): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowUpdate, ids: [id] }); if (Object.values(dto).filter((prop) => prop !== undefined).length === 0) { throw new BadRequestException('No fields to update'); } const workflow = await this.findOrFail(id); const trigger = this.getTriggerOrFail(workflow.triggerType); const { filters, actions, ...workflowUpdate } = dto; const filterInserts = filters && (await this.validateAndMapFilters(filters, trigger.context)); const actionInserts = actions && (await this.validateAndMapActions(actions, trigger.context)); const updatedWorkflow = await this.workflowRepository.updateWorkflow( id, workflowUpdate, filterInserts, actionInserts, ); return this.mapWorkflow(updatedWorkflow); } async delete(auth: AuthDto, id: string): Promise { await this.requireAccess({ auth, permission: Permission.WorkflowDelete, ids: [id] }); await this.workflowRepository.deleteWorkflow(id); } private async validateAndMapFilters( filters: Array<{ filterId: string; filterConfig?: any }>, requiredContext: PluginContext, ) { for (const dto of filters) { const filter = await this.pluginRepository.getFilter(dto.filterId); if (!filter) { throw new BadRequestException(`Invalid filter ID: ${dto.filterId}`); } if (!filter.supportedContexts.includes(requiredContext)) { throw new BadRequestException( `Filter "${filter.title}" does not support ${requiredContext} context. Supported contexts: ${filter.supportedContexts.join(', ')}`, ); } } return filters.map((dto, index) => ({ filterId: dto.filterId, filterConfig: dto.filterConfig || null, order: index, })); } private async validateAndMapActions( actions: Array<{ actionId: string; actionConfig?: any }>, requiredContext: PluginContext, ) { for (const dto of actions) { const action = await this.pluginRepository.getAction(dto.actionId); if (!action) { throw new BadRequestException(`Invalid action ID: ${dto.actionId}`); } if (!action.supportedContexts.includes(requiredContext)) { throw new BadRequestException( `Action "${action.title}" does not support ${requiredContext} context. Supported contexts: ${action.supportedContexts.join(', ')}`, ); } } return actions.map((dto, index) => ({ actionId: dto.actionId, actionConfig: dto.actionConfig || null, order: index, })); } private getTriggerOrFail(triggerType: PluginTriggerType) { const trigger = pluginTriggers.find((t) => t.type === triggerType); if (!trigger) { throw new BadRequestException(`Invalid trigger type: ${triggerType}`); } return trigger; } private async findOrFail(id: string) { const workflow = await this.workflowRepository.getWorkflow(id); if (!workflow) { throw new BadRequestException('Workflow not found'); } return workflow; } private async mapWorkflow(workflow: Workflow): Promise { const filters = await this.workflowRepository.getFilters(workflow.id); const actions = await this.workflowRepository.getActions(workflow.id); return { id: workflow.id, ownerId: workflow.ownerId, triggerType: workflow.triggerType, name: workflow.name, description: workflow.description, createdAt: workflow.createdAt.toISOString(), enabled: workflow.enabled, filters: filters.map((f) => mapWorkflowFilter(f)), actions: actions.map((a) => mapWorkflowAction(a)), }; } }