From 91c40367e11b93967b093b50d012efa2b8396d13 Mon Sep 17 00:00:00 2001 From: ricardo Date: Wed, 27 May 2020 19:32:49 -0400 Subject: [PATCH 01/11] Done --- packages/cli/src/ActiveWorkflowRunner.ts | 156 ++++++++++++------ packages/cli/src/Db.ts | 7 +- packages/cli/src/Interfaces.ts | 8 + packages/cli/src/Server.ts | 12 +- packages/cli/src/WebhookHelpers.ts | 27 +++ .../src/databases/mongodb/WebhookEntity.ts | 31 ++++ packages/cli/src/databases/mongodb/index.ts | 2 + .../src/databases/mysqldb/WebhookEntity.ts | 30 ++++ packages/cli/src/databases/mysqldb/index.ts | 1 + .../src/databases/postgresdb/WebhookEntity.ts | 30 ++++ .../cli/src/databases/postgresdb/index.ts | 2 + .../migrations/1589476000887-WebhookModel.ts | 62 +++++++ .../databases/postgresdb/migrations/index.ts | 2 + .../cli/src/databases/sqlite/WebhookEntity.ts | 30 ++++ packages/cli/src/databases/sqlite/index.ts | 2 +- packages/editor-ui/package.json | 4 +- packages/editor-ui/src/views/NodeView.vue | 6 + packages/workflow/src/Interfaces.ts | 1 + packages/workflow/src/NodeHelpers.ts | 64 ++++++- 19 files changed, 423 insertions(+), 54 deletions(-) create mode 100644 packages/cli/src/databases/mongodb/WebhookEntity.ts create mode 100644 packages/cli/src/databases/mysqldb/WebhookEntity.ts create mode 100644 packages/cli/src/databases/postgresdb/WebhookEntity.ts create mode 100644 packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts create mode 100644 packages/cli/src/databases/sqlite/WebhookEntity.ts diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 9fef6ed243..4be2326323 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -11,11 +11,11 @@ import { WorkflowHelpers, WorkflowRunner, WorkflowExecuteAdditionalData, + IWebhookDb, } from './'; import { ActiveWorkflows, - ActiveWebhooks, NodeExecuteFunctions, } from 'n8n-core'; @@ -26,7 +26,7 @@ import { INode, INodeExecutionData, IRunExecutionData, - IWebhookData, + NodeHelpers, IWorkflowExecuteAdditionalData as IWorkflowExecuteAdditionalDataWorkflow, WebhookHttpMethod, Workflow, @@ -38,19 +38,21 @@ import * as express from 'express'; export class ActiveWorkflowRunner { private activeWorkflows: ActiveWorkflows | null = null; - private activeWebhooks: ActiveWebhooks | null = null; + private activationErrors: { [key: string]: IActivationError; } = {}; async init() { + // Get the active workflows from database + + // NOTE + // Here I guess we can have a flag on the workflow table like hasTrigger + // so intead of pulling all the active wehhooks just pull the actives that have a trigger const workflowsData: IWorkflowDb[] = await Db.collections.Workflow!.find({ active: true }) as IWorkflowDb[]; - this.activeWebhooks = new ActiveWebhooks(); - - // Add them as active workflows this.activeWorkflows = new ActiveWorkflows(); if (workflowsData.length !== 0) { @@ -58,20 +60,27 @@ export class ActiveWorkflowRunner { console.log(' Start Active Workflows:'); console.log(' ================================'); + const nodeTypes = NodeTypes(); + for (const workflowData of workflowsData) { - console.log(` - ${workflowData.name}`); - try { - await this.add(workflowData.id.toString(), workflowData); - console.log(` => Started`); - } catch (error) { - console.log(` => ERROR: Workflow could not be activated:`); - console.log(` ${error.message}`); + + const workflow = new Workflow({ id: workflowData.id.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings}); + + if (workflow.getTriggerNodes().length !== 0 + || workflow.getPollNodes().length !== 0) { + console.log(` - ${workflowData.name}`); + try { + await this.add(workflowData.id.toString(), workflowData); + console.log(` => Started`); + } catch (error) { + console.log(` => ERROR: Workflow could not be activated:`); + console.log(` ${error.message}`); + } } } } } - /** * Removes all the currently active workflows * @@ -94,7 +103,6 @@ export class ActiveWorkflowRunner { return; } - /** * Checks if a webhook for the given method and path exists and executes the workflow. * @@ -110,30 +118,41 @@ export class ActiveWorkflowRunner { throw new ResponseHelper.ResponseError('The "activeWorkflows" instance did not get initialized yet.', 404, 404); } - const webhookData: IWebhookData | undefined = this.activeWebhooks!.get(httpMethod, path); + const webhook = await Db.collections.Webhook?.findOne({ webhookPath: path, method: httpMethod }) as IWebhookDb; - if (webhookData === undefined) { + // check if something exist + if (webhook === undefined) { // The requested webhook is not registered throw new ResponseHelper.ResponseError(`The requested webhook "${httpMethod} ${path}" is not registered.`, 404, 404); } - const workflowData = await Db.collections.Workflow!.findOne(webhookData.workflowId); + const workflowData = await Db.collections.Workflow!.findOne(webhook.workflowId); if (workflowData === undefined) { - throw new ResponseHelper.ResponseError(`Could not find workflow with id "${webhookData.workflowId}"`, 404, 404); + throw new ResponseHelper.ResponseError(`Could not find workflow with id "${webhook.workflowId}"`, 404, 404); } const nodeTypes = NodeTypes(); - const workflow = new Workflow({ id: webhookData.workflowId, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings}); + const workflow = new Workflow({ id: webhook.workflowId.toString(), name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings}); + + const credentials = await WorkflowCredentials([workflow.getNode(webhook.node as string) as INode]); + + const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials); + + const webhookData = NodeHelpers.getNodeWebhooks(workflow, workflow.getNode(webhook.node as string) as INode, additionalData).filter((webhook) => { + return (webhook.httpMethod === httpMethod && webhook.path === path); + })[0]; // Get the node which has the webhook defined to know where to start from and to // get additional data const workflowStartNode = workflow.getNode(webhookData.node); + if (workflowStartNode === null) { throw new ResponseHelper.ResponseError('Could not find node to process webhook.', 404, 404); } return new Promise((resolve, reject) => { const executionMode = 'webhook'; + //@ts-ignore WebhookHelpers.executeWebhook(workflow, webhookData, workflowData, workflowStartNode, executionMode, undefined, req, res, (error: Error | null, data: object) => { if (error !== null) { return reject(error); @@ -143,19 +162,14 @@ export class ActiveWorkflowRunner { }); } - /** * Returns the ids of the currently active workflows * * @returns {string[]} * @memberof ActiveWorkflowRunner */ - getActiveWorkflows(): string[] { - if (this.activeWorkflows === null) { - return []; - } - - return this.activeWorkflows.allActiveWorkflows(); + getActiveWorkflows(): Promise { + return Db.collections.Workflow?.find({ select: ['id'] }) as Promise; } @@ -166,15 +180,11 @@ export class ActiveWorkflowRunner { * @returns {boolean} * @memberof ActiveWorkflowRunner */ - isActive(id: string): boolean { - if (this.activeWorkflows !== null) { - return this.activeWorkflows.isActive(id); - } - - return false; + async isActive(id: string): Promise { + const workflow = await Db.collections.Workflow?.findOne({ id }) as IWorkflowDb; + return workflow?.active as boolean; } - /** * Return error if there was a problem activating the workflow * @@ -190,7 +200,6 @@ export class ActiveWorkflowRunner { return this.activationErrors[id]; } - /** * Adds all the webhooks of the workflow * @@ -202,12 +211,47 @@ export class ActiveWorkflowRunner { */ async addWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): Promise { const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData); + let path = ''; for (const webhookData of webhooks) { - await this.activeWebhooks!.add(workflow, webhookData, mode); - // Save static data! - await WorkflowHelpers.saveStaticData(workflow); + + const node = workflow.getNode(webhookData.node) as INode; + node.name = webhookData.node; + + path = node.parameters.path as string; + + if (node.parameters.path === undefined) { + path = workflow.getSimpleParameterValue(node, webhookData.webhookDescription['path'], 'GET') as string; + } + + const webhook = { + workflowId: webhookData.workflowId, + webhookPath: NodeHelpers.getNodeWebhookPath(workflow.id as string, node, path), + node: node.name, + method: webhookData.httpMethod, + } as IWebhookDb; + + try { + await Db.collections.Webhook?.insert(webhook); + + const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, false); + if (webhookExists === false) { + // If webhook does not exist yet create it + await workflow.runWebhookMethod('create', webhookData, NodeExecuteFunctions, mode, false); + } + + } catch (error) { + // The workflow was saved with two webhooks with the + // same path/method so delete all webhooks saved + + await Db.collections.Webhook?.delete({ workflowId: workflow.id }); + + // then show error to the user + throw new Error(error.message || error.detail); + } } + // Save static data! + await WorkflowHelpers.saveStaticData(workflow); } @@ -227,10 +271,22 @@ export class ActiveWorkflowRunner { const nodeTypes = NodeTypes(); const workflow = new Workflow({ id: workflowId, name: workflowData.name, nodes: workflowData.nodes, connections: workflowData.connections, active: workflowData.active, nodeTypes, staticData: workflowData.staticData, settings: workflowData.settings }); - await this.activeWebhooks!.removeWorkflow(workflow); + const mode = 'internal'; - // Save the static workflow data if needed - await WorkflowHelpers.saveStaticData(workflow); + const credentials = await WorkflowCredentials(workflowData.nodes); + const additionalData = await WorkflowExecuteAdditionalData.getBase(credentials); + + const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData); + + for (const webhookData of webhooks) { + await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, false); + } + + const webhook = { + workflowId: workflowData.id, + } as IWebhookDb; + + await Db.collections.Webhook?.delete(webhook); } @@ -322,7 +378,6 @@ export class ActiveWorkflowRunner { }); } - /** * Makes a workflow active * @@ -361,7 +416,11 @@ export class ActiveWorkflowRunner { // Add the workflows which have webhooks defined await this.addWorkflowWebhooks(workflowInstance, additionalData, mode); - await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, getTriggerFunctions, getPollFunctions); + + if (workflowInstance.getTriggerNodes().length !== 0 + || workflowInstance.getPollNodes().length !== 0) { + await this.activeWorkflows.add(workflowId, workflowInstance, additionalData, getTriggerFunctions, getPollFunctions); + } if (this.activationErrors[workflowId] !== undefined) { // If there were activation errors delete them @@ -386,7 +445,6 @@ export class ActiveWorkflowRunner { await WorkflowHelpers.saveStaticData(workflowInstance!); } - /** * Makes a workflow inactive * @@ -395,6 +453,7 @@ export class ActiveWorkflowRunner { * @memberof ActiveWorkflowRunner */ async remove(workflowId: string): Promise { + if (this.activeWorkflows !== null) { // Remove all the webhooks of the workflow await this.removeWorkflowWebhooks(workflowId); @@ -404,8 +463,13 @@ export class ActiveWorkflowRunner { delete this.activationErrors[workflowId]; } - // Remove the workflow from the "list" of active workflows - return this.activeWorkflows.remove(workflowId); + // if it's active in memory then it's a trigger + // so remove from list of actives workflows + if (this.activeWorkflows.isActive(workflowId)) { + this.activeWorkflows.remove(workflowId); + } + + return; } throw new Error(`The "activeWorkflows" instance did not get initialized yet.`); diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 54633adb13..9077922a5c 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -27,10 +27,12 @@ export let collections: IDatabaseCollections = { Credentials: null, Execution: null, Workflow: null, + Webhook: null, }; import { - InitialMigration1587669153312 + InitialMigration1587669153312, + WebhookModel1589476000887, } from './databases/postgresdb/migrations'; import { @@ -81,7 +83,7 @@ export async function init(): Promise { port: await GenericHelpers.getConfigValue('database.postgresdb.port') as number, username: await GenericHelpers.getConfigValue('database.postgresdb.user') as string, schema: config.get('database.postgresdb.schema'), - migrations: [InitialMigration1587669153312], + migrations: [InitialMigration1587669153312, WebhookModel1589476000887], migrationsRun: true, migrationsTableName: `${entityPrefix}migrations`, }; @@ -135,6 +137,7 @@ export async function init(): Promise { collections.Credentials = getRepository(entities.CredentialsEntity); collections.Execution = getRepository(entities.ExecutionEntity); collections.Workflow = getRepository(entities.WorkflowEntity); + collections.Webhook = getRepository(entities.WebhookEntity); return collections; } diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 225e02885f..65b211b2a9 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -40,8 +40,16 @@ export interface IDatabaseCollections { Credentials: Repository | null; Execution: Repository | null; Workflow: Repository | null; + Webhook: Repository | null; } +export interface IWebhookDb { + id?: number | ObjectID; + workflowId: number | string | ObjectID; + webhookPath: string; + method: string; + node: string; +} export interface IWorkflowBase extends IWorkflowBaseWorkflow { id?: number | string | ObjectID; diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index d078c8594e..4e27568983 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -427,7 +427,9 @@ class App { const newWorkflowData = req.body; const id = req.params.id; - if (this.activeWorkflowRunner.isActive(id)) { + const isActive = await this.activeWorkflowRunner.isActive(id); + + if (isActive) { // When workflow gets saved always remove it as the triggers could have been // changed and so the changes would not take effect await this.activeWorkflowRunner.remove(id); @@ -492,7 +494,9 @@ class App { this.app.delete('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const id = req.params.id; - if (this.activeWorkflowRunner.isActive(id)) { + const isActive = await this.activeWorkflowRunner.isActive(id); + + if (isActive) { // Before deleting a workflow deactivate it await this.activeWorkflowRunner.remove(id); } @@ -503,6 +507,7 @@ class App { })); + this.app.post('/rest/workflows/run', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const workflowData = req.body.workflowData; const runData: IRunData | undefined = req.body.runData; @@ -632,7 +637,8 @@ class App { // Returns the active workflow ids this.app.get('/rest/active', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { - return this.activeWorkflowRunner.getActiveWorkflows(); + const activeWorkflows = await this.activeWorkflowRunner.getActiveWorkflows(); + return activeWorkflows.map(workflow => workflow.id.toString()) as string[]; })); diff --git a/packages/cli/src/WebhookHelpers.ts b/packages/cli/src/WebhookHelpers.ts index d39e3f5dee..0badd1c9d1 100644 --- a/packages/cli/src/WebhookHelpers.ts +++ b/packages/cli/src/WebhookHelpers.ts @@ -69,6 +69,33 @@ export function getWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflo return returnData; } +/** + * Returns all the webhooks which should be created for the give workflow + * + * @export + * @param {string} workflowId + * @param {Workflow} workflow + * @returns {IWebhookData[]} + */ +export function getWorkflowWebhooksBasic(workflow: Workflow): IWebhookData[] { + // Check all the nodes in the workflow if they have webhooks + + const returnData: IWebhookData[] = []; + + let parentNodes: string[] | undefined; + + for (const node of Object.values(workflow.nodes)) { + if (parentNodes !== undefined && !parentNodes.includes(node.name)) { + // If parentNodes are given check only them if they have webhooks + // and no other ones + continue; + } + returnData.push.apply(returnData, NodeHelpers.getNodeWebhooksBasic(workflow, node)); + } + + return returnData; +} + /** * Executes a webhook diff --git a/packages/cli/src/databases/mongodb/WebhookEntity.ts b/packages/cli/src/databases/mongodb/WebhookEntity.ts new file mode 100644 index 0000000000..c9952c2c6d --- /dev/null +++ b/packages/cli/src/databases/mongodb/WebhookEntity.ts @@ -0,0 +1,31 @@ +import { + Column, + Entity, + Unique, + ObjectIdColumn, + ObjectID, +} from 'typeorm'; + +import { + IWebhookDb, + } from '../../Interfaces'; + +@Entity() +@Unique(['webhookPath', 'method']) +export class WebhookEntity implements IWebhookDb { + + @ObjectIdColumn() + id: ObjectID; + + @Column() + workflowId: number; + + @Column() + webhookPath: string; + + @Column() + method: string; + + @Column() + node: string; +} diff --git a/packages/cli/src/databases/mongodb/index.ts b/packages/cli/src/databases/mongodb/index.ts index 164d67fd0c..bd6b9abd60 100644 --- a/packages/cli/src/databases/mongodb/index.ts +++ b/packages/cli/src/databases/mongodb/index.ts @@ -1,3 +1,5 @@ export * from './CredentialsEntity'; export * from './ExecutionEntity'; export * from './WorkflowEntity'; +export * from './WebhookEntity'; + diff --git a/packages/cli/src/databases/mysqldb/WebhookEntity.ts b/packages/cli/src/databases/mysqldb/WebhookEntity.ts new file mode 100644 index 0000000000..df89da4319 --- /dev/null +++ b/packages/cli/src/databases/mysqldb/WebhookEntity.ts @@ -0,0 +1,30 @@ +import { + Column, + Entity, + Unique, + PrimaryGeneratedColumn, +} from 'typeorm'; + +import { + IWebhookDb, + } from '../../Interfaces'; + +@Entity() +@Unique(['webhookPath', 'method']) +export class WebhookEntity implements IWebhookDb { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + workflowId: number; + + @Column() + webhookPath: string; + + @Column() + method: string; + + @Column() + node: string; +} diff --git a/packages/cli/src/databases/mysqldb/index.ts b/packages/cli/src/databases/mysqldb/index.ts index 164d67fd0c..a3494531db 100644 --- a/packages/cli/src/databases/mysqldb/index.ts +++ b/packages/cli/src/databases/mysqldb/index.ts @@ -1,3 +1,4 @@ export * from './CredentialsEntity'; export * from './ExecutionEntity'; export * from './WorkflowEntity'; +export * from './WebhookEntity'; diff --git a/packages/cli/src/databases/postgresdb/WebhookEntity.ts b/packages/cli/src/databases/postgresdb/WebhookEntity.ts new file mode 100644 index 0000000000..07c1c88ae8 --- /dev/null +++ b/packages/cli/src/databases/postgresdb/WebhookEntity.ts @@ -0,0 +1,30 @@ +import { + Column, + Entity, + Unique, + PrimaryGeneratedColumn, +} from 'typeorm'; + +import { + IWebhookDb, + } from '../../'; + +@Entity() +@Unique(['webhookPath', 'method']) +export class WebhookEntity implements IWebhookDb { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + workflowId: number; + + @Column() + webhookPath: string; + + @Column() + method: string; + + @Column() + node: string; +} diff --git a/packages/cli/src/databases/postgresdb/index.ts b/packages/cli/src/databases/postgresdb/index.ts index 164d67fd0c..bd6b9abd60 100644 --- a/packages/cli/src/databases/postgresdb/index.ts +++ b/packages/cli/src/databases/postgresdb/index.ts @@ -1,3 +1,5 @@ export * from './CredentialsEntity'; export * from './ExecutionEntity'; export * from './WorkflowEntity'; +export * from './WebhookEntity'; + diff --git a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts new file mode 100644 index 0000000000..0e8ed1e76a --- /dev/null +++ b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts @@ -0,0 +1,62 @@ +import {MigrationInterface, QueryRunner} from 'typeorm'; + +import { IWorkflowDb, NodeTypes, WebhookHelpers } from '../../..'; +import { Workflow } from 'n8n-workflow/dist/src/Workflow'; +import { + IWebhookDb, + } from '../../../Interfaces'; + + import * as config from '../../../../config'; + +export class WebhookModel1589476000887 implements MigrationInterface { + name = 'WebhookModel1589476000887'; + + async up(queryRunner: QueryRunner): Promise { + let tablePrefix = config.get('database.tablePrefix'); + const schema = config.get('database.postgresdb.schema'); + if (schema) { + tablePrefix = schema + '.' + tablePrefix; + } + + await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("id" SERIAL NOT NULL, "workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "UQ_b21ace2e13596ccd87dc9bf4ea6" UNIQUE ("webhookPath", "method"), CONSTRAINT "PK_202217c8b912cf70b93b1e87256" PRIMARY KEY ("id"))`, undefined); + + const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; + const data: IWebhookDb[] = []; + const nodeTypes = NodeTypes(); + for (const workflow of workflows) { + const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); + // I'm writing something more simple than this. I tried to use the built in method + // getWorkflowWebhooks but it needs additionaldata and to get it I need the credentials + // and for some reason when I use + // const credentials = await WorkflowCredentials(node); + // to get the credentials I got an error I think is cuz the database is yet not ready. + const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); + for (const webhook of webhooks) { + data.push({ + workflowId: workflowInstance.id as string, + webhookPath: webhook.path, + method: webhook.httpMethod, + node: webhook.node, + }); + } + } + + if (data.length !== 0) { + await queryRunner.manager.createQueryBuilder() + .insert() + .into(`${tablePrefix}webhook_entity`) + .values(data) + .execute(); + } + } + + async down(queryRunner: QueryRunner): Promise { + let tablePrefix = config.get('database.tablePrefix'); + const schema = config.get('database.postgresdb.schema'); + if (schema) { + tablePrefix = schema + '.' + tablePrefix; + } + await queryRunner.query(`DROP TABLE ${tablePrefix}webhook_entity`, undefined); + } + +} diff --git a/packages/cli/src/databases/postgresdb/migrations/index.ts b/packages/cli/src/databases/postgresdb/migrations/index.ts index 5bb6551492..827f01796c 100644 --- a/packages/cli/src/databases/postgresdb/migrations/index.ts +++ b/packages/cli/src/databases/postgresdb/migrations/index.ts @@ -1 +1,3 @@ export * from './1587669153312-InitialMigration'; +export * from './1589476000887-WebhookModel'; + diff --git a/packages/cli/src/databases/sqlite/WebhookEntity.ts b/packages/cli/src/databases/sqlite/WebhookEntity.ts new file mode 100644 index 0000000000..df89da4319 --- /dev/null +++ b/packages/cli/src/databases/sqlite/WebhookEntity.ts @@ -0,0 +1,30 @@ +import { + Column, + Entity, + Unique, + PrimaryGeneratedColumn, +} from 'typeorm'; + +import { + IWebhookDb, + } from '../../Interfaces'; + +@Entity() +@Unique(['webhookPath', 'method']) +export class WebhookEntity implements IWebhookDb { + + @PrimaryGeneratedColumn() + id: number; + + @Column() + workflowId: number; + + @Column() + webhookPath: string; + + @Column() + method: string; + + @Column() + node: string; +} diff --git a/packages/cli/src/databases/sqlite/index.ts b/packages/cli/src/databases/sqlite/index.ts index 2c7d6e25e9..a3494531db 100644 --- a/packages/cli/src/databases/sqlite/index.ts +++ b/packages/cli/src/databases/sqlite/index.ts @@ -1,4 +1,4 @@ export * from './CredentialsEntity'; export * from './ExecutionEntity'; export * from './WorkflowEntity'; - +export * from './WebhookEntity'; diff --git a/packages/editor-ui/package.json b/packages/editor-ui/package.json index ec505a172c..783a6ef1dc 100644 --- a/packages/editor-ui/package.json +++ b/packages/editor-ui/package.json @@ -23,7 +23,9 @@ "test:e2e": "vue-cli-service test:e2e", "test:unit": "vue-cli-service test:unit" }, - "dependencies": {}, + "dependencies": { + "uuid": "^8.1.0" + }, "devDependencies": { "@beyonk/google-fonts-webpack-plugin": "^1.2.3", "@fortawesome/fontawesome-svg-core": "^1.2.19", diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index de89534e09..2a86c1be12 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -126,6 +126,8 @@ import RunData from '@/components/RunData.vue'; import mixins from 'vue-typed-mixins'; +import { v4 as uuidv4 } from 'uuid'; + import { debounce } from 'lodash'; import axios from 'axios'; import { @@ -946,6 +948,10 @@ export default mixins( // Check if node-name is unique else find one that is newNodeData.name = this.getUniqueNodeName(newNodeData.name); + if (nodeTypeData.webhooks && nodeTypeData.webhooks.length) { + newNodeData.webhookPath = uuidv4(); + } + await this.addNodes([newNodeData]); // Automatically deselect all nodes and select the current one and also active diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index ce75635fc0..b5f143e3f6 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -297,6 +297,7 @@ export interface INode { continueOnFail?: boolean; parameters: INodeParameters; credentials?: INodeCredentials; + webhookPath?: string; } diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index a72ee565de..87e219699d 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -791,6 +791,59 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: return returnData; } +export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookData[] { + if (node.disabled === true) { + // Node is disabled so webhooks will also not be enabled + return []; + } + + const nodeType = workflow.nodeTypes.getByName(node.type) as INodeType; + + if (nodeType.description.webhooks === undefined) { + // Node does not have any webhooks so return + return []; + } + + const workflowId = workflow.id || '__UNSAVED__'; + + const returnData: IWebhookData[] = []; + for (const webhookDescription of nodeType.description.webhooks) { + let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path'], 'GET'); + if (nodeWebhookPath === undefined) { + // TODO: Use a proper logger + console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`); + continue; + } + + nodeWebhookPath = nodeWebhookPath.toString(); + + if (nodeWebhookPath.charAt(0) === '/') { + nodeWebhookPath = nodeWebhookPath.slice(1); + } + + const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath); + + const httpMethod = workflow.getSimpleParameterValue(node, webhookDescription['httpMethod'], 'GET'); + + if (httpMethod === undefined) { + // TODO: Use a proper logger + console.error(`The webhook "${path}" for node "${node.name}" in workflow "${workflowId}" could not be added because the httpMethod is not defined.`); + continue; + } + + //@ts-ignore + returnData.push({ + httpMethod: httpMethod.toString() as WebhookHttpMethod, + node: node.name, + path, + webhookDescription, + workflowId, + }); + } + + return returnData; +} + /** * Returns the webhook path @@ -802,7 +855,16 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: * @returns {string} */ export function getNodeWebhookPath(workflowId: string, node: INode, path: string): string { - return `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`; + let webhookPath = ''; + if (node.webhookPath === undefined) { + webhookPath = `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`; + } else { + if (node.type === 'n8n-nodes-base.webhook') { + return path; + } + webhookPath = `${node.webhookPath}/${path}`; + } + return webhookPath; } From 4e9490a88d4c6a97414c438ea6ae45604c99c3b0 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 30 May 2020 19:03:58 -0400 Subject: [PATCH 02/11] :zap: Improvements --- packages/cli/src/ActiveWorkflowRunner.ts | 21 +++++++++++++++---- packages/cli/src/Interfaces.ts | 1 - packages/cli/src/TestWebhooks.ts | 4 +++- .../src/databases/mongodb/WebhookEntity.ts | 12 +++-------- .../src/databases/mysqldb/WebhookEntity.ts | 11 +++------- .../src/databases/postgresdb/WebhookEntity.ts | 11 +++------- .../1587669153312-InitialMigration.ts | 3 ++- .../migrations/1589476000887-WebhookModel.ts | 19 +++++++++++++---- .../cli/src/databases/sqlite/WebhookEntity.ts | 11 +++------- packages/core/src/ActiveWebhooks.ts | 9 +++++++- packages/editor-ui/src/views/NodeView.vue | 5 +++++ 11 files changed, 62 insertions(+), 45 deletions(-) diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 4be2326323..a72cb065c7 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -241,13 +241,26 @@ export class ActiveWorkflowRunner { } } catch (error) { - // The workflow was saved with two webhooks with the - // same path/method so delete all webhooks saved + + let errorMessage = ''; await Db.collections.Webhook?.delete({ workflowId: workflow.id }); - // then show error to the user - throw new Error(error.message || error.detail); + // if it's a workflow from the the insert + // TODO check if there is standard error code for deplicate key violation that works + // with all databases + if (error.name === 'QueryFailedError') { + + errorMessage = `The webhook path [${webhook.webhookPath}] and method [${webhook.method}] already exist.`; + + } else if (error.detail) { + // it's a error runnig the webhook methods (checkExists, create) + errorMessage = error.detail; + } else { + errorMessage = error; + } + + throw new Error(errorMessage); } } // Save static data! diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index 65b211b2a9..fd56ad3ada 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -44,7 +44,6 @@ export interface IDatabaseCollections { } export interface IWebhookDb { - id?: number | ObjectID; workflowId: number | string | ObjectID; webhookPath: string; method: string; diff --git a/packages/cli/src/TestWebhooks.ts b/packages/cli/src/TestWebhooks.ts index 45ae624e2b..a20fa76c88 100644 --- a/packages/cli/src/TestWebhooks.ts +++ b/packages/cli/src/TestWebhooks.ts @@ -141,12 +141,14 @@ export class TestWebhooks { let key: string; for (const webhookData of webhooks) { key = this.activeWebhooks!.getWebhookKey(webhookData.httpMethod, webhookData.path); + + await this.activeWebhooks!.add(workflow, webhookData, mode); + this.testWebhookData[key] = { sessionId, timeout, workflowData, }; - await this.activeWebhooks!.add(workflow, webhookData, mode); // Save static data! this.testWebhookData[key].workflowData.staticData = workflow.staticData; diff --git a/packages/cli/src/databases/mongodb/WebhookEntity.ts b/packages/cli/src/databases/mongodb/WebhookEntity.ts index c9952c2c6d..a78fd34ae9 100644 --- a/packages/cli/src/databases/mongodb/WebhookEntity.ts +++ b/packages/cli/src/databases/mongodb/WebhookEntity.ts @@ -1,9 +1,7 @@ import { Column, Entity, - Unique, - ObjectIdColumn, - ObjectID, + PrimaryColumn, } from 'typeorm'; import { @@ -11,19 +9,15 @@ import { } from '../../Interfaces'; @Entity() -@Unique(['webhookPath', 'method']) export class WebhookEntity implements IWebhookDb { - @ObjectIdColumn() - id: ObjectID; - @Column() workflowId: number; - @Column() + @PrimaryColumn() webhookPath: string; - @Column() + @PrimaryColumn() method: string; @Column() diff --git a/packages/cli/src/databases/mysqldb/WebhookEntity.ts b/packages/cli/src/databases/mysqldb/WebhookEntity.ts index df89da4319..a78fd34ae9 100644 --- a/packages/cli/src/databases/mysqldb/WebhookEntity.ts +++ b/packages/cli/src/databases/mysqldb/WebhookEntity.ts @@ -1,8 +1,7 @@ import { Column, Entity, - Unique, - PrimaryGeneratedColumn, + PrimaryColumn, } from 'typeorm'; import { @@ -10,19 +9,15 @@ import { } from '../../Interfaces'; @Entity() -@Unique(['webhookPath', 'method']) export class WebhookEntity implements IWebhookDb { - @PrimaryGeneratedColumn() - id: number; - @Column() workflowId: number; - @Column() + @PrimaryColumn() webhookPath: string; - @Column() + @PrimaryColumn() method: string; @Column() diff --git a/packages/cli/src/databases/postgresdb/WebhookEntity.ts b/packages/cli/src/databases/postgresdb/WebhookEntity.ts index 07c1c88ae8..6e511cde74 100644 --- a/packages/cli/src/databases/postgresdb/WebhookEntity.ts +++ b/packages/cli/src/databases/postgresdb/WebhookEntity.ts @@ -1,8 +1,7 @@ import { Column, Entity, - Unique, - PrimaryGeneratedColumn, + PrimaryColumn, } from 'typeorm'; import { @@ -10,19 +9,15 @@ import { } from '../../'; @Entity() -@Unique(['webhookPath', 'method']) export class WebhookEntity implements IWebhookDb { - @PrimaryGeneratedColumn() - id: number; - @Column() workflowId: number; - @Column() + @PrimaryColumn() webhookPath: string; - @Column() + @PrimaryColumn() method: string; @Column() diff --git a/packages/cli/src/databases/postgresdb/migrations/1587669153312-InitialMigration.ts b/packages/cli/src/databases/postgresdb/migrations/1587669153312-InitialMigration.ts index 555015c10d..5f3798ca66 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1587669153312-InitialMigration.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1587669153312-InitialMigration.ts @@ -1,4 +1,5 @@ -import { MigrationInterface, QueryRunner } from 'typeorm'; +import { + MigrationInterface, QueryRunner } from 'typeorm'; import * as config from '../../../../config'; diff --git a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts index 0e8ed1e76a..a521d227d0 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts @@ -1,7 +1,18 @@ -import {MigrationInterface, QueryRunner} from 'typeorm'; +import { + MigrationInterface, + QueryRunner, +} from 'typeorm'; + +import { + IWorkflowDb, + NodeTypes, + WebhookHelpers, + } from '../../..'; + +import { + Workflow, +} from 'n8n-workflow/dist/src/Workflow'; -import { IWorkflowDb, NodeTypes, WebhookHelpers } from '../../..'; -import { Workflow } from 'n8n-workflow/dist/src/Workflow'; import { IWebhookDb, } from '../../../Interfaces'; @@ -18,7 +29,7 @@ export class WebhookModel1589476000887 implements MigrationInterface { tablePrefix = schema + '.' + tablePrefix; } - await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("id" SERIAL NOT NULL, "workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "UQ_b21ace2e13596ccd87dc9bf4ea6" UNIQUE ("webhookPath", "method"), CONSTRAINT "PK_202217c8b912cf70b93b1e87256" PRIMARY KEY ("id"))`, undefined); + await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "PK_b21ace2e13596ccd87dc9bf4ea6" PRIMARY KEY ("webhookPath", "method"))`, undefined); const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; const data: IWebhookDb[] = []; diff --git a/packages/cli/src/databases/sqlite/WebhookEntity.ts b/packages/cli/src/databases/sqlite/WebhookEntity.ts index df89da4319..a78fd34ae9 100644 --- a/packages/cli/src/databases/sqlite/WebhookEntity.ts +++ b/packages/cli/src/databases/sqlite/WebhookEntity.ts @@ -1,8 +1,7 @@ import { Column, Entity, - Unique, - PrimaryGeneratedColumn, + PrimaryColumn, } from 'typeorm'; import { @@ -10,19 +9,15 @@ import { } from '../../Interfaces'; @Entity() -@Unique(['webhookPath', 'method']) export class WebhookEntity implements IWebhookDb { - @PrimaryGeneratedColumn() - id: number; - @Column() workflowId: number; - @Column() + @PrimaryColumn() webhookPath: string; - @Column() + @PrimaryColumn() method: string; @Column() diff --git a/packages/core/src/ActiveWebhooks.ts b/packages/core/src/ActiveWebhooks.ts index 17cf753830..7f6d705ccd 100644 --- a/packages/core/src/ActiveWebhooks.ts +++ b/packages/core/src/ActiveWebhooks.ts @@ -35,13 +35,20 @@ export class ActiveWebhooks { throw new Error('Webhooks can only be added for saved workflows as an id is needed!'); } + const webhookKey = this.getWebhookKey(webhookData.httpMethod, webhookData.path); + + //check that there is not a webhook already registed with that path/method + if (this.webhookUrls[webhookKey] !== undefined) { + throw new Error('There is test wenhook registered on that path'); + } + if (this.workflowWebhooks[webhookData.workflowId] === undefined) { this.workflowWebhooks[webhookData.workflowId] = []; } // Make the webhook available directly because sometimes to create it successfully // it gets called - this.webhookUrls[this.getWebhookKey(webhookData.httpMethod, webhookData.path)] = webhookData; + this.webhookUrls[webhookKey] = webhookData; const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, this.testWebhooks); if (webhookExists === false) { diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 2a86c1be12..65ff1b5267 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -1585,6 +1585,11 @@ export default mixins( console.error(e); // eslint-disable-line no-console } node.parameters = nodeParameters !== null ? nodeParameters : {}; + + // if it's a webhook and the path is empty set the UUID as the default path + if (node.type === 'n8n-nodes-base.webhook' && node.parameters.path === '') { + node.parameters.path = node.webhookPath as string; + } } foundNodeIssues = this.getNodeIssues(nodeType, node); From 1f4b8f8999046a62c3e85b5b4dfd16bc723abf29 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Sun, 31 May 2020 21:13:45 +0200 Subject: [PATCH 03/11] :zap: Small improvements --- packages/core/src/ActiveWebhooks.ts | 2 +- packages/nodes-base/nodes/Webhook.node.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/core/src/ActiveWebhooks.ts b/packages/core/src/ActiveWebhooks.ts index 7f6d705ccd..529fe01af3 100644 --- a/packages/core/src/ActiveWebhooks.ts +++ b/packages/core/src/ActiveWebhooks.ts @@ -39,7 +39,7 @@ export class ActiveWebhooks { //check that there is not a webhook already registed with that path/method if (this.webhookUrls[webhookKey] !== undefined) { - throw new Error('There is test wenhook registered on that path'); + throw new Error(`Test-Webhook can not be activated because another one with the same method "${webhookData.httpMethod}" and path "${webhookData.path}" is already active!`); } if (this.workflowWebhooks[webhookData.workflowId] === undefined) { diff --git a/packages/nodes-base/nodes/Webhook.node.ts b/packages/nodes-base/nodes/Webhook.node.ts index 135960ea4e..26562abc4c 100644 --- a/packages/nodes-base/nodes/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook.node.ts @@ -133,7 +133,7 @@ export class Webhook implements INodeType { default: '', placeholder: 'webhook', required: true, - description: 'The path to listen to. Slashes("/") in the path are not allowed.', + description: 'The path to listen to.', }, { displayName: 'Response Code', From 5ed86670a8a029c841407a901d7829492b81f311 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 10 Jun 2020 15:39:15 +0200 Subject: [PATCH 04/11] :zap: Make it use of full webhook path more generic --- packages/cli/src/ActiveWorkflowRunner.ts | 16 +++++++++---- packages/cli/src/Server.ts | 1 - packages/core/src/NodeExecuteFunctions.ts | 3 ++- .../editor-ui/src/components/NodeWebhooks.vue | 3 ++- packages/nodes-base/nodes/Webhook.node.ts | 1 + packages/workflow/src/Interfaces.ts | 3 ++- packages/workflow/src/NodeHelpers.ts | 23 +++++++++++-------- packages/workflow/src/Workflow.ts | 2 +- 8 files changed, 33 insertions(+), 19 deletions(-) diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index a72cb065c7..13fc5697ec 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -211,7 +211,7 @@ export class ActiveWorkflowRunner { */ async addWorkflowWebhooks(workflow: Workflow, additionalData: IWorkflowExecuteAdditionalDataWorkflow, mode: WorkflowExecuteMode): Promise { const webhooks = WebhookHelpers.getWorkflowWebhooks(workflow, additionalData); - let path = ''; + let path = '' as string | undefined; for (const webhookData of webhooks) { @@ -221,12 +221,20 @@ export class ActiveWorkflowRunner { path = node.parameters.path as string; if (node.parameters.path === undefined) { - path = workflow.getSimpleParameterValue(node, webhookData.webhookDescription['path'], 'GET') as string; + path = workflow.getSimpleParameterValue(node, webhookData.webhookDescription['path']) as string | undefined; + + if (path === undefined) { + // TODO: Use a proper logger + console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflow.id}".`); + continue; + } } + const isFullPath: boolean = workflow.getSimpleParameterValue(node, webhookData.webhookDescription['isFullPath'], false) as boolean; + const webhook = { workflowId: webhookData.workflowId, - webhookPath: NodeHelpers.getNodeWebhookPath(workflow.id as string, node, path), + webhookPath: NodeHelpers.getNodeWebhookPath(workflow.id as string, node, path, isFullPath), node: node.name, method: webhookData.httpMethod, } as IWebhookDb; @@ -257,7 +265,7 @@ export class ActiveWorkflowRunner { // it's a error runnig the webhook methods (checkExists, create) errorMessage = error.detail; } else { - errorMessage = error; + errorMessage = error.message; } throw new Error(errorMessage); diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index 4e27568983..cf53b85665 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -46,7 +46,6 @@ import { WorkflowCredentials, WebhookHelpers, WorkflowExecuteAdditionalData, - WorkflowHelpers, WorkflowRunner, GenericHelpers, } from './'; diff --git a/packages/core/src/NodeExecuteFunctions.ts b/packages/core/src/NodeExecuteFunctions.ts index 640a75ca9a..9cc9c776de 100644 --- a/packages/core/src/NodeExecuteFunctions.ts +++ b/packages/core/src/NodeExecuteFunctions.ts @@ -289,7 +289,8 @@ export function getNodeWebhookUrl(name: string, workflow: Workflow, node: INode, return undefined; } - return NodeHelpers.getNodeWebhookUrl(baseUrl, workflow.id!, node, path.toString()); + const isFullPath: boolean = workflow.getSimpleParameterValue(node, webhookDescription['isFullPath'], false) as boolean; + return NodeHelpers.getNodeWebhookUrl(baseUrl, workflow.id!, node, path.toString(), isFullPath); } diff --git a/packages/editor-ui/src/components/NodeWebhooks.vue b/packages/editor-ui/src/components/NodeWebhooks.vue index b649575a0b..e2875a49fd 100644 --- a/packages/editor-ui/src/components/NodeWebhooks.vue +++ b/packages/editor-ui/src/components/NodeWebhooks.vue @@ -110,8 +110,9 @@ export default mixins( const workflowId = this.$store.getters.workflowId; const path = this.getValue(webhookData, 'path'); + const isFullPath = this.getValue(webhookData, 'isFullPath') as unknown as boolean || false; - return NodeHelpers.getNodeWebhookUrl(baseUrl, workflowId, this.node, path); + return NodeHelpers.getNodeWebhookUrl(baseUrl, workflowId, this.node, path, isFullPath); }, }, watch: { diff --git a/packages/nodes-base/nodes/Webhook.node.ts b/packages/nodes-base/nodes/Webhook.node.ts index 26562abc4c..1778d44290 100644 --- a/packages/nodes-base/nodes/Webhook.node.ts +++ b/packages/nodes-base/nodes/Webhook.node.ts @@ -77,6 +77,7 @@ export class Webhook implements INodeType { { name: 'default', httpMethod: '={{$parameter["httpMethod"]}}', + isFullPath: true, responseCode: '={{$parameter["responseCode"]}}', responseMode: '={{$parameter["responseMode"]}}', responseData: '={{$parameter["responseData"]}}', diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index b5f143e3f6..32f272f25c 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -520,8 +520,9 @@ export interface IWebhookData { } export interface IWebhookDescription { - [key: string]: WebhookHttpMethod | WebhookResponseMode | string | undefined; + [key: string]: WebhookHttpMethod | WebhookResponseMode | boolean | string | undefined; httpMethod: WebhookHttpMethod | string; + isFullPath?: boolean; name: string; path: string; responseBinaryPropertyName?: string; diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 87e219699d..610aefda56 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -755,7 +755,7 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: const returnData: IWebhookData[] = []; for (const webhookDescription of nodeType.description.webhooks) { - let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path'], 'GET'); + let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path']); if (nodeWebhookPath === undefined) { // TODO: Use a proper logger console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`); @@ -768,7 +768,8 @@ export function getNodeWebhooks(workflow: Workflow, node: INode, additionalData: nodeWebhookPath = nodeWebhookPath.slice(1); } - const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath); + const isFullPath: boolean = workflow.getSimpleParameterValue(node, webhookDescription['isFullPath'], false) as boolean; + const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath); const httpMethod = workflow.getSimpleParameterValue(node, webhookDescription['httpMethod'], 'GET'); @@ -808,7 +809,7 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD const returnData: IWebhookData[] = []; for (const webhookDescription of nodeType.description.webhooks) { - let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path'], 'GET'); + let nodeWebhookPath = workflow.getSimpleParameterValue(node, webhookDescription['path']); if (nodeWebhookPath === undefined) { // TODO: Use a proper logger console.error(`No webhook path could be found for node "${node.name}" in workflow "${workflowId}".`); @@ -821,9 +822,11 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD nodeWebhookPath = nodeWebhookPath.slice(1); } - const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath); + const isFullPath: boolean = workflow.getSimpleParameterValue(node, webhookDescription['isFullPath'], false) as boolean; - const httpMethod = workflow.getSimpleParameterValue(node, webhookDescription['httpMethod'], 'GET'); + const path = getNodeWebhookPath(workflowId, node, nodeWebhookPath, isFullPath); + + const httpMethod = workflow.getSimpleParameterValue(node, webhookDescription['httpMethod']); if (httpMethod === undefined) { // TODO: Use a proper logger @@ -854,12 +857,12 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD * @param {string} path * @returns {string} */ -export function getNodeWebhookPath(workflowId: string, node: INode, path: string): string { +export function getNodeWebhookPath(workflowId: string, node: INode, path: string, isFullPath?: boolean): string { let webhookPath = ''; if (node.webhookPath === undefined) { webhookPath = `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`; } else { - if (node.type === 'n8n-nodes-base.webhook') { + if (isFullPath === true) { return path; } webhookPath = `${node.webhookPath}/${path}`; @@ -876,11 +879,11 @@ export function getNodeWebhookPath(workflowId: string, node: INode, path: string * @param {string} workflowId * @param {string} nodeTypeName * @param {string} path + * @param {boolean} isFullPath * @returns {string} */ -export function getNodeWebhookUrl(baseUrl: string, workflowId: string, node: INode, path: string): string { - // return `${baseUrl}/${workflowId}/${nodeTypeName}/${path}`; - return `${baseUrl}/${getNodeWebhookPath(workflowId, node, path)}`; +export function getNodeWebhookUrl(baseUrl: string, workflowId: string, node: INode, path: string, isFullPath?: boolean): string { + return `${baseUrl}/${getNodeWebhookPath(workflowId, node, path, isFullPath)}`; } diff --git a/packages/workflow/src/Workflow.ts b/packages/workflow/src/Workflow.ts index 9a7d3ef1eb..8d09738598 100644 --- a/packages/workflow/src/Workflow.ts +++ b/packages/workflow/src/Workflow.ts @@ -715,7 +715,7 @@ export class Workflow { * @returns {(string | undefined)} * @memberof Workflow */ - getSimpleParameterValue(node: INode, parameterValue: string | undefined, defaultValue?: boolean | number | string): boolean | number | string | undefined { + getSimpleParameterValue(node: INode, parameterValue: string | boolean | undefined, defaultValue?: boolean | number | string): boolean | number | string | undefined { if (parameterValue === undefined) { // Value is not set so return the default return defaultValue; From 17ee152eaf0647d333e6f27e4243b287e15f6296 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 10 Jun 2020 15:58:57 +0200 Subject: [PATCH 05/11] :zap: Fix indentation --- .../migrations/1589476000887-WebhookModel.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts index a521d227d0..8ff5d583ca 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts @@ -7,7 +7,7 @@ import { IWorkflowDb, NodeTypes, WebhookHelpers, - } from '../../..'; +} from '../../..'; import { Workflow, @@ -15,21 +15,21 @@ import { import { IWebhookDb, - } from '../../../Interfaces'; +} from '../../../Interfaces'; - import * as config from '../../../../config'; +import * as config from '../../../../config'; export class WebhookModel1589476000887 implements MigrationInterface { name = 'WebhookModel1589476000887'; - async up(queryRunner: QueryRunner): Promise { + async up(queryRunner: QueryRunner): Promise { let tablePrefix = config.get('database.tablePrefix'); const schema = config.get('database.postgresdb.schema'); if (schema) { tablePrefix = schema + '.' + tablePrefix; } - await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "PK_b21ace2e13596ccd87dc9bf4ea6" PRIMARY KEY ("webhookPath", "method"))`, undefined); + await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "PK_b21ace2e13596ccd87dc9bf4ea6" PRIMARY KEY ("webhookPath", "method"))`, undefined); const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; const data: IWebhookDb[] = []; @@ -61,13 +61,13 @@ export class WebhookModel1589476000887 implements MigrationInterface { } } - async down(queryRunner: QueryRunner): Promise { + async down(queryRunner: QueryRunner): Promise { let tablePrefix = config.get('database.tablePrefix'); const schema = config.get('database.postgresdb.schema'); if (schema) { tablePrefix = schema + '.' + tablePrefix; } - await queryRunner.query(`DROP TABLE ${tablePrefix}webhook_entity`, undefined); - } + await queryRunner.query(`DROP TABLE ${tablePrefix}webhook_entity`, undefined); + } } From cee5c522de495d1977a548a9dd1d88c60fcc944c Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Wed, 10 Jun 2020 16:17:16 +0200 Subject: [PATCH 06/11] :zap: Rename webhookPath parameter on node to webhookId --- packages/editor-ui/src/views/NodeView.vue | 4 ++-- packages/workflow/src/Interfaces.ts | 2 +- packages/workflow/src/NodeHelpers.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/editor-ui/src/views/NodeView.vue b/packages/editor-ui/src/views/NodeView.vue index 65ff1b5267..e22b98f595 100644 --- a/packages/editor-ui/src/views/NodeView.vue +++ b/packages/editor-ui/src/views/NodeView.vue @@ -949,7 +949,7 @@ export default mixins( newNodeData.name = this.getUniqueNodeName(newNodeData.name); if (nodeTypeData.webhooks && nodeTypeData.webhooks.length) { - newNodeData.webhookPath = uuidv4(); + newNodeData.webhookId = uuidv4(); } await this.addNodes([newNodeData]); @@ -1588,7 +1588,7 @@ export default mixins( // if it's a webhook and the path is empty set the UUID as the default path if (node.type === 'n8n-nodes-base.webhook' && node.parameters.path === '') { - node.parameters.path = node.webhookPath as string; + node.parameters.path = node.webhookId as string; } } diff --git a/packages/workflow/src/Interfaces.ts b/packages/workflow/src/Interfaces.ts index 32f272f25c..393f9e28af 100644 --- a/packages/workflow/src/Interfaces.ts +++ b/packages/workflow/src/Interfaces.ts @@ -297,7 +297,7 @@ export interface INode { continueOnFail?: boolean; parameters: INodeParameters; credentials?: INodeCredentials; - webhookPath?: string; + webhookId?: string; } diff --git a/packages/workflow/src/NodeHelpers.ts b/packages/workflow/src/NodeHelpers.ts index 610aefda56..cede36f5b5 100644 --- a/packages/workflow/src/NodeHelpers.ts +++ b/packages/workflow/src/NodeHelpers.ts @@ -859,13 +859,13 @@ export function getNodeWebhooksBasic(workflow: Workflow, node: INode): IWebhookD */ export function getNodeWebhookPath(workflowId: string, node: INode, path: string, isFullPath?: boolean): string { let webhookPath = ''; - if (node.webhookPath === undefined) { + if (node.webhookId === undefined) { webhookPath = `${workflowId}/${encodeURIComponent(node.name.toLowerCase())}/${path}`; } else { if (isFullPath === true) { return path; } - webhookPath = `${node.webhookPath}/${path}`; + webhookPath = `${node.webhookId}/${path}`; } return webhookPath; } From 84c4b32261fcb48e2777cf08b83e74636bf56cd9 Mon Sep 17 00:00:00 2001 From: ricardo Date: Wed, 17 Jun 2020 23:42:04 -0400 Subject: [PATCH 07/11] :zap: Added migrations to Sqlite and Mysql --- packages/cli/src/Db.ts | 10 ++-- .../src/databases/mongodb/WebhookEntity.ts | 7 ++- .../migrations/1592447867632-WebhookModel.ts | 59 +++++++++++++++++++ .../src/databases/mysqldb/migrations/index.ts | 3 +- .../migrations/1592445003908-WebhookModel.ts | 59 +++++++++++++++++++ .../src/databases/sqlite/migrations/index.ts | 3 +- 6 files changed, 132 insertions(+), 9 deletions(-) create mode 100644 packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts create mode 100644 packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 9077922a5c..7a8c808363 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -40,11 +40,13 @@ import { } from './databases/mongodb/migrations'; import { - InitialMigration1588157391238 + InitialMigration1588157391238, + WebhookModel1592447867632, } from './databases/mysqldb/migrations'; import { - InitialMigration1588102412422 + InitialMigration1588102412422, + WebhookModel1592445003908, } from './databases/sqlite/migrations'; import * as path from 'path'; @@ -100,7 +102,7 @@ export async function init(): Promise { password: await GenericHelpers.getConfigValue('database.mysqldb.password') as string, port: await GenericHelpers.getConfigValue('database.mysqldb.port') as number, username: await GenericHelpers.getConfigValue('database.mysqldb.user') as string, - migrations: [InitialMigration1588157391238], + migrations: [InitialMigration1588157391238, WebhookModel1592447867632], migrationsRun: true, migrationsTableName: `${entityPrefix}migrations`, }; @@ -112,7 +114,7 @@ export async function init(): Promise { type: 'sqlite', database: path.join(n8nFolder, 'database.sqlite'), entityPrefix, - migrations: [InitialMigration1588102412422], + migrations: [InitialMigration1588102412422, WebhookModel1592445003908], migrationsRun: true, migrationsTableName: `${entityPrefix}migrations`, }; diff --git a/packages/cli/src/databases/mongodb/WebhookEntity.ts b/packages/cli/src/databases/mongodb/WebhookEntity.ts index a78fd34ae9..646a995ba9 100644 --- a/packages/cli/src/databases/mongodb/WebhookEntity.ts +++ b/packages/cli/src/databases/mongodb/WebhookEntity.ts @@ -1,7 +1,7 @@ import { Column, Entity, - PrimaryColumn, + Index, } from 'typeorm'; import { @@ -9,15 +9,16 @@ import { } from '../../Interfaces'; @Entity() +@Index(['webhookPath', 'method'], { unique: true }) export class WebhookEntity implements IWebhookDb { @Column() workflowId: number; - @PrimaryColumn() + @Column() webhookPath: string; - @PrimaryColumn() + @Column() method: string; @Column() diff --git a/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts b/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts new file mode 100644 index 0000000000..6c81f612ed --- /dev/null +++ b/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts @@ -0,0 +1,59 @@ +import { + MigrationInterface, + QueryRunner, +} from 'typeorm'; + +import * as config from '../../../../config'; + +import { + IWorkflowDb, + NodeTypes, + WebhookHelpers, +} from '../../..'; + +import { + Workflow, +} from 'n8n-workflow/dist/src/Workflow'; + +import { + IWebhookDb, +} from '../../../Interfaces'; + +export class WebhookModel1592447867632 implements MigrationInterface { + name = 'WebhookModel1592447867632'; + + async up(queryRunner: QueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + + await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}webhook_entity (workflowId int NOT NULL, webhookPath varchar(255) NOT NULL, method varchar(255) NOT NULL, node varchar(255) NOT NULL, PRIMARY KEY (webhookPath, method)) ENGINE=InnoDB`); + + const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; + const data: IWebhookDb[] = []; + const nodeTypes = NodeTypes(); + for (const workflow of workflows) { + const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); + const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); + for (const webhook of webhooks) { + data.push({ + workflowId: workflowInstance.id as string, + webhookPath: webhook.path, + method: webhook.httpMethod, + node: webhook.node, + }); + } + } + + if (data.length !== 0) { + await queryRunner.manager.createQueryBuilder() + .insert() + .into(`${tablePrefix}webhook_entity`) + .values(data) + .execute(); + } + } + + async down(queryRunner: QueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + await queryRunner.query(`DROP TABLE ${tablePrefix}webhook_entity`); + } +} diff --git a/packages/cli/src/databases/mysqldb/migrations/index.ts b/packages/cli/src/databases/mysqldb/migrations/index.ts index ac2dcab467..9e5abd3b8c 100644 --- a/packages/cli/src/databases/mysqldb/migrations/index.ts +++ b/packages/cli/src/databases/mysqldb/migrations/index.ts @@ -1 +1,2 @@ -export * from './1588157391238-InitialMigration'; \ No newline at end of file +export * from './1588157391238-InitialMigration'; +export * from './1592447867632-WebhookModel'; diff --git a/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts b/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts new file mode 100644 index 0000000000..83e59b2f6f --- /dev/null +++ b/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts @@ -0,0 +1,59 @@ +import { + MigrationInterface, + QueryRunner, +} from "typeorm"; + +import * as config from '../../../../config'; + +import { + IWorkflowDb, + NodeTypes, + WebhookHelpers, +} from '../../..'; + +import { + Workflow, +} from 'n8n-workflow/dist/src/Workflow'; + +import { + IWebhookDb, +} from '../../../Interfaces'; + +export class WebhookModel1592445003908 implements MigrationInterface { + name = 'WebhookModel1592445003908'; + + async up(queryRunner: QueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + + await queryRunner.query(`CREATE TABLE IF NOT EXISTS ${tablePrefix}webhook_entity ("workflowId" integer NOT NULL, "webhookPath" varchar NOT NULL, "method" varchar NOT NULL, "node" varchar NOT NULL, PRIMARY KEY ("webhookPath", "method"))`); + + const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; + const data: IWebhookDb[] = []; + const nodeTypes = NodeTypes(); + for (const workflow of workflows) { + const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); + const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); + for (const webhook of webhooks) { + data.push({ + workflowId: workflowInstance.id as string, + webhookPath: webhook.path, + method: webhook.httpMethod, + node: webhook.node, + }); + } + } + + if (data.length !== 0) { + await queryRunner.manager.createQueryBuilder() + .insert() + .into(`${tablePrefix}webhook_entity`) + .values(data) + .execute(); + } + } + + async down(queryRunner: QueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + await queryRunner.query(`DROP TABLE ${tablePrefix}webhook_entity`); + } +} diff --git a/packages/cli/src/databases/sqlite/migrations/index.ts b/packages/cli/src/databases/sqlite/migrations/index.ts index 8d9a0a0b16..a830a007c7 100644 --- a/packages/cli/src/databases/sqlite/migrations/index.ts +++ b/packages/cli/src/databases/sqlite/migrations/index.ts @@ -1 +1,2 @@ -export * from './1588102412422-InitialMigration'; \ No newline at end of file +export * from './1588102412422-InitialMigration'; +export * from './1592445003908-WebhookModel'; From 3ca9647215c9e137f4b5e0fa2e0a524d48366927 Mon Sep 17 00:00:00 2001 From: ricardo Date: Sat, 20 Jun 2020 21:59:06 -0400 Subject: [PATCH 08/11] :zap: Add mongodb migration --- packages/cli/src/ActiveWorkflowRunner.ts | 10 +++- packages/cli/src/Db.ts | 5 +- .../src/databases/mongodb/WebhookEntity.ts | 6 +- .../migrations/1592679094242-WebhookModel.ts | 57 +++++++++++++++++++ .../src/databases/mongodb/migrations/index.ts | 1 + .../migrations/1589476000887-WebhookModel.ts | 5 -- 6 files changed, 73 insertions(+), 11 deletions(-) create mode 100644 packages/cli/src/databases/mongodb/migrations/1592679094242-WebhookModel.ts diff --git a/packages/cli/src/ActiveWorkflowRunner.ts b/packages/cli/src/ActiveWorkflowRunner.ts index 13fc5697ec..c7f7ea1d69 100644 --- a/packages/cli/src/ActiveWorkflowRunner.ts +++ b/packages/cli/src/ActiveWorkflowRunner.ts @@ -35,7 +35,6 @@ import { import * as express from 'express'; - export class ActiveWorkflowRunner { private activeWorkflows: ActiveWorkflows | null = null; @@ -240,6 +239,7 @@ export class ActiveWorkflowRunner { } as IWebhookDb; try { + await Db.collections.Webhook?.insert(webhook); const webhookExists = await workflow.runWebhookMethod('checkExists', webhookData, NodeExecuteFunctions, mode, false); @@ -257,7 +257,7 @@ export class ActiveWorkflowRunner { // if it's a workflow from the the insert // TODO check if there is standard error code for deplicate key violation that works // with all databases - if (error.name === 'QueryFailedError') { + if (error.name === 'MongoError' || error.name === 'QueryFailedError') { errorMessage = `The webhook path [${webhook.webhookPath}] and method [${webhook.method}] already exist.`; @@ -303,6 +303,11 @@ export class ActiveWorkflowRunner { await workflow.runWebhookMethod('delete', webhookData, NodeExecuteFunctions, mode, false); } + // if it's a mongo objectId convert it to string + if (typeof workflowData.id === 'object') { + workflowData.id = workflowData.id.toString(); + } + const webhook = { workflowId: workflowData.id, } as IWebhookDb; @@ -310,7 +315,6 @@ export class ActiveWorkflowRunner { await Db.collections.Webhook?.delete(webhook); } - /** * Runs the given workflow * diff --git a/packages/cli/src/Db.ts b/packages/cli/src/Db.ts index 7a8c808363..ace8b62f1f 100644 --- a/packages/cli/src/Db.ts +++ b/packages/cli/src/Db.ts @@ -36,7 +36,8 @@ import { } from './databases/postgresdb/migrations'; import { - InitialMigration1587563438936 + InitialMigration1587563438936, + WebhookModel1592679094242, } from './databases/mongodb/migrations'; import { @@ -68,7 +69,7 @@ export async function init(): Promise { entityPrefix, url: await GenericHelpers.getConfigValue('database.mongodb.connectionUrl') as string, useNewUrlParser: true, - migrations: [InitialMigration1587563438936], + migrations: [InitialMigration1587563438936, WebhookModel1592679094242], migrationsRun: true, migrationsTableName: `${entityPrefix}migrations`, }; diff --git a/packages/cli/src/databases/mongodb/WebhookEntity.ts b/packages/cli/src/databases/mongodb/WebhookEntity.ts index 646a995ba9..dbf90f3da1 100644 --- a/packages/cli/src/databases/mongodb/WebhookEntity.ts +++ b/packages/cli/src/databases/mongodb/WebhookEntity.ts @@ -2,6 +2,8 @@ import { Column, Entity, Index, + ObjectID, + ObjectIdColumn, } from 'typeorm'; import { @@ -9,9 +11,11 @@ import { } from '../../Interfaces'; @Entity() -@Index(['webhookPath', 'method'], { unique: true }) export class WebhookEntity implements IWebhookDb { + @ObjectIdColumn() + id: ObjectID; + @Column() workflowId: number; diff --git a/packages/cli/src/databases/mongodb/migrations/1592679094242-WebhookModel.ts b/packages/cli/src/databases/mongodb/migrations/1592679094242-WebhookModel.ts new file mode 100644 index 0000000000..c05a44f765 --- /dev/null +++ b/packages/cli/src/databases/mongodb/migrations/1592679094242-WebhookModel.ts @@ -0,0 +1,57 @@ +import { + MigrationInterface, +} from 'typeorm'; + +import { + IWorkflowDb, + NodeTypes, + WebhookHelpers, +} from '../../..'; + +import { + Workflow, +} from 'n8n-workflow/dist/src/Workflow'; + +import { + IWebhookDb, +} from '../../../Interfaces'; + +import * as config from '../../../../config'; + +import { + MongoQueryRunner, +} from 'typeorm/driver/mongodb/MongoQueryRunner'; + +export class WebhookModel1592679094242 implements MigrationInterface { + name = 'WebhookModel1592679094242'; + + async up(queryRunner: MongoQueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + const workflows = await queryRunner.cursor( `${tablePrefix}workflow_entity`, { active: true }).toArray() as IWorkflowDb[]; + const data: IWebhookDb[] = []; + const nodeTypes = NodeTypes(); + for (const workflow of workflows) { + const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); + const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); + for (const webhook of webhooks) { + data.push({ + workflowId: workflowInstance.id as string, + webhookPath: webhook.path, + method: webhook.httpMethod, + node: webhook.node, + }); + } + } + + if (data.length !== 0) { + await queryRunner.manager.insertMany(`${tablePrefix}webhook_entity`, data); + } + + await queryRunner.manager.createCollectionIndex(`${tablePrefix}webhook_entity`, ['webhookPath', 'method'], { unique: true, background: false }); + } + + async down(queryRunner: MongoQueryRunner): Promise { + const tablePrefix = config.get('database.tablePrefix'); + await queryRunner.dropTable(`${tablePrefix}webhook_entity`); + } +} diff --git a/packages/cli/src/databases/mongodb/migrations/index.ts b/packages/cli/src/databases/mongodb/migrations/index.ts index a60bdc7cf8..4072ab582d 100644 --- a/packages/cli/src/databases/mongodb/migrations/index.ts +++ b/packages/cli/src/databases/mongodb/migrations/index.ts @@ -1 +1,2 @@ export * from './1587563438936-InitialMigration'; +export * from './1592679094242-WebhookModel'; diff --git a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts index 8ff5d583ca..dfbf94a799 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts @@ -36,11 +36,6 @@ export class WebhookModel1589476000887 implements MigrationInterface { const nodeTypes = NodeTypes(); for (const workflow of workflows) { const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); - // I'm writing something more simple than this. I tried to use the built in method - // getWorkflowWebhooks but it needs additionaldata and to get it I need the credentials - // and for some reason when I use - // const credentials = await WorkflowCredentials(node); - // to get the credentials I got an error I think is cuz the database is yet not ready. const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); for (const webhook of webhooks) { data.push({ From e59dd00c107d656abb03d132113298d0c7bf9f1c Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 22 Jun 2020 13:26:41 -0400 Subject: [PATCH 09/11] done --- .../databases/sqlite/migrations/1592445003908-WebhookModel.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts b/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts index 83e59b2f6f..f1402fad88 100644 --- a/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts +++ b/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts @@ -31,6 +31,10 @@ export class WebhookModel1592445003908 implements MigrationInterface { const data: IWebhookDb[] = []; const nodeTypes = NodeTypes(); for (const workflow of workflows) { + workflow.nodes = JSON.parse(workflow.nodes as unknown as string); + workflow.connections = JSON.parse(workflow.connections as unknown as string); + workflow.staticData = JSON.parse(workflow.staticData as unknown as string); + workflow.settings = JSON.parse(workflow.settings as unknown as string); const workflowInstance = new Workflow({ id: workflow.id as string, name: workflow.name, nodes: workflow.nodes, connections: workflow.connections, active: workflow.active, nodeTypes, staticData: workflow.staticData, settings: workflow.settings }); const webhooks = WebhookHelpers.getWorkflowWebhooksBasic(workflowInstance); for (const webhook of webhooks) { From 94cd2c76c2abcbbcc880f88dda7ebfa280089f68 Mon Sep 17 00:00:00 2001 From: ricardo Date: Mon, 22 Jun 2020 19:37:58 -0400 Subject: [PATCH 10/11] :zap: updated n8n packages to it's latest version. --- packages/core/package.json | 2 +- packages/node-dev/package.json | 4 ++-- packages/nodes-base/package.json | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/core/package.json b/packages/core/package.json index 598ae14b23..9a6352671b 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -45,7 +45,7 @@ "crypto-js": "3.1.9-1", "lodash.get": "^4.4.2", "mmmagic": "^0.5.2", - "n8n-workflow": "~0.32.0", + "n8n-workflow": "~0.33.0", "p-cancelable": "^2.0.0", "request": "^2.88.2", "request-promise-native": "^1.0.7" diff --git a/packages/node-dev/package.json b/packages/node-dev/package.json index 66394fec29..8df65a2792 100644 --- a/packages/node-dev/package.json +++ b/packages/node-dev/package.json @@ -58,8 +58,8 @@ "change-case": "^4.1.1", "copyfiles": "^2.1.1", "inquirer": "^7.0.0", - "n8n-core": "^0.31.0", - "n8n-workflow": "^0.28.0", + "n8n-core": "^0.36.0", + "n8n-workflow": "^0.33.0", "replace-in-file": "^6.0.0", "request": "^2.88.2", "tmp-promise": "^2.0.2", diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json index a09d97d032..e38403add3 100644 --- a/packages/nodes-base/package.json +++ b/packages/nodes-base/package.json @@ -317,7 +317,7 @@ "@types/xml2js": "^0.4.3", "gulp": "^4.0.0", "jest": "^24.9.0", - "n8n-workflow": "~0.32.0", + "n8n-workflow": "~0.33.0", "ts-jest": "^24.0.2", "tslint": "^5.17.0", "typescript": "~3.7.4" From e60edc3bff68a6e7fb72af7454bf8f77d2e3baad Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 23 Jun 2020 12:43:40 +0200 Subject: [PATCH 11/11] :zap: Minor improvements --- .../mysqldb/migrations/1592447867632-WebhookModel.ts | 2 +- .../postgresdb/migrations/1589476000887-WebhookModel.ts | 5 +++-- .../sqlite/migrations/1588102412422-InitialMigration.ts | 5 ++++- .../sqlite/migrations/1592445003908-WebhookModel.ts | 4 ++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts b/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts index 6c81f612ed..8a49080462 100644 --- a/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts +++ b/packages/cli/src/databases/mysqldb/migrations/1592447867632-WebhookModel.ts @@ -13,7 +13,7 @@ import { import { Workflow, -} from 'n8n-workflow/dist/src/Workflow'; +} from 'n8n-workflow'; import { IWebhookDb, diff --git a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts index dfbf94a799..e53fc28915 100644 --- a/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts +++ b/packages/cli/src/databases/postgresdb/migrations/1589476000887-WebhookModel.ts @@ -11,7 +11,7 @@ import { import { Workflow, -} from 'n8n-workflow/dist/src/Workflow'; +} from 'n8n-workflow'; import { IWebhookDb, @@ -24,12 +24,13 @@ export class WebhookModel1589476000887 implements MigrationInterface { async up(queryRunner: QueryRunner): Promise { let tablePrefix = config.get('database.tablePrefix'); + const tablePrefixIndex = tablePrefix; const schema = config.get('database.postgresdb.schema'); if (schema) { tablePrefix = schema + '.' + tablePrefix; } - await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "PK_b21ace2e13596ccd87dc9bf4ea6" PRIMARY KEY ("webhookPath", "method"))`, undefined); + await queryRunner.query(`CREATE TABLE ${tablePrefix}webhook_entity ("workflowId" integer NOT NULL, "webhookPath" character varying NOT NULL, "method" character varying NOT NULL, "node" character varying NOT NULL, CONSTRAINT "PK_${tablePrefixIndex}b21ace2e13596ccd87dc9bf4ea6" PRIMARY KEY ("webhookPath", "method"))`, undefined); const workflows = await queryRunner.query(`SELECT * FROM ${tablePrefix}workflow_entity WHERE active=true`) as IWorkflowDb[]; const data: IWebhookDb[] = []; diff --git a/packages/cli/src/databases/sqlite/migrations/1588102412422-InitialMigration.ts b/packages/cli/src/databases/sqlite/migrations/1588102412422-InitialMigration.ts index c2bb55040e..09a0da911a 100644 --- a/packages/cli/src/databases/sqlite/migrations/1588102412422-InitialMigration.ts +++ b/packages/cli/src/databases/sqlite/migrations/1588102412422-InitialMigration.ts @@ -1,4 +1,7 @@ -import { MigrationInterface, QueryRunner } from "typeorm"; +import { + MigrationInterface, + QueryRunner, +} from 'typeorm'; import * as config from '../../../../config'; diff --git a/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts b/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts index f1402fad88..92704482b2 100644 --- a/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts +++ b/packages/cli/src/databases/sqlite/migrations/1592445003908-WebhookModel.ts @@ -1,7 +1,7 @@ import { MigrationInterface, QueryRunner, -} from "typeorm"; +} from 'typeorm'; import * as config from '../../../../config'; @@ -13,7 +13,7 @@ import { import { Workflow, -} from 'n8n-workflow/dist/src/Workflow'; +} from 'n8n-workflow'; import { IWebhookDb,