diff --git a/pages/workflow/[id].vue b/pages/workflow/[id].vue index 4ed96ab..1280b81 100644 --- a/pages/workflow/[id].vue +++ b/pages/workflow/[id].vue @@ -655,11 +655,18 @@ function buildApiRequest(nodeData, variables) { // 4. Authorization if (nodeData.authorization && nodeData.authorization.type && nodeData.authorization.type !== 'none') { const auth = nodeData.authorization; + if (auth.type === 'bearer' && auth.token) { headers['Authorization'] = `Bearer ${substituteVariables(auth.token, variables)}`; - } else if (auth.type === 'basic' && auth.username && auth.password) { - const token = btoa(`${substituteVariables(auth.username, variables)}:${substituteVariables(auth.password, variables)}`); - headers['Authorization'] = `Basic ${token}`; + } else if (auth.type === 'basic') { + if (auth.token) { + // Basic Auth with token (JWT or other token) + headers['Authorization'] = `Basic ${substituteVariables(auth.token, variables)}`; + } else if (auth.username && auth.password) { + // Basic Auth with username/password + const token = btoa(`${substituteVariables(auth.username, variables)}:${substituteVariables(auth.password, variables)}`); + headers['Authorization'] = `Basic ${token}`; + } } else if (auth.type === 'apiKey' && auth.key && auth.value) { if (auth.in === 'header') { headers[substituteVariables(auth.key, variables)] = substituteVariables(auth.value, variables); @@ -712,26 +719,45 @@ const executeCurrentStep = async () => { if (currentNode.value?.type === 'api') { console.log(`[Workflow] Executing API node: ${currentNode.value.data?.label || currentNode.value.label}`); const nodeData = currentNode.value.data || {}; - // Use new structure if present - if (nodeData.body || nodeData.headers || nodeData.params || nodeData.authorization) { - const { url, headers, body } = buildApiRequest(nodeData, processVariables.value); - const apiMethod = nodeData.apiMethod || 'GET'; + + // Use new structure if present (check for any new structure properties) + if (nodeData.body !== undefined || nodeData.headers !== undefined || nodeData.params !== undefined || nodeData.authorization !== undefined) { const outputVariable = nodeData.outputVariable || 'apiResponse'; const errorVariable = nodeData.errorVariable || 'apiError'; const continueOnError = nodeData.continueOnError || false; + try { - const response = await $fetch(url, { - method: apiMethod, - headers, - body: ['GET', 'HEAD'].includes(apiMethod) ? undefined : body, + // Use proxy endpoint to avoid CORS issues + const response = await $fetch('/api/process/workflow-api-call', { + method: 'POST', + body: { + nodeData, + processVariables: processVariables.value + } }); - processVariables.value[outputVariable] = response; - processVariables.value[errorVariable] = null; - console.log('[Workflow] API call success. Output variable set:', outputVariable, response); - if (canAutoProgress(currentNode.value)) { - moveToNextStep(); + + if (response.success) { + processVariables.value[outputVariable] = response.data; + processVariables.value[errorVariable] = null; + console.log('[Workflow] API call success. Output variable set:', outputVariable, response.data); + if (canAutoProgress(currentNode.value)) { + moveToNextStep(); + } else { + console.log('[Workflow] API completed, multiple paths available - waiting for user choice'); + } } else { - console.log('[Workflow] API completed, multiple paths available - waiting for user choice'); + processVariables.value[errorVariable] = response.error; + console.error('[Workflow] API call failed:', response.error); + if (continueOnError) { + if (canAutoProgress(currentNode.value)) { + moveToNextStep(); + } else { + console.log('[Workflow] API failed but continuing, multiple paths available - waiting for user choice'); + } + } else { + error.value = 'API call failed: ' + (response.error.message || response.error); + notifyParentOfError(error.value); + } } } catch (err) { processVariables.value[errorVariable] = err; @@ -749,6 +775,7 @@ const executeCurrentStep = async () => { } } else { // Fallback: old structure + const { apiUrl, apiMethod = 'GET', diff --git a/prisma/json/json-schema.json b/prisma/json/json-schema.json index 5125336..de2955f 100644 --- a/prisma/json/json-schema.json +++ b/prisma/json/json-schema.json @@ -1,73 +1,82 @@ { "$schema": "http://json-schema.org/draft-07/schema#", "definitions": { - "user": { + "caseInstance": { "type": "object", "properties": { - "userID": { + "caseID": { "type": "integer" }, - "userSecretKey": { + "caseUUID": { + "type": "string" + }, + "caseName": { + "type": "string" + }, + "caseStatus": { + "type": "string", + "default": "active" + }, + "caseVariables": { "type": [ + "number", "string", + "boolean", + "object", + "array", "null" ] }, - "userUsername": { + "caseSettings": { "type": [ + "number", "string", + "boolean", + "object", + "array", "null" ] }, - "userPassword": { + "caseDefinition": { "type": [ + "number", "string", + "boolean", + "object", + "array", "null" ] }, - "userFullName": { - "type": [ - "string", - "null" - ] + "caseCreatedDate": { + "type": "string", + "format": "date-time" }, - "userEmail": { - "type": [ - "string", - "null" - ] - }, - "userPhone": { - "type": [ - "string", - "null" - ] - }, - "userStatus": { - "type": [ - "string", - "null" - ] - }, - "userCreatedDate": { + "caseModifiedDate": { "type": [ "string", "null" ], "format": "date-time" }, - "userModifiedDate": { + "caseCompletedDate": { "type": [ "string", "null" ], "format": "date-time" }, - "caseInstance": { - "type": "array", - "items": { - "$ref": "#/definitions/caseInstance" - } + "startedBy": { + "anyOf": [ + { + "$ref": "#/definitions/user" + }, + { + "type": "null" + } + ] + }, + "process": { + "$ref": "#/definitions/process" }, "caseTimeline": { "type": "array", @@ -75,123 +84,45 @@ "$ref": "#/definitions/caseTimeline" } }, - "forms": { - "type": "array", - "items": { - "$ref": "#/definitions/form" - } - }, - "formHistoryEntries": { - "type": "array", - "items": { - "$ref": "#/definitions/formHistory" - } - }, - "processes": { - "type": "array", - "items": { - "$ref": "#/definitions/process" - } - }, - "processHistoryEntries": { - "type": "array", - "items": { - "$ref": "#/definitions/processHistory" - } - }, "task": { "type": "array", "items": { "$ref": "#/definitions/task" } - }, - "userrole": { - "type": "array", - "items": { - "$ref": "#/definitions/userrole" - } - }, - "startedCases": { - "type": "array", - "items": { - "$ref": "#/definitions/caseInstance" - } - }, - "assignedTasks": { - "type": "array", - "items": { - "$ref": "#/definitions/task" - } - }, - "caseTimelineEntries": { - "type": "array", - "items": { - "$ref": "#/definitions/caseTimeline" - } } } }, - "role": { + "caseTimeline": { "type": "object", "properties": { - "roleID": { + "timelineID": { "type": "integer" }, - "roleName": { + "timelineType": { + "type": "string" + }, + "timelineDescription": { "type": [ "string", "null" ] }, - "roleDescription": { - "type": [ - "string", - "null" - ] - }, - "roleStatus": { - "type": [ - "string", - "null" - ] - }, - "roleCreatedDate": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "roleModifiedDate": { - "type": [ - "string", - "null" - ], - "format": "date-time" - }, - "userrole": { - "type": "array", - "items": { - "$ref": "#/definitions/userrole" - } - } - } - }, - "userrole": { - "type": "object", - "properties": { - "userRoleID": { - "type": "integer" - }, - "userRoleCreatedDate": { + "timelineDate": { "type": "string", "format": "date-time" }, - "role": { - "$ref": "#/definitions/role" + "caseInstance": { + "$ref": "#/definitions/caseInstance" }, "user": { - "$ref": "#/definitions/user" + "anyOf": [ + { + "$ref": "#/definitions/user" + }, + { + "type": "null" + } + ] } } }, @@ -277,13 +208,13 @@ } ] }, - "history": { + "formHistory": { "type": "array", "items": { "$ref": "#/definitions/formHistory" } }, - "tasks": { + "task": { "type": "array", "items": { "$ref": "#/definitions/task" @@ -366,7 +297,7 @@ "form": { "$ref": "#/definitions/form" }, - "savedByUser": { + "user": { "anyOf": [ { "$ref": "#/definitions/user" @@ -507,17 +438,11 @@ } ] }, - "history": { + "processHistory": { "type": "array", "items": { "$ref": "#/definitions/processHistory" } - }, - "cases": { - "type": "array", - "items": { - "$ref": "#/definitions/caseInstance" - } } } }, @@ -625,7 +550,7 @@ "process": { "$ref": "#/definitions/process" }, - "savedByUser": { + "user": { "anyOf": [ { "$ref": "#/definitions/user" @@ -637,93 +562,48 @@ } } }, - "caseInstance": { + "role": { "type": "object", "properties": { - "caseID": { + "roleID": { "type": "integer" }, - "caseUUID": { - "type": "string" - }, - "caseName": { - "type": "string" - }, - "caseStatus": { - "type": "string", - "default": "active" - }, - "caseVariables": { + "roleName": { "type": [ - "number", "string", - "boolean", - "object", - "array", "null" ] }, - "caseSettings": { + "roleDescription": { "type": [ - "number", "string", - "boolean", - "object", - "array", "null" ] }, - "caseDefinition": { + "roleStatus": { "type": [ - "number", "string", - "boolean", - "object", - "array", "null" ] }, - "caseCreatedDate": { - "type": "string", - "format": "date-time" - }, - "caseModifiedDate": { + "roleCreatedDate": { "type": [ "string", "null" ], "format": "date-time" }, - "caseCompletedDate": { + "roleModifiedDate": { "type": [ "string", "null" ], "format": "date-time" }, - "process": { - "$ref": "#/definitions/process" - }, - "startedBy": { - "anyOf": [ - { - "$ref": "#/definitions/user" - }, - { - "type": "null" - } - ] - }, - "tasks": { + "userrole": { "type": "array", "items": { - "$ref": "#/definitions/task" - } - }, - "timeline": { - "type": "array", - "items": { - "$ref": "#/definitions/caseTimeline" + "$ref": "#/definitions/userrole" } } } @@ -775,10 +655,10 @@ ], "format": "date-time" }, - "case": { + "caseInstance": { "$ref": "#/definitions/caseInstance" }, - "assignedTo": { + "user": { "anyOf": [ { "$ref": "#/definitions/user" @@ -800,51 +680,144 @@ } } }, - "caseTimeline": { + "user": { "type": "object", "properties": { - "timelineID": { + "userID": { "type": "integer" }, - "timelineType": { - "type": "string" - }, - "timelineDescription": { + "userSecretKey": { "type": [ "string", "null" ] }, - "timelineDate": { + "userUsername": { + "type": [ + "string", + "null" + ] + }, + "userPassword": { + "type": [ + "string", + "null" + ] + }, + "userFullName": { + "type": [ + "string", + "null" + ] + }, + "userEmail": { + "type": [ + "string", + "null" + ] + }, + "userPhone": { + "type": [ + "string", + "null" + ] + }, + "userStatus": { + "type": [ + "string", + "null" + ] + }, + "userCreatedDate": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "userModifiedDate": { + "type": [ + "string", + "null" + ], + "format": "date-time" + }, + "caseInstance": { + "type": "array", + "items": { + "$ref": "#/definitions/caseInstance" + } + }, + "caseTimeline": { + "type": "array", + "items": { + "$ref": "#/definitions/caseTimeline" + } + }, + "form": { + "type": "array", + "items": { + "$ref": "#/definitions/form" + } + }, + "formHistory": { + "type": "array", + "items": { + "$ref": "#/definitions/formHistory" + } + }, + "process": { + "type": "array", + "items": { + "$ref": "#/definitions/process" + } + }, + "processHistory": { + "type": "array", + "items": { + "$ref": "#/definitions/processHistory" + } + }, + "task": { + "type": "array", + "items": { + "$ref": "#/definitions/task" + } + }, + "userrole": { + "type": "array", + "items": { + "$ref": "#/definitions/userrole" + } + } + } + }, + "userrole": { + "type": "object", + "properties": { + "userRoleID": { + "type": "integer" + }, + "userRoleCreatedDate": { "type": "string", "format": "date-time" }, - "case": { - "$ref": "#/definitions/caseInstance" + "role": { + "$ref": "#/definitions/role" }, - "createdBy": { - "anyOf": [ - { - "$ref": "#/definitions/user" - }, - { - "type": "null" - } - ] + "user": { + "$ref": "#/definitions/user" } } } }, "type": "object", "properties": { - "user": { - "$ref": "#/definitions/user" + "caseInstance": { + "$ref": "#/definitions/caseInstance" }, - "role": { - "$ref": "#/definitions/role" - }, - "userrole": { - "$ref": "#/definitions/userrole" + "caseTimeline": { + "$ref": "#/definitions/caseTimeline" }, "form": { "$ref": "#/definitions/form" @@ -858,14 +831,17 @@ "processHistory": { "$ref": "#/definitions/processHistory" }, - "caseInstance": { - "$ref": "#/definitions/caseInstance" + "role": { + "$ref": "#/definitions/role" }, "task": { "$ref": "#/definitions/task" }, - "caseTimeline": { - "$ref": "#/definitions/caseTimeline" + "user": { + "$ref": "#/definitions/user" + }, + "userrole": { + "$ref": "#/definitions/userrole" } } } \ No newline at end of file diff --git a/prisma/schema.prisma b/prisma/schema.prisma index b56d256..a9b05f4 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -12,50 +12,42 @@ datasource db { url = env("DATABASE_URL") } -model user { - userID Int @id @default(autoincrement()) - userSecretKey String? @db.VarChar(255) - userUsername String? @db.VarChar(255) - userPassword String? @db.VarChar(255) - userFullName String? @db.VarChar(255) - userEmail String? @db.VarChar(255) - userPhone String? @db.VarChar(255) - userStatus String? @db.VarChar(255) - userCreatedDate DateTime? @db.DateTime(0) - userModifiedDate DateTime? @db.DateTime(0) - caseInstance caseInstance[] - caseTimeline caseTimeline[] - forms form[] @relation("FormCreator") - formHistoryEntries formHistory[] - processes process[] @relation("ProcessCreator") - processHistoryEntries processHistory[] - task task[] - userrole userrole[] - startedCases caseInstance[] @relation("CaseStartedBy") - assignedTasks task[] @relation("TaskAssignedTo") - caseTimelineEntries caseTimeline[] +model caseInstance { + caseID Int @id @default(autoincrement()) + caseUUID String @unique @db.VarChar(36) + processID Int + caseName String @db.VarChar(255) + caseStatus String @default("active") @db.VarChar(50) + caseStartedBy Int? + caseVariables Json? + caseSettings Json? + caseDefinition Json? + caseCreatedDate DateTime @default(now()) @db.DateTime(0) + caseModifiedDate DateTime? @db.DateTime(0) + caseCompletedDate DateTime? @db.DateTime(0) + startedBy user? @relation(fields: [caseStartedBy], references: [userID]) + process process @relation(fields: [processID], references: [processID]) + caseTimeline caseTimeline[] + task task[] + + @@index([processID], map: "FK_case_process") + @@index([caseStartedBy], map: "FK_case_startedBy") + @@index([caseStatus], map: "IDX_case_status") } -model role { - roleID Int @id @default(autoincrement()) - roleName String? @db.VarChar(255) - roleDescription String? @db.VarChar(255) - roleStatus String? @db.VarChar(255) - roleCreatedDate DateTime? @db.DateTime(0) - roleModifiedDate DateTime? @db.DateTime(0) - userrole userrole[] -} +model caseTimeline { + timelineID Int @id @default(autoincrement()) + caseID Int + timelineType String @db.VarChar(50) + timelineDescription String? @db.Text + timelineDate DateTime @default(now()) @db.DateTime(0) + timelineCreatedBy Int? + caseInstance caseInstance @relation(fields: [caseID], references: [caseID]) + user user? @relation(fields: [timelineCreatedBy], references: [userID]) -model userrole { - userRoleID Int @id @default(autoincrement()) - userRoleUserID Int @default(0) - userRoleRoleID Int @default(0) - userRoleCreatedDate DateTime @db.DateTime(0) - role role @relation(fields: [userRoleRoleID], references: [roleID], onDelete: NoAction, onUpdate: NoAction, map: "FK_userrole_role") - user user @relation(fields: [userRoleUserID], references: [userID], onDelete: NoAction, onUpdate: NoAction, map: "FK_userrole_user") - - @@index([userRoleRoleID], map: "FK_userrole_role") - @@index([userRoleUserID], map: "FK_userrole_user") + @@index([caseID], map: "FK_caseTimeline_case") + @@index([timelineCreatedBy], map: "FK_caseTimeline_createdBy") + @@index([timelineDate], map: "IDX_caseTimeline_date") } model form { @@ -67,14 +59,14 @@ model form { formStatus String @default("active") @db.VarChar(50) formCreatedBy Int? formCreatedDate DateTime @default(now()) @db.DateTime(0) - formModifiedDate DateTime? @updatedAt @db.DateTime(0) + formModifiedDate DateTime? @db.DateTime(0) customCSS String? @db.Text customScript String? @db.LongText formEvents Json? scriptMode String? @default("safe") @db.VarChar(20) - creator user? @relation("FormCreator", fields: [formCreatedBy], references: [userID]) - history formHistory[] @relation("FormHistoryEntries") - tasks task[] + creator user? @relation(fields: [formCreatedBy], references: [userID]) + formHistory formHistory[] + task task[] @@index([formCreatedBy], map: "FK_form_creator") } @@ -95,13 +87,13 @@ model formHistory { changeDescription String? @db.Text savedBy Int? savedDate DateTime @default(now()) @db.DateTime(0) - form form @relation("FormHistoryEntries", fields: [formID], references: [formID], onDelete: Cascade) - savedByUser user? @relation(fields: [savedBy], references: [userID]) + form form @relation(fields: [formID], references: [formID], onDelete: Cascade) + user user? @relation(fields: [savedBy], references: [userID]) @@index([formID], map: "FK_formHistory_form") @@index([savedBy], map: "FK_formHistory_savedBy") - @@index([formUUID], map: "IDX_formHistory_uuid") @@index([savedDate], map: "IDX_formHistory_date") + @@index([formUUID], map: "IDX_formHistory_uuid") } model process { @@ -114,7 +106,7 @@ model process { processStatus String @default("draft") @db.VarChar(50) processCreatedBy Int? processCreatedDate DateTime @default(now()) @db.DateTime(0) - processModifiedDate DateTime? @updatedAt @db.DateTime(0) + processModifiedDate DateTime? @db.DateTime(0) isTemplate Boolean @default(false) processCategory String? @db.VarChar(100) processOwner String? @db.VarChar(255) @@ -125,13 +117,12 @@ model process { templateCategory String? @db.VarChar(100) processDeletedDate DateTime? @db.DateTime(0) caseInstance caseInstance[] - creator user? @relation("ProcessCreator", fields: [processCreatedBy], references: [userID]) - history processHistory[] @relation("ProcessHistoryEntries") - cases caseInstance[] + creator user? @relation(fields: [processCreatedBy], references: [userID]) + processHistory processHistory[] @@index([processCreatedBy], map: "FK_process_creator") - @@index([processStatus], map: "IDX_process_status") @@index([processCategory], map: "IDX_process_category") + @@index([processStatus], map: "IDX_process_status") @@index([isTemplate], map: "IDX_process_template") } @@ -155,72 +146,77 @@ model processHistory { changeDescription String? @db.Text savedBy Int? savedDate DateTime @default(now()) @db.DateTime(0) - process process @relation("ProcessHistoryEntries", fields: [processID], references: [processID], onDelete: Cascade) - savedByUser user? @relation(fields: [savedBy], references: [userID]) + process process @relation(fields: [processID], references: [processID], onDelete: Cascade) + user user? @relation(fields: [savedBy], references: [userID]) @@index([processID], map: "FK_processHistory_process") @@index([savedBy], map: "FK_processHistory_savedBy") - @@index([processUUID], map: "IDX_processHistory_uuid") @@index([savedDate], map: "IDX_processHistory_date") + @@index([processUUID], map: "IDX_processHistory_uuid") } -model caseInstance { - caseID Int @id @default(autoincrement()) - caseUUID String @unique @db.VarChar(36) - processID Int - caseName String @db.VarChar(255) - caseStatus String @default("active") @db.VarChar(50) - caseStartedBy Int? - caseVariables Json? - caseSettings Json? - caseDefinition Json? - caseCreatedDate DateTime @default(now()) @db.DateTime(0) - caseModifiedDate DateTime? @updatedAt @db.DateTime(0) - caseCompletedDate DateTime? @db.DateTime(0) - process process @relation(fields: [processID], references: [processID]) - startedBy user? @relation("CaseStartedBy", fields: [caseStartedBy], references: [userID]) - tasks task[] - timeline caseTimeline[] - - @@index([processID], map: "FK_case_process") - @@index([caseStartedBy], map: "FK_case_startedBy") - @@index([caseStatus], map: "IDX_case_status") +model role { + roleID Int @id @default(autoincrement()) + roleName String? @db.VarChar(255) + roleDescription String? @db.VarChar(255) + roleStatus String? @db.VarChar(255) + roleCreatedDate DateTime? @db.DateTime(0) + roleModifiedDate DateTime? @db.DateTime(0) + userrole userrole[] } model task { - taskID Int @id @default(autoincrement()) - taskUUID String @unique @db.VarChar(36) - caseID Int - taskName String @db.VarChar(255) - taskType String @db.VarChar(50) - taskStatus String @default("pending") @db.VarChar(50) - taskAssignedTo Int? - taskFormID Int? - taskData Json? - taskCreatedDate DateTime @default(now()) @db.DateTime(0) - taskModifiedDate DateTime? @updatedAt @db.DateTime(0) + taskID Int @id @default(autoincrement()) + taskUUID String @unique @db.VarChar(36) + caseID Int + taskName String @db.VarChar(255) + taskType String @db.VarChar(50) + taskStatus String @default("pending") @db.VarChar(50) + taskAssignedTo Int? + taskFormID Int? + taskData Json? + taskCreatedDate DateTime @default(now()) @db.DateTime(0) + taskModifiedDate DateTime? @db.DateTime(0) taskCompletedDate DateTime? @db.DateTime(0) - case caseInstance @relation(fields: [caseID], references: [caseID]) - assignedTo user? @relation("TaskAssignedTo", fields: [taskAssignedTo], references: [userID]) - form form? @relation(fields: [taskFormID], references: [formID]) + caseInstance caseInstance @relation(fields: [caseID], references: [caseID]) + user user? @relation(fields: [taskAssignedTo], references: [userID]) + form form? @relation(fields: [taskFormID], references: [formID]) - @@index([caseID], map: "FK_task_case") @@index([taskAssignedTo], map: "FK_task_assignedTo") + @@index([caseID], map: "FK_task_case") @@index([taskFormID], map: "FK_task_form") @@index([taskStatus], map: "IDX_task_status") } -model caseTimeline { - timelineID Int @id @default(autoincrement()) - caseID Int - timelineType String @db.VarChar(50) - timelineDescription String? @db.Text - timelineDate DateTime @default(now()) @db.DateTime(0) - timelineCreatedBy Int? - case caseInstance @relation(fields: [caseID], references: [caseID]) - createdBy user? @relation(fields: [timelineCreatedBy], references: [userID]) - - @@index([caseID], map: "FK_caseTimeline_case") - @@index([timelineCreatedBy], map: "FK_caseTimeline_createdBy") - @@index([timelineDate], map: "IDX_caseTimeline_date") +model user { + userID Int @id @default(autoincrement()) + userSecretKey String? @db.VarChar(255) + userUsername String? @db.VarChar(255) + userPassword String? @db.VarChar(255) + userFullName String? @db.VarChar(255) + userEmail String? @db.VarChar(255) + userPhone String? @db.VarChar(255) + userStatus String? @db.VarChar(255) + userCreatedDate DateTime? @db.DateTime(0) + userModifiedDate DateTime? @db.DateTime(0) + caseInstance caseInstance[] + caseTimeline caseTimeline[] + form form[] + formHistory formHistory[] + process process[] + processHistory processHistory[] + task task[] + userrole userrole[] +} + +model userrole { + userRoleID Int @id @default(autoincrement()) + userRoleUserID Int @default(0) + userRoleRoleID Int @default(0) + userRoleCreatedDate DateTime @db.DateTime(0) + role role @relation(fields: [userRoleRoleID], references: [roleID], onDelete: NoAction, onUpdate: NoAction, map: "FK_userrole_role") + user user @relation(fields: [userRoleUserID], references: [userID], onDelete: NoAction, onUpdate: NoAction, map: "FK_userrole_user") + + @@index([userRoleRoleID], map: "FK_userrole_role") + @@index([userRoleUserID], map: "FK_userrole_user") } diff --git a/server/api/process/workflow-api-call.post.js b/server/api/process/workflow-api-call.post.js new file mode 100644 index 0000000..321d5d7 --- /dev/null +++ b/server/api/process/workflow-api-call.post.js @@ -0,0 +1,198 @@ +/** + * Workflow API Call Proxy Endpoint + * + * This endpoint acts as a proxy for API calls made during workflow execution. + * It handles the new API node structure with proper authorization and avoids CORS issues. + */ + +// Helper function to substitute variables in a string +function substituteVariables(str, variables) { + if (typeof str !== 'string') return str; + + // Replace {{variable}} first + str = str.replace(/\{\{\s*([a-zA-Z0-9_]+)\s*\}\}/g, (match, varName) => { + const value = variables[varName]; + if (value === undefined || value === null) return ''; + if (typeof value === 'object') { + return JSON.stringify(value); + } + return String(value); + }); + + // Then replace {variable} + str = str.replace(/\{([a-zA-Z0-9_]+)\}/g, (match, varName) => { + const value = variables[varName]; + if (value === undefined || value === null) return ''; + if (typeof value === 'object') { + return JSON.stringify(value); + } + return String(value); + }); + + return str; +} + +// Build API request from node data +function buildApiRequest(nodeData, variables) { + // 1. URL (with param substitution) + let url = substituteVariables(nodeData.apiUrl, variables); + + // 2. Params (for GET, DELETE, etc.) + let params = Array.isArray(nodeData.params) ? nodeData.params : []; + if (params.length) { + const query = params + .filter(p => p.key) + .map(p => `${encodeURIComponent(substituteVariables(p.key, variables))}=${encodeURIComponent(substituteVariables(p.value, variables))}`) + .join('&'); + if (query) { + url += (url.includes('?') ? '&' : '?') + query; + } + } + + // 3. Headers + let headers = {}; + if (Array.isArray(nodeData.headers)) { + nodeData.headers.forEach(h => { + if (h.key) headers[substituteVariables(h.key, variables)] = substituteVariables(h.value, variables); + }); + } else if (typeof nodeData.headers === 'object') { + headers = { ...nodeData.headers }; + } + + // 4. Authorization + if (nodeData.authorization && nodeData.authorization.type && nodeData.authorization.type !== 'none') { + const auth = nodeData.authorization; + if (auth.type === 'bearer' && auth.token) { + headers['Authorization'] = `Bearer ${substituteVariables(auth.token, variables)}`; + } else if (auth.type === 'basic') { + if (auth.token) { + // Basic Auth with token (JWT or other token) + headers['Authorization'] = `Basic ${substituteVariables(auth.token, variables)}`; + } else if (auth.username && auth.password) { + // Basic Auth with username/password + const token = Buffer.from(`${substituteVariables(auth.username, variables)}:${substituteVariables(auth.password, variables)}`).toString('base64'); + headers['Authorization'] = `Basic ${token}`; + } + } else if (auth.type === 'apiKey' && auth.key && auth.value) { + if (auth.in === 'header') { + headers[substituteVariables(auth.key, variables)] = substituteVariables(auth.value, variables); + } else if (auth.in === 'query') { + url += (url.includes('?') ? '&' : '?') + `${encodeURIComponent(substituteVariables(auth.key, variables))}=${encodeURIComponent(substituteVariables(auth.value, variables))}`; + } + } + } + + // 5. Body + let body; + if (nodeData.body && nodeData.body.type && nodeData.body.type !== 'none') { + if (['form-data', 'x-www-form-urlencoded'].includes(nodeData.body.type)) { + const dataArr = Array.isArray(nodeData.body.data) ? nodeData.body.data : []; + if (nodeData.body.type === 'form-data') { + // For server-side, we'll use URLSearchParams for form-data + const formData = new URLSearchParams(); + dataArr.forEach(item => { + if (item.key) formData.append(substituteVariables(item.key, variables), substituteVariables(item.value, variables)); + }); + body = formData.toString(); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } else { + // x-www-form-urlencoded + body = dataArr + .filter(item => item.key) + .map(item => `${encodeURIComponent(substituteVariables(item.key, variables))}=${encodeURIComponent(substituteVariables(item.value, variables))}`) + .join('&'); + headers['Content-Type'] = 'application/x-www-form-urlencoded'; + } + } else if (nodeData.body.type === 'raw') { + body = substituteVariables(nodeData.body.data, variables); + // Try to detect JSON + if (body && body.trim().startsWith('{')) { + headers['Content-Type'] = 'application/json'; + } + } + } + + return { url, headers, body }; +} + +export default defineEventHandler(async (event) => { + try { + // Get request body + const body = await readBody(event); + + // Extract node configuration and process variables + const { nodeData, processVariables } = body; + + // Validate input + if (!nodeData || !nodeData.apiUrl) { + return { + success: false, + error: { + message: 'Invalid API node configuration. Missing apiUrl.' + } + }; + } + + // Build the API request + const { url, headers, body: requestBody } = buildApiRequest(nodeData, processVariables); + const apiMethod = nodeData.apiMethod || 'GET'; + const outputVariable = nodeData.outputVariable || 'apiResponse'; + const errorVariable = nodeData.errorVariable || 'apiError'; + const continueOnError = nodeData.continueOnError || false; + + + + // Prepare fetch options + const fetchOptions = { + method: apiMethod, + headers + }; + + // Add body for non-GET requests + if (!['GET', 'HEAD'].includes(apiMethod) && requestBody) { + fetchOptions.body = requestBody; + } + + // Make the API call + const response = await fetch(url, fetchOptions); + + // Get response data + let responseData; + const contentType = response.headers.get('content-type'); + + if (contentType && contentType.includes('application/json')) { + responseData = await response.json(); + } else { + responseData = await response.text(); + } + + // Prepare result + const result = { + success: response.ok, + data: responseData, + status: response.status, + statusText: response.statusText, + headers: Object.fromEntries([...response.headers.entries()]) + }; + + if (!response.ok) { + result.error = { + message: `API call failed with status ${response.status}`, + status: response.status, + statusText: response.statusText, + data: responseData + }; + } + + return result; + + } catch (error) { + return { + success: false, + error: { + message: error.message || 'An error occurred while making the API call', + stack: process.env.NODE_ENV === 'development' ? error.stack : undefined + } + }; + } +}); \ No newline at end of file