From 0387671cae3084235619a20b76395f144df36f88 Mon Sep 17 00:00:00 2001 From: Jan Oberhauser Date: Tue, 5 May 2020 01:23:54 +0200 Subject: [PATCH] :zap: Add additional external hooks and provide additional data --- packages/cli/config/index.ts | 2 +- packages/cli/src/ExternalHooks.ts | 48 ++++++++----------- packages/cli/src/Interfaces.ts | 6 ++- packages/cli/src/Server.ts | 18 +++++-- .../cli/src/externalHooksTemp/test-hooks.ts | 42 ++++++++++++++-- 5 files changed, 79 insertions(+), 37 deletions(-) diff --git a/packages/cli/config/index.ts b/packages/cli/config/index.ts index 5705a5fc7..7ef5155a6 100644 --- a/packages/cli/config/index.ts +++ b/packages/cli/config/index.ts @@ -253,7 +253,7 @@ const config = convict({ }, externalHookFiles: { - doc: 'Files containing external hooks', + doc: 'Files containing external hooks. Multiple files can be separated by colon (":")', format: String, default: '', env: 'EXTERNAL_HOOK_FILES' diff --git a/packages/cli/src/ExternalHooks.ts b/packages/cli/src/ExternalHooks.ts index 2c00a525c..c4f12301b 100644 --- a/packages/cli/src/ExternalHooks.ts +++ b/packages/cli/src/ExternalHooks.ts @@ -1,21 +1,10 @@ import { Db, - IDatabaseCollections, + IExternalHookFunctions, IExternalHooks, -} from "./"; +} from './'; import * as config from '../config'; -// import { -// access as fsAccess, -// readdir as fsReaddir, -// readFile as fsReadFile, -// stat as fsStat, -// } from 'fs'; - -// TODO: Give different name -interface IHookData { - DbCollections: IDatabaseCollections; -} // export EXTERNAL_HOOK_FILES=/data/packages/cli/dist/src/externalHooksTemp/test-hooks.js @@ -29,39 +18,42 @@ class ExternalHooksClass implements IExternalHooks { async init(): Promise { console.log('ExternalHooks.init'); - const externalHookFiles = config.get('externalHookFiles').split(','); + const externalHookFiles = config.get('externalHookFiles').split(':'); console.log('externalHookFiles'); console.log(externalHookFiles); + // Load all the provided hook-files for (let hookFilePath of externalHookFiles) { hookFilePath = hookFilePath.trim(); if (hookFilePath !== '') { console.log(' --- load: ' + hookFilePath); - const hookFile = require(hookFilePath); + try { + const hookFile = require(hookFilePath); - for (const resource of Object.keys(hookFile)) { - // if (this.externalHooks[resource] === undefined) { - // this.externalHooks[resource] = {}; - // } + for (const resource of Object.keys(hookFile)) { + for (const operation of Object.keys(hookFile[resource])) { + // Save all the hook functions directly under their string + // format in an array + const hookString = `${resource}.${operation}`; + if (this.externalHooks[hookString] === undefined) { + this.externalHooks[hookString] = []; + } - for (const operation of Object.keys(hookFile[resource])) { - const hookString = `${resource}.${operation}`; - if (this.externalHooks[hookString] === undefined) { - this.externalHooks[hookString] = []; + this.externalHooks[hookString].push.apply(this.externalHooks[hookString], hookFile[resource][operation]); } - - this.externalHooks[hookString].push.apply(this.externalHooks[hookString], hookFile[resource][operation]); } + } catch (error) { + throw new Error(`Problem loading external hook file "${hookFilePath}": ${error.message}`); } } } } - async run(hookName: string): Promise { + async run(hookName: string, hookParameters?: any[]): Promise { // tslint:disable-line:no-any console.log('RUN NOW: ' + hookName); - const hookData: IHookData = { + const externalHookFunctions: IExternalHookFunctions = { DbCollections: Db.collections, }; @@ -70,7 +62,7 @@ class ExternalHooksClass implements IExternalHooks { } for(const externalHookFunction of this.externalHooks[hookName]) { - externalHookFunction.call(hookData); + await externalHookFunction.apply(externalHookFunctions, hookParameters); } } diff --git a/packages/cli/src/Interfaces.ts b/packages/cli/src/Interfaces.ts index b54e1eb29..0b9b9eca6 100644 --- a/packages/cli/src/Interfaces.ts +++ b/packages/cli/src/Interfaces.ts @@ -188,9 +188,13 @@ export interface IExecutingWorkflowData { workflowExecution?: PCancelable; } +export interface IExternalHookFunctions { + DbCollections: IDatabaseCollections; +} + export interface IExternalHooks { init(): Promise; - run(hookName: string): Promise; + run(hookName: string, hookParameters?: any[]): Promise; // tslint:disable-line:no-any } export interface IN8nConfig { diff --git a/packages/cli/src/Server.ts b/packages/cli/src/Server.ts index baf4f1aa5..a592c642c 100644 --- a/packages/cli/src/Server.ts +++ b/packages/cli/src/Server.ts @@ -346,7 +346,7 @@ class App { // Creates a new workflow this.app.post('/rest/workflows', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { - const newWorkflowData = req.body; + const newWorkflowData = req.body as IWorkflowBase; newWorkflowData.name = newWorkflowData.name.trim(); newWorkflowData.createdAt = this.getCurrentDate(); @@ -354,6 +354,8 @@ class App { newWorkflowData.id = undefined; + await this.externalHooks.run('workflow.create', [newWorkflowData]); + // Save the workflow in DB const result = await Db.collections.Workflow!.save(newWorkflowData); @@ -429,9 +431,11 @@ class App { // Updates an existing workflow this.app.patch('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { - const newWorkflowData = req.body; + const newWorkflowData = req.body as IWorkflowBase; const id = req.params.id; + await this.externalHooks.run('workflow.update', [newWorkflowData]); + if (this.activeWorkflowRunner.isActive(id)) { // When workflow gets saved always remove it as the triggers could have been // changed and so the changes would not take effect @@ -497,6 +501,8 @@ class App { this.app.delete('/rest/workflows/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const id = req.params.id; + await this.externalHooks.run('workflow.delete', [id]); + if (this.activeWorkflowRunner.isActive(id)) { // Before deleting a workflow deactivate it await this.activeWorkflowRunner.remove(id); @@ -658,6 +664,8 @@ class App { this.app.delete('/rest/credentials/:id', ResponseHelper.send(async (req: express.Request, res: express.Response): Promise => { const id = req.params.id; + await this.externalHooks.run('credentials.delete', [id]); + await Db.collections.Credentials!.delete({ id }); return true; @@ -672,6 +680,8 @@ class App { nodeAccess.date = this.getCurrentDate(); } + await this.externalHooks.run('credentials.create'); + const encryptionKey = await UserSettings.getEncryptionKey(); if (encryptionKey === undefined) { throw new Error('No encryption key got found to encrypt the credentials!'); @@ -694,8 +704,6 @@ class App { throw new ResponseHelper.ResponseError(`Credentials with the same type and name exist already.`, undefined, 400); } - await this.externalHooks.run('credentials.new'); - // Encrypt the data const credentials = new Credentials(incomingData.name, incomingData.type, incomingData.nodesAccess); credentials.setData(incomingData.data, encryptionKey); @@ -722,6 +730,8 @@ class App { const id = req.params.id; + await this.externalHooks.run('credentials.update', [id]); + if (incomingData.name === '') { throw new Error('Credentials have to have a name set!'); } diff --git a/packages/cli/src/externalHooksTemp/test-hooks.ts b/packages/cli/src/externalHooksTemp/test-hooks.ts index 2cb60decf..f10000f1f 100644 --- a/packages/cli/src/externalHooksTemp/test-hooks.ts +++ b/packages/cli/src/externalHooksTemp/test-hooks.ts @@ -1,10 +1,46 @@ +import { + IExternalHookFunctions, + IWorkflowBase, +} from '../'; + +// TODO: Move that to interfaces +interface IExternalHooks { + credentials?: { + create?: Array<{ (this: IExternalHookFunctions): Promise; }> + delete?: Array<{ (this: IExternalHookFunctions, credentialId: string): Promise; }> + update?: Array<{ (this: IExternalHookFunctions, credentialId: string): Promise; }> + }; + workflow?: { + create?: Array<{ (this: IExternalHookFunctions, workflowData: IWorkflowBase): Promise; }> + delete?: Array<{ (this: IExternalHookFunctions, workflowId: string): Promise; }> + update?: Array<{ (this: IExternalHookFunctions, workflowData: IWorkflowBase): Promise; }> + }; +} + export = { credentials: { - new: [ - () => { + create: [ + async function (this: IExternalHookFunctions) { + // console.log(this.DbCollections.Workflow); + // Here any additional code can run or the creation blocked throw new Error('No additional credentials can be created.'); }, ], }, -}; + workflow: { + update: [ + async function (this: IExternalHookFunctions, workflowData: IWorkflowBase) { + console.log('update workflow hook'); + + // const responseData = await this.DbCollections.Workflow!.findOne(workflowData.id); + // console.log('workflowData'); + // console.log(responseData); + // console.log(workflowData); + + // Here any additional code can run or the creation blocked + throw new Error('Workflow can not be updated.'); + }, + ], + }, +} as IExternalHooks;