2021-09-21 10:38:24 -07:00
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
2021-08-29 11:58:11 -07:00
|
|
|
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
|
|
|
/* eslint-disable no-underscore-dangle */
|
|
|
|
/* eslint-disable no-continue */
|
|
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
|
|
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
|
|
/* eslint-disable no-restricted-syntax */
|
|
|
|
/* eslint-disable no-param-reassign */
|
|
|
|
import {
|
|
|
|
IDataObject,
|
|
|
|
IExecuteData,
|
|
|
|
INode,
|
2021-10-13 15:21:00 -07:00
|
|
|
INodeCredentialsDetails,
|
2021-08-29 11:58:11 -07:00
|
|
|
IRun,
|
|
|
|
IRunExecutionData,
|
|
|
|
ITaskData,
|
|
|
|
LoggerProxy as Logger,
|
|
|
|
Workflow,
|
|
|
|
} from 'n8n-workflow';
|
|
|
|
import { validate } from 'class-validator';
|
|
|
|
// eslint-disable-next-line import/no-cycle
|
2019-06-23 03:35:23 -07:00
|
|
|
import {
|
2020-05-16 10:05:40 -07:00
|
|
|
CredentialTypes,
|
2019-06-23 03:35:23 -07:00
|
|
|
Db,
|
2020-05-16 10:05:40 -07:00
|
|
|
ICredentialsTypeData,
|
2019-12-19 14:07:55 -08:00
|
|
|
ITransferNodeTypes,
|
2019-06-23 03:35:23 -07:00
|
|
|
IWorkflowErrorData,
|
2020-10-22 06:46:03 -07:00
|
|
|
IWorkflowExecutionDataProcess,
|
2019-06-23 03:35:23 -07:00
|
|
|
NodeTypes,
|
2021-05-29 11:31:21 -07:00
|
|
|
ResponseHelper,
|
2019-08-08 11:38:25 -07:00
|
|
|
WorkflowRunner,
|
2021-08-29 11:58:11 -07:00
|
|
|
} from '.';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
2021-06-23 02:20:07 -07:00
|
|
|
import * as config from '../config';
|
2021-08-29 11:58:11 -07:00
|
|
|
// eslint-disable-next-line import/no-cycle
|
2021-05-29 11:31:21 -07:00
|
|
|
import { WorkflowEntity } from './databases/entities/WorkflowEntity';
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
const ERROR_TRIGGER_TYPE = config.get('nodes.errorTriggerType') as string;
|
|
|
|
|
2019-12-19 14:07:55 -08:00
|
|
|
/**
|
|
|
|
* Returns the data of the last executed node
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {IRun} inputData
|
|
|
|
* @returns {(ITaskData | undefined)}
|
|
|
|
*/
|
|
|
|
export function getDataLastExecutedNodeData(inputData: IRun): ITaskData | undefined {
|
2021-08-29 11:58:11 -07:00
|
|
|
const { runData } = inputData.data.resultData;
|
|
|
|
const { lastNodeExecuted } = inputData.data.resultData;
|
2019-12-19 14:07:55 -08:00
|
|
|
|
|
|
|
if (lastNodeExecuted === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (runData[lastNodeExecuted] === undefined) {
|
|
|
|
return undefined;
|
|
|
|
}
|
|
|
|
|
|
|
|
return runData[lastNodeExecuted][runData[lastNodeExecuted].length - 1];
|
|
|
|
}
|
|
|
|
|
2019-06-23 03:35:23 -07:00
|
|
|
/**
|
|
|
|
* Returns if the given id is a valid workflow id
|
|
|
|
*
|
|
|
|
* @param {(string | null | undefined)} id The id to check
|
|
|
|
* @returns {boolean}
|
|
|
|
* @memberof App
|
|
|
|
*/
|
2021-08-29 11:58:11 -07:00
|
|
|
export function isWorkflowIdValid(id: string | null | undefined | number): boolean {
|
2019-06-23 03:35:23 -07:00
|
|
|
if (typeof id === 'string') {
|
|
|
|
id = parseInt(id, 10);
|
|
|
|
}
|
|
|
|
|
2021-08-29 11:58:11 -07:00
|
|
|
// eslint-disable-next-line no-restricted-globals
|
2019-06-23 03:35:23 -07:00
|
|
|
if (isNaN(id as number)) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Executes the error workflow
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {string} workflowId The id of the error workflow
|
|
|
|
* @param {IWorkflowErrorData} workflowErrorData The error data
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2021-08-29 11:58:11 -07:00
|
|
|
export async function executeErrorWorkflow(
|
|
|
|
workflowId: string,
|
|
|
|
workflowErrorData: IWorkflowErrorData,
|
|
|
|
): Promise<void> {
|
2019-06-23 03:35:23 -07:00
|
|
|
// Wrap everything in try/catch to make sure that no errors bubble up and all get caught here
|
|
|
|
try {
|
2021-05-29 11:31:21 -07:00
|
|
|
const workflowData = await Db.collections.Workflow!.findOne({ id: Number(workflowId) });
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
if (workflowData === undefined) {
|
|
|
|
// The error workflow could not be found
|
2021-08-29 11:58:11 -07:00
|
|
|
Logger.error(
|
|
|
|
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
|
|
|
|
`Calling Error Workflow for "${workflowErrorData.workflow.id}". Could not find error workflow "${workflowId}"`,
|
|
|
|
{ workflowId },
|
|
|
|
);
|
2019-06-23 03:35:23 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
const executionMode = 'error';
|
|
|
|
const nodeTypes = NodeTypes();
|
|
|
|
|
2021-08-29 11:58:11 -07:00
|
|
|
const workflowInstance = new Workflow({
|
|
|
|
id: workflowId,
|
|
|
|
name: workflowData.name,
|
|
|
|
nodeTypes,
|
|
|
|
nodes: workflowData.nodes,
|
|
|
|
connections: workflowData.connections,
|
|
|
|
active: workflowData.active,
|
|
|
|
staticData: workflowData.staticData,
|
|
|
|
settings: workflowData.settings,
|
|
|
|
});
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
let node: INode;
|
|
|
|
let workflowStartNode: INode | undefined;
|
|
|
|
for (const nodeName of Object.keys(workflowInstance.nodes)) {
|
|
|
|
node = workflowInstance.nodes[nodeName];
|
|
|
|
if (node.type === ERROR_TRIGGER_TYPE) {
|
|
|
|
workflowStartNode = node;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (workflowStartNode === undefined) {
|
2021-08-29 11:58:11 -07:00
|
|
|
Logger.error(
|
|
|
|
`Calling Error Workflow for "${workflowErrorData.workflow.id}". Could not find "${ERROR_TRIGGER_TYPE}" in workflow "${workflowId}"`,
|
|
|
|
);
|
2019-06-23 03:35:23 -07:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Can execute without webhook so go on
|
|
|
|
|
|
|
|
// Initialize the data of the webhook node
|
|
|
|
const nodeExecutionStack: IExecuteData[] = [];
|
2021-08-29 11:58:11 -07:00
|
|
|
nodeExecutionStack.push({
|
|
|
|
node: workflowStartNode,
|
|
|
|
data: {
|
|
|
|
main: [
|
|
|
|
[
|
|
|
|
{
|
|
|
|
json: workflowErrorData,
|
|
|
|
},
|
2019-06-23 03:35:23 -07:00
|
|
|
],
|
2021-08-29 11:58:11 -07:00
|
|
|
],
|
|
|
|
},
|
|
|
|
});
|
2019-06-23 03:35:23 -07:00
|
|
|
|
|
|
|
const runExecutionData: IRunExecutionData = {
|
2021-08-29 11:58:11 -07:00
|
|
|
startData: {},
|
2019-06-23 03:35:23 -07:00
|
|
|
resultData: {
|
|
|
|
runData: {},
|
|
|
|
},
|
|
|
|
executionData: {
|
|
|
|
contextData: {},
|
|
|
|
nodeExecutionStack,
|
|
|
|
waitingExecution: {},
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2019-08-08 11:38:25 -07:00
|
|
|
const runData: IWorkflowExecutionDataProcess = {
|
|
|
|
executionMode,
|
|
|
|
executionData: runExecutionData,
|
|
|
|
workflowData,
|
|
|
|
};
|
|
|
|
|
|
|
|
const workflowRunner = new WorkflowRunner();
|
|
|
|
await workflowRunner.run(runData);
|
2019-06-23 03:35:23 -07:00
|
|
|
} catch (error) {
|
2021-08-29 11:58:11 -07:00
|
|
|
Logger.error(
|
|
|
|
`Calling Error Workflow for "${workflowErrorData.workflow.id}": "${error.message}"`,
|
|
|
|
{ workflowId: workflowErrorData.workflow.id },
|
|
|
|
);
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-12-19 14:07:55 -08:00
|
|
|
/**
|
|
|
|
* Returns all the defined NodeTypes
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @returns {ITransferNodeTypes}
|
|
|
|
*/
|
|
|
|
export function getAllNodeTypeData(): ITransferNodeTypes {
|
|
|
|
const nodeTypes = NodeTypes();
|
|
|
|
|
|
|
|
// Get the data of all thenode types that they
|
|
|
|
// can be loaded again in the process
|
|
|
|
const returnData: ITransferNodeTypes = {};
|
|
|
|
for (const nodeTypeName of Object.keys(nodeTypes.nodeTypes)) {
|
|
|
|
if (nodeTypes.nodeTypes[nodeTypeName] === undefined) {
|
|
|
|
throw new Error(`The NodeType "${nodeTypeName}" could not be found!`);
|
|
|
|
}
|
|
|
|
|
|
|
|
returnData[nodeTypeName] = {
|
|
|
|
className: nodeTypes.nodeTypes[nodeTypeName].type.constructor.name,
|
|
|
|
sourcePath: nodeTypes.nodeTypes[nodeTypeName].sourcePath,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return returnData;
|
|
|
|
}
|
|
|
|
|
2022-02-05 13:55:43 -08:00
|
|
|
/**
|
|
|
|
* Returns all the defined CredentialTypes
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @returns {ICredentialsTypeData}
|
|
|
|
*/
|
|
|
|
export function getAllCredentalsTypeData(): ICredentialsTypeData {
|
|
|
|
const credentialTypes = CredentialTypes();
|
|
|
|
|
|
|
|
// Get the data of all the credential types that they
|
|
|
|
// can be loaded again in the subprocess
|
|
|
|
const returnData: ICredentialsTypeData = {};
|
|
|
|
for (const credentialTypeName of Object.keys(credentialTypes.credentialTypes)) {
|
|
|
|
if (credentialTypes.credentialTypes[credentialTypeName] === undefined) {
|
|
|
|
throw new Error(`The CredentialType "${credentialTypeName}" could not be found!`);
|
|
|
|
}
|
|
|
|
|
|
|
|
returnData[credentialTypeName] = {
|
|
|
|
className: credentialTypes.credentialTypes[credentialTypeName].type.constructor.name,
|
|
|
|
sourcePath: credentialTypes.credentialTypes[credentialTypeName].sourcePath,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return returnData;
|
|
|
|
}
|
|
|
|
|
2019-12-19 14:07:55 -08:00
|
|
|
/**
|
|
|
|
* Returns the data of the node types that are needed
|
|
|
|
* to execute the given nodes
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {INode[]} nodes
|
|
|
|
* @returns {ITransferNodeTypes}
|
|
|
|
*/
|
|
|
|
export function getNodeTypeData(nodes: INode[]): ITransferNodeTypes {
|
|
|
|
const nodeTypes = NodeTypes();
|
|
|
|
|
|
|
|
// Check which node-types have to be loaded
|
2021-08-29 11:58:11 -07:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
2019-12-19 14:07:55 -08:00
|
|
|
const neededNodeTypes = getNeededNodeTypes(nodes);
|
|
|
|
|
|
|
|
// Get all the data of the needed node types that they
|
|
|
|
// can be loaded again in the process
|
|
|
|
const returnData: ITransferNodeTypes = {};
|
|
|
|
for (const nodeTypeName of neededNodeTypes) {
|
2021-09-21 10:38:24 -07:00
|
|
|
if (nodeTypes.nodeTypes[nodeTypeName.type] === undefined) {
|
|
|
|
throw new Error(`The NodeType "${nodeTypeName.type}" could not be found!`);
|
2019-12-19 14:07:55 -08:00
|
|
|
}
|
|
|
|
|
2021-09-21 10:38:24 -07:00
|
|
|
returnData[nodeTypeName.type] = {
|
|
|
|
className: nodeTypes.nodeTypes[nodeTypeName.type].type.constructor.name,
|
|
|
|
sourcePath: nodeTypes.nodeTypes[nodeTypeName.type].sourcePath,
|
2019-12-19 14:07:55 -08:00
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
return returnData;
|
|
|
|
}
|
|
|
|
|
2020-05-16 10:05:40 -07:00
|
|
|
/**
|
|
|
|
* Returns the credentials data of the given type and its parent types
|
|
|
|
* it extends
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {string} type The credential type to return data off
|
|
|
|
* @returns {ICredentialsTypeData}
|
|
|
|
*/
|
|
|
|
export function getCredentialsDataWithParents(type: string): ICredentialsTypeData {
|
|
|
|
const credentialTypes = CredentialTypes();
|
|
|
|
const credentialType = credentialTypes.getByName(type);
|
|
|
|
|
|
|
|
const credentialTypeData: ICredentialsTypeData = {};
|
2022-02-05 13:55:43 -08:00
|
|
|
credentialTypeData[type] = {
|
|
|
|
className: credentialTypes.credentialTypes[type].type.constructor.name,
|
|
|
|
sourcePath: credentialTypes.credentialTypes[type].sourcePath,
|
|
|
|
};
|
2020-05-16 10:05:40 -07:00
|
|
|
|
|
|
|
if (credentialType === undefined || credentialType.extends === undefined) {
|
|
|
|
return credentialTypeData;
|
|
|
|
}
|
|
|
|
|
|
|
|
for (const typeName of credentialType.extends) {
|
|
|
|
if (credentialTypeData[typeName] !== undefined) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2022-02-05 13:55:43 -08:00
|
|
|
credentialTypeData[typeName] = {
|
|
|
|
className: credentialTypes.credentialTypes[typeName].type.constructor.name,
|
|
|
|
sourcePath: credentialTypes.credentialTypes[typeName].sourcePath,
|
|
|
|
};
|
2020-05-16 10:05:40 -07:00
|
|
|
Object.assign(credentialTypeData, getCredentialsDataWithParents(typeName));
|
|
|
|
}
|
|
|
|
|
|
|
|
return credentialTypeData;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns all the credentialTypes which are needed to resolve
|
|
|
|
* the given workflow credentials
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {IWorkflowCredentials} credentials The credentials which have to be able to be resolved
|
|
|
|
* @returns {ICredentialsTypeData}
|
|
|
|
*/
|
2021-08-26 04:45:15 -07:00
|
|
|
export function getCredentialsDataByNodes(nodes: INode[]): ICredentialsTypeData {
|
2020-05-16 10:05:40 -07:00
|
|
|
const credentialTypeData: ICredentialsTypeData = {};
|
|
|
|
|
2021-08-26 04:45:15 -07:00
|
|
|
for (const node of nodes) {
|
|
|
|
const credentialsUsedByThisNode = node.credentials;
|
|
|
|
if (credentialsUsedByThisNode) {
|
|
|
|
// const credentialTypesUsedByThisNode = Object.keys(credentialsUsedByThisNode!);
|
2021-08-29 11:58:11 -07:00
|
|
|
for (const credentialType of Object.keys(credentialsUsedByThisNode)) {
|
2021-08-26 04:45:15 -07:00
|
|
|
if (credentialTypeData[credentialType] !== undefined) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
Object.assign(credentialTypeData, getCredentialsDataWithParents(credentialType));
|
|
|
|
}
|
2020-05-16 10:05:40 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return credentialTypeData;
|
|
|
|
}
|
|
|
|
|
2019-12-19 14:07:55 -08:00
|
|
|
/**
|
|
|
|
* Returns the names of the NodeTypes which are are needed
|
|
|
|
* to execute the gives nodes
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {INode[]} nodes
|
|
|
|
* @returns {string[]}
|
|
|
|
*/
|
2021-09-21 10:38:24 -07:00
|
|
|
export function getNeededNodeTypes(nodes: INode[]): Array<{ type: string; version: number }> {
|
2019-12-19 14:07:55 -08:00
|
|
|
// Check which node-types have to be loaded
|
2021-09-21 10:38:24 -07:00
|
|
|
const neededNodeTypes: Array<{ type: string; version: number }> = [];
|
2019-12-19 14:07:55 -08:00
|
|
|
for (const node of nodes) {
|
2021-09-21 10:38:24 -07:00
|
|
|
if (neededNodeTypes.find((neededNodes) => node.type === neededNodes.type) === undefined) {
|
|
|
|
neededNodeTypes.push({ type: node.type, version: node.typeVersion });
|
2019-12-19 14:07:55 -08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return neededNodeTypes;
|
|
|
|
}
|
|
|
|
|
2019-06-23 03:35:23 -07:00
|
|
|
/**
|
|
|
|
* Saves the static data if it changed
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {Workflow} workflow
|
|
|
|
* @returns {Promise <void>}
|
|
|
|
*/
|
2021-08-29 11:58:11 -07:00
|
|
|
export async function saveStaticData(workflow: Workflow): Promise<void> {
|
2019-06-23 03:35:23 -07:00
|
|
|
if (workflow.staticData.__dataChanged === true) {
|
|
|
|
// Static data of workflow changed and so has to be saved
|
2021-08-29 11:58:11 -07:00
|
|
|
if (isWorkflowIdValid(workflow.id)) {
|
2019-06-23 03:35:23 -07:00
|
|
|
// Workflow is saved so update in database
|
|
|
|
try {
|
2021-08-29 11:58:11 -07:00
|
|
|
// eslint-disable-next-line @typescript-eslint/no-use-before-define
|
2019-08-08 11:38:25 -07:00
|
|
|
await saveStaticDataById(workflow.id!, workflow.staticData);
|
2019-06-23 03:35:23 -07:00
|
|
|
workflow.staticData.__dataChanged = false;
|
|
|
|
} catch (e) {
|
2021-08-29 11:58:11 -07:00
|
|
|
Logger.error(
|
|
|
|
`There was a problem saving the workflow with id "${workflow.id}" to save changed staticData: "${e.message}"`,
|
|
|
|
{ workflowId: workflow.id },
|
|
|
|
);
|
2019-06-23 03:35:23 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-08-08 11:38:25 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Saves the given static data on workflow
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {(string | number)} workflowId The id of the workflow to save data on
|
|
|
|
* @param {IDataObject} newStaticData The static data to save
|
|
|
|
* @returns {Promise<void>}
|
|
|
|
*/
|
2021-08-29 11:58:11 -07:00
|
|
|
export async function saveStaticDataById(
|
|
|
|
workflowId: string | number,
|
|
|
|
newStaticData: IDataObject,
|
|
|
|
): Promise<void> {
|
|
|
|
await Db.collections.Workflow!.update(workflowId, {
|
|
|
|
staticData: newStaticData,
|
|
|
|
});
|
2019-08-08 11:38:25 -07:00
|
|
|
}
|
2019-10-14 22:36:53 -07:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Returns the static data of workflow
|
|
|
|
*
|
|
|
|
* @export
|
|
|
|
* @param {(string | number)} workflowId The id of the workflow to get static data of
|
|
|
|
* @returns
|
|
|
|
*/
|
2021-08-29 11:58:11 -07:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2019-10-14 22:36:53 -07:00
|
|
|
export async function getStaticDataById(workflowId: string | number) {
|
2021-08-29 11:58:11 -07:00
|
|
|
const workflowData = await Db.collections.Workflow!.findOne(workflowId, {
|
|
|
|
select: ['staticData'],
|
|
|
|
});
|
2019-10-14 22:36:53 -07:00
|
|
|
|
|
|
|
if (workflowData === undefined) {
|
|
|
|
return {};
|
|
|
|
}
|
|
|
|
|
2021-08-29 11:58:11 -07:00
|
|
|
// eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
|
2019-10-14 22:36:53 -07:00
|
|
|
return workflowData.staticData || {};
|
|
|
|
}
|
2021-05-29 11:31:21 -07:00
|
|
|
|
2021-10-13 15:21:00 -07:00
|
|
|
// Checking if credentials of old format are in use and run a DB check if they might exist uniquely
|
|
|
|
export async function replaceInvalidCredentials(workflow: WorkflowEntity): Promise<WorkflowEntity> {
|
|
|
|
const { nodes } = workflow;
|
|
|
|
if (!nodes) return workflow;
|
|
|
|
|
|
|
|
// caching
|
|
|
|
const credentialsByName: Record<string, Record<string, INodeCredentialsDetails>> = {};
|
|
|
|
const credentialsById: Record<string, Record<string, INodeCredentialsDetails>> = {};
|
|
|
|
|
|
|
|
// for loop to run DB fetches sequential and use cache to keep pressure off DB
|
|
|
|
// trade-off: longer response time for less DB queries
|
|
|
|
/* eslint-disable no-await-in-loop */
|
|
|
|
for (const node of nodes) {
|
|
|
|
if (!node.credentials || node.disabled) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// extract credentials types
|
|
|
|
const allNodeCredentials = Object.entries(node.credentials);
|
|
|
|
for (const [nodeCredentialType, nodeCredentials] of allNodeCredentials) {
|
|
|
|
// Check if Node applies old credentials style
|
|
|
|
if (typeof nodeCredentials === 'string' || nodeCredentials.id === null) {
|
|
|
|
const name = typeof nodeCredentials === 'string' ? nodeCredentials : nodeCredentials.name;
|
|
|
|
// init cache for type
|
|
|
|
if (!credentialsByName[nodeCredentialType]) {
|
|
|
|
credentialsByName[nodeCredentialType] = {};
|
|
|
|
}
|
|
|
|
if (credentialsByName[nodeCredentialType][name] === undefined) {
|
|
|
|
const credentials = await Db.collections.Credentials?.find({
|
|
|
|
name,
|
|
|
|
type: nodeCredentialType,
|
|
|
|
});
|
|
|
|
// if credential name-type combination is unique, use it
|
|
|
|
if (credentials?.length === 1) {
|
|
|
|
credentialsByName[nodeCredentialType][name] = {
|
|
|
|
id: credentials[0].id.toString(),
|
|
|
|
name: credentials[0].name,
|
|
|
|
};
|
|
|
|
node.credentials[nodeCredentialType] = credentialsByName[nodeCredentialType][name];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// nothing found - add invalid credentials to cache to prevent further DB checks
|
|
|
|
credentialsByName[nodeCredentialType][name] = {
|
|
|
|
id: null,
|
|
|
|
name,
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
// get credentials from cache
|
|
|
|
node.credentials[nodeCredentialType] = credentialsByName[nodeCredentialType][name];
|
|
|
|
}
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Node has credentials with an ID
|
|
|
|
|
|
|
|
// init cache for type
|
|
|
|
if (!credentialsById[nodeCredentialType]) {
|
|
|
|
credentialsById[nodeCredentialType] = {};
|
|
|
|
}
|
|
|
|
|
|
|
|
// check if credentials for ID-type are not yet cached
|
|
|
|
if (credentialsById[nodeCredentialType][nodeCredentials.id] === undefined) {
|
|
|
|
// check first if ID-type combination exists
|
|
|
|
const credentials = await Db.collections.Credentials?.findOne({
|
|
|
|
id: nodeCredentials.id,
|
|
|
|
type: nodeCredentialType,
|
|
|
|
});
|
|
|
|
if (credentials) {
|
|
|
|
credentialsById[nodeCredentialType][nodeCredentials.id] = {
|
|
|
|
id: credentials.id.toString(),
|
|
|
|
name: credentials.name,
|
|
|
|
};
|
|
|
|
node.credentials[nodeCredentialType] =
|
|
|
|
credentialsById[nodeCredentialType][nodeCredentials.id];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
// no credentials found for ID, check if some exist for name
|
|
|
|
const credsByName = await Db.collections.Credentials?.find({
|
|
|
|
name: nodeCredentials.name,
|
|
|
|
type: nodeCredentialType,
|
|
|
|
});
|
|
|
|
// if credential name-type combination is unique, take it
|
|
|
|
if (credsByName?.length === 1) {
|
|
|
|
// add found credential to cache
|
|
|
|
credentialsById[nodeCredentialType][credsByName[0].id] = {
|
|
|
|
id: credsByName[0].id.toString(),
|
|
|
|
name: credsByName[0].name,
|
|
|
|
};
|
|
|
|
node.credentials[nodeCredentialType] =
|
|
|
|
credentialsById[nodeCredentialType][credsByName[0].id];
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// nothing found - add invalid credentials to cache to prevent further DB checks
|
|
|
|
credentialsById[nodeCredentialType][nodeCredentials.id] = nodeCredentials;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
// get credentials from cache
|
|
|
|
node.credentials[nodeCredentialType] =
|
|
|
|
credentialsById[nodeCredentialType][nodeCredentials.id];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* eslint-enable no-await-in-loop */
|
|
|
|
return workflow;
|
|
|
|
}
|
|
|
|
|
2021-05-29 11:31:21 -07:00
|
|
|
// TODO: Deduplicate `validateWorkflow` and `throwDuplicateEntryError` with TagHelpers?
|
|
|
|
|
2021-08-29 11:58:11 -07:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2021-05-29 11:31:21 -07:00
|
|
|
export async function validateWorkflow(newWorkflow: WorkflowEntity) {
|
|
|
|
const errors = await validate(newWorkflow);
|
|
|
|
|
|
|
|
if (errors.length) {
|
|
|
|
const validationErrorMessage = Object.values(errors[0].constraints!)[0];
|
|
|
|
throw new ResponseHelper.ResponseError(validationErrorMessage, undefined, 400);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-29 11:58:11 -07:00
|
|
|
// eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
|
2021-05-29 11:31:21 -07:00
|
|
|
export function throwDuplicateEntryError(error: Error) {
|
|
|
|
const errorMessage = error.message.toLowerCase();
|
|
|
|
if (errorMessage.includes('unique') || errorMessage.includes('duplicate')) {
|
2021-08-29 11:58:11 -07:00
|
|
|
throw new ResponseHelper.ResponseError(
|
|
|
|
'There is already a workflow with this name',
|
|
|
|
undefined,
|
|
|
|
400,
|
|
|
|
);
|
2021-05-29 11:31:21 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
throw new ResponseHelper.ResponseError(errorMessage, undefined, 400);
|
|
|
|
}
|
|
|
|
|
2021-09-11 01:15:36 -07:00
|
|
|
export type NameRequest = Express.Request & {
|
2021-05-29 11:31:21 -07:00
|
|
|
query: {
|
|
|
|
name?: string;
|
2021-08-29 11:58:11 -07:00
|
|
|
};
|
2021-05-29 11:31:21 -07:00
|
|
|
};
|