diff --git a/Common/Types/Workflow/Component.ts b/Common/Types/Workflow/Component.ts index 5619faa4f4..c2f8208c39 100644 --- a/Common/Types/Workflow/Component.ts +++ b/Common/Types/Workflow/Component.ts @@ -22,6 +22,7 @@ export enum ComponentInputType { BaseModelArray = 'Database Records', JSONArray = 'List of JSON', LongText = 'Long Text', + HTML = 'HTML', } export enum ComponentType { diff --git a/Common/Types/Workflow/ComponentID.ts b/Common/Types/Workflow/ComponentID.ts index f910fbda7c..c8b8a8b65d 100644 --- a/Common/Types/Workflow/ComponentID.ts +++ b/Common/Types/Workflow/ComponentID.ts @@ -4,6 +4,15 @@ enum ComponentID { Schedule = 'schedule', JavaScriptCode = 'javascript', Manual = 'manual', + JsonToText = 'json-to-text', + TextToJson = 'text-to-json', + MergeJson = 'merge-json', + ApiGet = 'api-get', + ApiPut = 'api-put', + ApiPost = 'api-post', + ApiDelete = 'api-delete', + SendEmail = 'send-email', + IfElse = 'if-else', } export default ComponentID; diff --git a/Common/Types/Workflow/Components/API.ts b/Common/Types/Workflow/Components/API.ts index 8eef72d456..9dd52cc4cf 100644 --- a/Common/Types/Workflow/Components/API.ts +++ b/Common/Types/Workflow/Components/API.ts @@ -1,4 +1,5 @@ import IconProp from '../../Icon/IconProp'; +import ComponentID from '../ComponentID'; import ComponentMetadata, { ComponentInputType, ComponentType, @@ -6,7 +7,7 @@ import ComponentMetadata, { const components: Array = [ { - id: 'api-get', + id: ComponentID.ApiGet, title: 'API Get (JSON)', category: 'API', description: 'Send Get API Request and get JSON Response', @@ -26,17 +27,9 @@ const components: Array = [ name: 'Request Body', description: 'Request Body in JSON', type: ComponentInputType.JSON, - required: true, - placeholder: '{"key1": "value1", "key2": "value2", ....}', - }, - { - id: 'query-string', - name: 'Query String', - description: 'Send query string params.', - type: ComponentInputType.StringDictionary, required: false, - isAdvanced: true, - placeholder: '{"query1": "value1", "query2": "value2", ....}', + placeholder: + 'Example: {"key1": "value1", "key2": "value2", ....}', }, { id: 'request-headers', @@ -45,7 +38,8 @@ const components: Array = [ type: ComponentInputType.StringDictionary, required: false, isAdvanced: true, - placeholder: '{"header1": "value1", "header2": "value2", ....}', + placeholder: + 'Example: {"header1": "value1", "header2": "value2", ....}', }, ], returnValues: [ @@ -57,7 +51,7 @@ const components: Array = [ required: false, }, { - id: 'status', + id: 'response-status', name: 'Response Status', description: 'Response Status (200, for example)', type: ComponentInputType.Number, @@ -101,7 +95,7 @@ const components: Array = [ ], }, { - id: 'api-post', + id: ComponentID.ApiPost, title: 'API Post (JSON)', category: 'API', description: 'Send a POST Request and get JSON Response', @@ -121,17 +115,9 @@ const components: Array = [ name: 'Request Body', description: 'Request Body in JSON', type: ComponentInputType.JSON, - required: true, - placeholder: '{"key1": "value1", "key2": "value2", ....}', - }, - { - id: 'query-string', - name: 'Query String', - description: 'Send query string params.', - type: ComponentInputType.StringDictionary, required: false, - isAdvanced: true, - placeholder: '{"query1": "value1", "query2": "value2", ....}', + placeholder: + 'Example: {"key1": "value1", "key2": "value2", ....}', }, { id: 'request-headers', @@ -140,7 +126,8 @@ const components: Array = [ type: ComponentInputType.StringDictionary, required: false, isAdvanced: true, - placeholder: '{"header1": "value1", "header2": "value2", ....}', + placeholder: + 'Example: {"header1": "value1", "header2": "value2", ....}', }, ], returnValues: [ @@ -152,7 +139,7 @@ const components: Array = [ required: false, }, { - id: 'status', + id: 'response-status', name: 'Response Status', description: 'Response Status (200, for example)', type: ComponentInputType.Number, @@ -196,8 +183,8 @@ const components: Array = [ ], }, { - id: 'api-patch', - title: 'API Patch (JSON)', + id: ComponentID.ApiPut, + title: 'API Put (JSON)', category: 'API', description: 'Send a PATCH Request and get JSON Response', iconProp: IconProp.Globe, @@ -216,17 +203,9 @@ const components: Array = [ name: 'Request Body', description: 'Request Body in JSON', type: ComponentInputType.JSON, - required: true, - placeholder: '{"key1": "value1", "key2": "value2", ....}', - }, - { - id: 'query-string', - name: 'Query String', - description: 'Send query string params.', - type: ComponentInputType.StringDictionary, required: false, - isAdvanced: true, - placeholder: '{"query1": "value1", "query2": "value2", ....}', + placeholder: + 'Example: {"key1": "value1", "key2": "value2", ....}', }, { id: 'request-headers', @@ -235,7 +214,8 @@ const components: Array = [ type: ComponentInputType.StringDictionary, required: false, isAdvanced: true, - placeholder: '{"header1": "value1", "header2": "value2", ....}', + placeholder: + 'Example: {"header1": "value1", "header2": "value2", ....}', }, ], returnValues: [ @@ -247,7 +227,7 @@ const components: Array = [ required: false, }, { - id: 'status', + id: 'response-status', name: 'Response Status', description: 'Response Status (200, for example)', type: ComponentInputType.Number, @@ -291,7 +271,7 @@ const components: Array = [ ], }, { - id: 'api-delete', + id: ComponentID.ApiDelete, title: 'API Delete (JSON)', category: 'API', description: 'Send a PATCH Request and get JSON Response', @@ -311,17 +291,9 @@ const components: Array = [ name: 'Request Body', description: 'Request Body in JSON', type: ComponentInputType.JSON, - required: true, - placeholder: '{"key1": "value1", "key2": "value2", ....}', - }, - { - id: 'query-string', - name: 'Query String', - description: 'Send query string params.', - type: ComponentInputType.StringDictionary, required: false, - isAdvanced: true, - placeholder: '{"query1": "value1", "query2": "value2", ....}', + placeholder: + 'Example: {"key1": "value1", "key2": "value2", ....}', }, { id: 'request-headers', @@ -330,7 +302,8 @@ const components: Array = [ type: ComponentInputType.StringDictionary, required: false, isAdvanced: true, - placeholder: '{"header1": "value1", "header2": "value2", ....}', + placeholder: + 'Example: {"header1": "value1", "header2": "value2", ....}', }, ], returnValues: [ @@ -342,7 +315,7 @@ const components: Array = [ required: false, }, { - id: 'status', + id: 'response-status', name: 'Response Status', description: 'Response Status (200, for example)', type: ComponentInputType.Number, diff --git a/Common/Types/Workflow/Components/BaseModel.ts b/Common/Types/Workflow/Components/BaseModel.ts index d01483eee0..003ff351b2 100644 --- a/Common/Types/Workflow/Components/BaseModel.ts +++ b/Common/Types/Workflow/Components/BaseModel.ts @@ -30,6 +30,7 @@ export default class BaseModelComponent { description: `Query on ${model.singularName}`, required: true, id: 'query', + placeholder: "Example: {'columnName': 'value', ...}", }, { type: ComponentInputType.Query, @@ -37,6 +38,7 @@ export default class BaseModelComponent { description: `Select on ${model.singularName}`, required: true, id: 'select', + placeholder: "Example: {'columnName': true, ...}", }, ], returnValues: [ @@ -87,6 +89,7 @@ export default class BaseModelComponent { description: 'Please fill out this query', required: true, id: 'query', + placeholder: "Example: {'columnName': 'value', ...}", }, { type: ComponentInputType.Query, @@ -94,6 +97,7 @@ export default class BaseModelComponent { description: `Select on ${model.singularName}`, required: true, id: 'select', + placeholder: "Example: {'columnName': true, ...}", }, { type: ComponentInputType.Number, @@ -189,6 +193,7 @@ export default class BaseModelComponent { description: 'Please fill out this query', required: true, id: 'query', + placeholder: "Example: {'columnName': 'value', ...}", }, ], returnValues: [], @@ -231,6 +236,7 @@ export default class BaseModelComponent { description: 'Please fill out this query', required: true, id: 'query', + placeholder: "Example: {'columnName': 'value', ...}", }, { type: ComponentInputType.Number, @@ -314,6 +320,7 @@ export default class BaseModelComponent { arguments: [ { id: 'json', + placeholder: "Example: {'columnName': 'value', ...}", name: 'JSON Object', description: `${model.singularName} represented as JSON`, type: ComponentInputType.JSON, @@ -365,6 +372,8 @@ export default class BaseModelComponent { { id: 'json-array', name: 'JSON Array', + placeholder: + "Example: [{'columnName': 'value', ...}, {...}]", description: 'List of models represented as JSON array', type: ComponentInputType.JSONArray, required: true, @@ -448,9 +457,11 @@ export default class BaseModelComponent { description: 'Please fill out this query', required: true, id: 'query', + placeholder: "Example: {'columnName': 'value', ...}", }, { id: 'data', + placeholder: "Example: {'columnName': 'value', ...}", name: 'Data (JSON Object)', description: `${model.singularName} represented as JSON`, type: ComponentInputType.JSON, @@ -497,10 +508,12 @@ export default class BaseModelComponent { description: 'Please fill out this query', required: true, id: 'query', + placeholder: "Example: {'columnName': 'value', ...}", }, { id: 'data', name: 'Data (JSON Object)', + placeholder: "Example: {'columnName': 'value', ...}", description: `${model.singularName} represented as JSON`, type: ComponentInputType.JSON, required: true, diff --git a/Common/Types/Workflow/Components/Condition.ts b/Common/Types/Workflow/Components/Condition.ts index b83e1d423f..50b4271567 100644 --- a/Common/Types/Workflow/Components/Condition.ts +++ b/Common/Types/Workflow/Components/Condition.ts @@ -1,4 +1,5 @@ import IconProp from '../../Icon/IconProp'; +import ComponentID from '../ComponentID'; import ComponentMetadata, { ComponentInputType, ComponentType, @@ -6,87 +7,7 @@ import ComponentMetadata, { const components: Array = [ { - id: 'if-true', - title: 'If True', - category: 'Conditions', - description: 'If the inputs are true then proceed', - iconProp: IconProp.Check, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Text, - name: 'Expression 1', - description: 'Expression 1', - required: true, - id: 'expression-1', - }, - { - type: ComponentInputType.Text, - name: 'Expression 2', - description: 'Expression 2', - required: true, - id: 'expression-2', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Yes', - description: 'If, yes then this port will be executed', - id: 'yes', - }, - ], - }, - { - id: 'if-false', - title: 'If False', - category: 'Conditions', - description: 'If the inputs are false then proceed', - iconProp: IconProp.Close, - componentType: ComponentType.Component, - arguments: [ - { - type: ComponentInputType.Text, - name: 'Expression 1', - description: 'Expression 1', - required: true, - id: 'expression-1', - }, - { - type: ComponentInputType.Text, - name: 'Expression 2', - description: 'Expression 2', - required: true, - id: 'expression-2', - }, - ], - returnValues: [], - inPorts: [ - { - title: 'In', - description: - 'Please connect components to this port for this component to work.', - id: 'in', - }, - ], - outPorts: [ - { - title: 'Yes', - description: 'If, yes then this port will be executed', - id: 'yes', - }, - ], - }, - { - id: 'if-else', + id: ComponentID.IfElse, title: 'If / Else', category: 'Conditions', description: 'Branch based on Inputs', @@ -95,17 +16,11 @@ const components: Array = [ arguments: [ { type: ComponentInputType.Text, - name: 'Expression 1', - description: 'Expression 1', + name: 'Expression', + description: 'Expression', + placeholder: 'x === y', required: true, - id: 'expression-1', - }, - { - type: ComponentInputType.Text, - name: 'Expression 2', - description: 'Expression 2', - required: true, - id: 'expression-2', + id: 'expression', }, ], returnValues: [], diff --git a/Common/Types/Workflow/Components/Email.ts b/Common/Types/Workflow/Components/Email.ts index 14f5b4bc31..daf22d3cde 100644 --- a/Common/Types/Workflow/Components/Email.ts +++ b/Common/Types/Workflow/Components/Email.ts @@ -1,4 +1,5 @@ import IconProp from '../../Icon/IconProp'; +import ComponentID from '../ComponentID'; import ComponentMetadata, { ComponentInputType, ComponentType, @@ -6,7 +7,7 @@ import ComponentMetadata, { const components: Array = [ { - id: 'send-email', + id: ComponentID.SendEmail, title: 'Send Email', category: 'Email', description: 'Send email from your workflows', @@ -15,10 +16,19 @@ const components: Array = [ arguments: [ { type: ComponentInputType.Text, - name: 'Email', - description: 'Email to send to', + name: 'From Email', + description: 'Email to send from', + placeholder: 'Name ', required: true, - id: 'email', + id: 'from', + }, + { + type: ComponentInputType.Text, + name: 'To Email', + description: 'Email to send to', + placeholder: 'email@company.com; email2@company.com; ...', + required: true, + id: 'to', }, { type: ComponentInputType.Text, @@ -28,7 +38,7 @@ const components: Array = [ id: 'subject', }, { - type: ComponentInputType.LongText, + type: ComponentInputType.HTML, name: 'Email Body', description: 'Email to send to', required: false, @@ -39,28 +49,28 @@ const components: Array = [ name: 'SMTP HOST', description: 'SMTP Host to send emails from', required: true, - id: 'smtp_host', + id: 'smtp-host', }, { type: ComponentInputType.Text, name: 'SMTP Username', description: 'SMTP Username to send emails from', required: true, - id: 'smtp_username', + id: 'smtp-username', }, { type: ComponentInputType.Password, name: 'SMTP Password', description: 'SMTP Password to send emails from', required: true, - id: 'smtp_password', + id: 'smtp-password', }, { type: ComponentInputType.Number, name: 'SMTP Port', description: 'SMTP Port to send emails from', required: true, - id: 'smtp_port', + id: 'smtp-port', }, { type: ComponentInputType.Boolean, @@ -82,10 +92,15 @@ const components: Array = [ ], outPorts: [ { - title: 'Email Sent', + title: 'Success', description: - 'Connect to this port if you want other componets to execute after the email is sent.', - id: 'out', + 'This is executed when the message is successfully posted', + id: 'success', + }, + { + title: 'Error', + description: 'This is executed when there is an error', + id: 'error', }, ], }, diff --git a/Common/Types/Workflow/Components/JSON.ts b/Common/Types/Workflow/Components/JSON.ts index 033f98b063..fa49c08266 100644 --- a/Common/Types/Workflow/Components/JSON.ts +++ b/Common/Types/Workflow/Components/JSON.ts @@ -1,4 +1,5 @@ import IconProp from '../../Icon/IconProp'; +import ComponentID from '../ComponentID'; import ComponentMetadata, { ComponentInputType, ComponentType, @@ -6,7 +7,7 @@ import ComponentMetadata, { const components: Array = [ { - id: 'json-to-text', + id: ComponentID.JsonToText, title: 'JSON to Text', category: 'JSON', description: 'Converts JSON Object to Text', @@ -54,7 +55,7 @@ const components: Array = [ ], }, { - id: 'text-to-json', + id: ComponentID.TextToJson, title: 'Text to JSON', category: 'JSON', description: 'Converts Text to JSON Object', @@ -102,7 +103,7 @@ const components: Array = [ ], }, { - id: 'json-merge', + id: ComponentID.MergeJson, title: 'Merge JSON', category: 'JSON', description: 'Merge two JSON Objects into one', @@ -148,6 +149,12 @@ const components: Array = [ 'This is executed when the JSON is successfully merged', id: 'success', }, + { + title: 'Error', + description: + 'This is executed when the JSON is not successfully merged', + id: 'error', + }, ], }, ]; diff --git a/CommonServer/Infrastructure/Queue.ts b/CommonServer/Infrastructure/Queue.ts index 5497bbd084..383a13db61 100644 --- a/CommonServer/Infrastructure/Queue.ts +++ b/CommonServer/Infrastructure/Queue.ts @@ -23,6 +23,10 @@ export default class Queue { queueName: QueueName, jobId: string ): Promise { + if (!jobId) { + return; + } + const job: Job | undefined = await this.getQueue(queueName).getJob( jobId ); diff --git a/CommonServer/Types/Workflow/Components/API/Delete.ts b/CommonServer/Types/Workflow/Components/API/Delete.ts new file mode 100644 index 0000000000..1d9f253f75 --- /dev/null +++ b/CommonServer/Types/Workflow/Components/API/Delete.ts @@ -0,0 +1,73 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import APIComponents from 'Common/Types/Workflow/Components/API'; +import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; +import { ApiComponentUtils } from './Utils'; +import URL from 'Common/Types/API/URL'; +import Dictionary from 'Common/Types/Dictionary'; +import HTTPResponse from 'Common/Types/API/HTTPResponse'; +import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; +import APIException from 'Common/Types/Exception/ApiException'; + +export default class ApiDelete extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = APIComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.ApiDelete; + } + ); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const result: { args: JSONObject; successPort: Port; errorPort: Port } = + ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); + + let apiResult: HTTPResponse | HTTPErrorResponse | null = + null; + + try { + apiResult = await API.delete( + args['url'] as URL, + args['request-body'] as JSONObject, + args['request-headers'] as Dictionary + ); + + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(err), + executePort: result.successPort, + }); + } + + if (apiResult) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } + + throw options.onError( + new APIException('Something wrong happened.') + ); + } + } +} diff --git a/CommonServer/Types/Workflow/Components/API/Get.ts b/CommonServer/Types/Workflow/Components/API/Get.ts new file mode 100644 index 0000000000..d1a18ff63a --- /dev/null +++ b/CommonServer/Types/Workflow/Components/API/Get.ts @@ -0,0 +1,73 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import APIComponents from 'Common/Types/Workflow/Components/API'; +import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; +import { ApiComponentUtils } from './Utils'; +import URL from 'Common/Types/API/URL'; +import Dictionary from 'Common/Types/Dictionary'; +import HTTPResponse from 'Common/Types/API/HTTPResponse'; +import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; +import APIException from 'Common/Types/Exception/ApiException'; + +export default class ApiGet extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = APIComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.ApiGet; + } + ); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const result: { args: JSONObject; successPort: Port; errorPort: Port } = + ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); + + let apiResult: HTTPResponse | HTTPErrorResponse | null = + null; + + try { + apiResult = await API.get( + args['url'] as URL, + args['request-body'] as JSONObject, + args['request-headers'] as Dictionary + ); + + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(err), + executePort: result.successPort, + }); + } + + if (apiResult) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } + + throw options.onError( + new APIException('Something wrong happened.') + ); + } + } +} diff --git a/CommonServer/Types/Workflow/Components/API/Post.ts b/CommonServer/Types/Workflow/Components/API/Post.ts new file mode 100644 index 0000000000..c5c0c68121 --- /dev/null +++ b/CommonServer/Types/Workflow/Components/API/Post.ts @@ -0,0 +1,73 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import APIComponents from 'Common/Types/Workflow/Components/API'; +import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; +import { ApiComponentUtils } from './Utils'; +import URL from 'Common/Types/API/URL'; +import Dictionary from 'Common/Types/Dictionary'; +import HTTPResponse from 'Common/Types/API/HTTPResponse'; +import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; +import APIException from 'Common/Types/Exception/ApiException'; + +export default class ApiPost extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = APIComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.ApiPost; + } + ); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const result: { args: JSONObject; successPort: Port; errorPort: Port } = + ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); + + let apiResult: HTTPResponse | HTTPErrorResponse | null = + null; + + try { + apiResult = await API.post( + args['url'] as URL, + args['request-body'] as JSONObject, + args['request-headers'] as Dictionary + ); + + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(err), + executePort: result.successPort, + }); + } + + if (apiResult) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } + + throw options.onError( + new APIException('Something wrong happened.') + ); + } + } +} diff --git a/CommonServer/Types/Workflow/Components/API/Put.ts b/CommonServer/Types/Workflow/Components/API/Put.ts new file mode 100644 index 0000000000..031ff40607 --- /dev/null +++ b/CommonServer/Types/Workflow/Components/API/Put.ts @@ -0,0 +1,73 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import APIComponents from 'Common/Types/Workflow/Components/API'; +import API from 'Common/Utils/API'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; +import { ApiComponentUtils } from './Utils'; +import URL from 'Common/Types/API/URL'; +import Dictionary from 'Common/Types/Dictionary'; +import HTTPResponse from 'Common/Types/API/HTTPResponse'; +import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; +import APIException from 'Common/Types/Exception/ApiException'; + +export default class ApiPut extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = APIComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.ApiPut; + } + ); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const result: { args: JSONObject; successPort: Port; errorPort: Port } = + ApiComponentUtils.sanitizeArgs(this.getMetadata(), args, options); + + let apiResult: HTTPResponse | HTTPErrorResponse | null = + null; + + try { + apiResult = await API.put( + args['url'] as URL, + args['request-body'] as JSONObject, + args['request-headers'] as Dictionary + ); + + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } catch (err) { + if (err instanceof HTTPErrorResponse) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(err), + executePort: result.successPort, + }); + } + + if (apiResult) { + return Promise.resolve({ + returnValues: ApiComponentUtils.getReturnValues(apiResult), + executePort: result.successPort, + }); + } + + throw options.onError( + new APIException('Something wrong happened.') + ); + } + } +} diff --git a/CommonServer/Types/Workflow/Components/API/Utils.ts b/CommonServer/Types/Workflow/Components/API/Utils.ts new file mode 100644 index 0000000000..bf079d0b62 --- /dev/null +++ b/CommonServer/Types/Workflow/Components/API/Utils.ts @@ -0,0 +1,84 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import { RunOptions } from '../../ComponentCode'; +import URL from 'Common/Types/API/URL'; +import HTTPResponse from 'Common/Types/API/HTTPResponse'; +import HTTPErrorResponse from 'Common/Types/API/HTTPErrorResponse'; + +export class ApiComponentUtils { + public static getReturnValues( + response: HTTPResponse | HTTPErrorResponse + ): JSONObject { + if (response instanceof HTTPErrorResponse) { + return { + 'response-status': response.statusCode, + 'response-body': response.jsonData, + 'response-headers': response.headers, + error: response.message || 'Server Error.', + }; + } + + return { + 'response-status': response.statusCode, + 'response-body': response.jsonData, + 'response-headers': response.headers, + error: null, + }; + } + + public static sanitizeArgs( + metadata: ComponentMetadata, + args: JSONObject, + options: RunOptions + ): { args: JSONObject; successPort: Port; errorPort: Port } { + const successPort: Port | undefined = metadata.outPorts.find( + (p: Port) => { + return p.id === 'success'; + } + ); + + if (!successPort) { + throw options.onError( + new BadDataException('Success port not found') + ); + } + + const errorPort: Port | undefined = metadata.outPorts.find( + (p: Port) => { + return p.id === 'error'; + } + ); + + if (!errorPort) { + throw options.onError(new BadDataException('Error port not found')); + } + + if (args['request-body'] && typeof args['request-body'] === 'string') { + args['request-body'] = JSON.parse(args['request-body'] as string); + } + + if ( + args['request-headers'] && + typeof args['request-headers'] === 'string' + ) { + args['request-headers'] = JSON.parse( + args['request-headers'] as string + ); + } + + if (!args['url']) { + throw options.onError(new BadDataException('URL not found')); + } + + if (args['url'] && typeof args['url'] !== 'string') { + throw options.onError( + new BadDataException('URL is not type of string') + ); + } + + args['url'] = URL.fromString(args['url'] as string); + + return { args, successPort, errorPort }; + } +} diff --git a/CommonServer/Types/Workflow/Components/CreateManyBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts similarity index 96% rename from CommonServer/Types/Workflow/Components/CreateManyBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts index dabbfa0083..f3ea78acfc 100644 --- a/CommonServer/Types/Workflow/Components/CreateManyBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/CreateManyBaseModel.ts @@ -1,8 +1,8 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; import { JSONObject } from 'Common/Types/JSON'; diff --git a/CommonServer/Types/Workflow/Components/CreateOneBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts similarity index 96% rename from CommonServer/Types/Workflow/Components/CreateOneBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts index 0255355577..7bc088e761 100644 --- a/CommonServer/Types/Workflow/Components/CreateOneBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/CreateOneBaseModel.ts @@ -1,8 +1,8 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; import { JSONObject } from 'Common/Types/JSON'; diff --git a/CommonServer/Types/Workflow/Components/DeleteManyBaseMoidel.ts b/CommonServer/Types/Workflow/Components/BaseModel/DeleteManyBaseMoidel.ts similarity index 96% rename from CommonServer/Types/Workflow/Components/DeleteManyBaseMoidel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/DeleteManyBaseMoidel.ts index 5fd7a1bcd2..b4f6f1d7a8 100644 --- a/CommonServer/Types/Workflow/Components/DeleteManyBaseMoidel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/DeleteManyBaseMoidel.ts @@ -1,12 +1,12 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; import { JSONObject } from 'Common/Types/JSON'; -import Query from '../../Database/Query'; +import Query from '../../../Database/Query'; import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; import PositiveNumber from 'Common/Types/PositiveNumber'; diff --git a/CommonServer/Types/Workflow/Components/DeleteOneBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts similarity index 95% rename from CommonServer/Types/Workflow/Components/DeleteOneBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts index 82576afb4c..8a1aa7f412 100644 --- a/CommonServer/Types/Workflow/Components/DeleteOneBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/DeleteOneBaseModel.ts @@ -1,12 +1,12 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; import { JSONObject } from 'Common/Types/JSON'; -import Query from '../../Database/Query'; +import Query from '../../../Database/Query'; export default class DeleteOneBaseModel< TBaseModel extends BaseModel diff --git a/CommonServer/Types/Workflow/Components/FindManyBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts similarity index 95% rename from CommonServer/Types/Workflow/Components/FindManyBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts index 357e985a5f..6836e83f3a 100644 --- a/CommonServer/Types/Workflow/Components/FindManyBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/FindManyBaseModel.ts @@ -1,14 +1,14 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; import { JSONObject } from 'Common/Types/JSON'; -import Query from '../../Database/Query'; +import Query from '../../../Database/Query'; import JSONFunctions from 'Common/Types/JSONFunctions'; -import Select from '../../Database/Select'; +import Select from '../../../Database/Select'; import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; import PositiveNumber from 'Common/Types/PositiveNumber'; diff --git a/CommonServer/Types/Workflow/Components/FindOneBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts similarity index 95% rename from CommonServer/Types/Workflow/Components/FindOneBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts index 3608c8a18f..af4f912a77 100644 --- a/CommonServer/Types/Workflow/Components/FindOneBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/FindOneBaseModel.ts @@ -1,14 +1,14 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; import { JSONObject } from 'Common/Types/JSON'; -import Query from '../../Database/Query'; +import Query from '../../../Database/Query'; import JSONFunctions from 'Common/Types/JSONFunctions'; -import Select from '../../Database/Select'; +import Select from '../../../Database/Select'; export default class FindOneBaseModel< TBaseModel extends BaseModel diff --git a/CommonServer/Types/Workflow/Components/OnCreateBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/OnCreateBaseModel.ts similarity index 83% rename from CommonServer/Types/Workflow/Components/OnCreateBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/OnCreateBaseModel.ts index 6058bc9092..dc85edfecf 100644 --- a/CommonServer/Types/Workflow/Components/OnCreateBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/OnCreateBaseModel.ts @@ -1,5 +1,5 @@ import BaseModel from 'Common/Models/BaseModel'; -import DatabaseService from '../../../Services/DatabaseService'; +import DatabaseService from '../../../../Services/DatabaseService'; import OnTriggerBaseModel from './OnTriggerBaseModel'; export default class OnCreateBaseModel< diff --git a/CommonServer/Types/Workflow/Components/OnDeleteBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/OnDeleteBaseModel.ts similarity index 83% rename from CommonServer/Types/Workflow/Components/OnDeleteBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/OnDeleteBaseModel.ts index 8a55a15860..b009a895c0 100644 --- a/CommonServer/Types/Workflow/Components/OnDeleteBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/OnDeleteBaseModel.ts @@ -1,5 +1,5 @@ import BaseModel from 'Common/Models/BaseModel'; -import DatabaseService from '../../../Services/DatabaseService'; +import DatabaseService from '../../../../Services/DatabaseService'; import OnTriggerBaseModel from './OnTriggerBaseModel'; export default class OnDeleteBaseModel< diff --git a/CommonServer/Types/Workflow/Components/OnTriggerBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts similarity index 90% rename from CommonServer/Types/Workflow/Components/OnTriggerBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts index 385276af5c..585f7f9cf5 100644 --- a/CommonServer/Types/Workflow/Components/OnTriggerBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/OnTriggerBaseModel.ts @@ -2,16 +2,16 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ObjectID from 'Common/Types/ObjectID'; import ComponentMetadata from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import { ExpressRequest, ExpressResponse } from '../../../Utils/Express'; -import Response from '../../../Utils/Response'; -import TriggerCode, { ExecuteWorkflowType, InitProps } from '../TriggerCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import { ExpressRequest, ExpressResponse } from '../../../../Utils/Express'; +import Response from '../../../../Utils/Response'; +import TriggerCode, { ExecuteWorkflowType, InitProps } from '../../TriggerCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; -import WorkflowService from '../../../Services/WorkflowService'; +import WorkflowService from '../../../../Services/WorkflowService'; import LIMIT_MAX from 'Common/Types/Database/LimitMax'; import Workflow from 'Model/Models/Workflow'; -import ClusterKeyAuthorization from '../../../Middleware/ClusterKeyAuthorization'; +import ClusterKeyAuthorization from '../../../../Middleware/ClusterKeyAuthorization'; export default class OnTriggerBaseModel< TBaseModel extends BaseModel diff --git a/CommonServer/Types/Workflow/Components/OnUpdateBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/OnUpdateBaseModel.ts similarity index 83% rename from CommonServer/Types/Workflow/Components/OnUpdateBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/OnUpdateBaseModel.ts index 2726d1f1a2..86450da5d6 100644 --- a/CommonServer/Types/Workflow/Components/OnUpdateBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/OnUpdateBaseModel.ts @@ -1,5 +1,5 @@ import BaseModel from 'Common/Models/BaseModel'; -import DatabaseService from '../../../Services/DatabaseService'; +import DatabaseService from '../../../../Services/DatabaseService'; import OnTriggerBaseModel from './OnTriggerBaseModel'; export default class OnUpdateBaseModel< diff --git a/CommonServer/Types/Workflow/Components/UpdateManyBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts similarity index 96% rename from CommonServer/Types/Workflow/Components/UpdateManyBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts index 1d40b4d081..3a74e2328f 100644 --- a/CommonServer/Types/Workflow/Components/UpdateManyBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/UpdateManyBaseModel.ts @@ -1,12 +1,12 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; import { JSONObject } from 'Common/Types/JSON'; -import Query from '../../Database/Query'; +import Query from '../../../Database/Query'; import QueryDeepPartialEntity from 'Common/Types/Database/PartialEntity'; import { LIMIT_PER_PROJECT } from 'Common/Types/Database/LimitMax'; import PositiveNumber from 'Common/Types/PositiveNumber'; diff --git a/CommonServer/Types/Workflow/Components/UpdateOneBaseModel.ts b/CommonServer/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts similarity index 96% rename from CommonServer/Types/Workflow/Components/UpdateOneBaseModel.ts rename to CommonServer/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts index 4310003923..115ad0abc3 100644 --- a/CommonServer/Types/Workflow/Components/UpdateOneBaseModel.ts +++ b/CommonServer/Types/Workflow/Components/BaseModel/UpdateOneBaseModel.ts @@ -1,12 +1,12 @@ import BaseModel from 'Common/Models/BaseModel'; import BadDataException from 'Common/Types/Exception/BadDataException'; import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; -import DatabaseService from '../../../Services/DatabaseService'; -import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import DatabaseService from '../../../../Services/DatabaseService'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; import BaseModelComponents from 'Common/Types/Workflow/Components/BaseModel'; import Text from 'Common/Types/Text'; import { JSONObject } from 'Common/Types/JSON'; -import Query from '../../Database/Query'; +import Query from '../../../Database/Query'; import QueryDeepPartialEntity from 'Common/Types/Database/PartialEntity'; export default class UpdateOneBaseModel< diff --git a/CommonServer/Types/Workflow/Components/Conditions/IfElse.ts b/CommonServer/Types/Workflow/Components/Conditions/IfElse.ts new file mode 100644 index 0000000000..6dde987196 --- /dev/null +++ b/CommonServer/Types/Workflow/Components/Conditions/IfElse.ts @@ -0,0 +1,98 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject, JSONValue } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import Components from 'Common/Types/Workflow/Components/Condition'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; +import VM, { VMScript } from 'vm2'; + +export default class IfElse extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = Components.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.IfElse; + } + ); + + if (!Component) { + throw new BadDataException( + 'Custom JavaScirpt Component not found.' + ); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const yesPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'yes'; + } + ); + + if (!yesPort) { + throw options.onError(new BadDataException('Yes port not found')); + } + + const noPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'no'; + } + ); + + if (!noPort) { + throw options.onError(new BadDataException('No port not found')); + } + + try { + // Set timeout + // Inject args + // Inject dependencies + + const vm: VM.NodeVM = new VM.NodeVM({ + timeout: 5000, + allowAsync: true, + sandbox: { + args: args['arguments'], + console: { + log: (logValue: JSONValue) => { + options.log(logValue); + }, + }, + }, + }); + + const script: VMScript = new VMScript( + `module.exports = async function() { return ${ + (args['expression'] as string) || '' + } }` + ).compile(); + + const functionToRun: any = vm.run(script); + + const returnVal: any = await functionToRun(); + + if (returnVal) { + return { + returnValues: {}, + executePort: yesPort, + }; + } + return { + returnValues: {}, + executePort: noPort, + }; + } catch (err: any) { + options.log('Error running script'); + options.log( + err.message ? err.message : JSON.stringify(err, null, 2) + ); + throw options.onError(err); + } + } +} diff --git a/CommonServer/Types/Workflow/Components/Email.ts b/CommonServer/Types/Workflow/Components/Email.ts new file mode 100644 index 0000000000..81f83b9c7e --- /dev/null +++ b/CommonServer/Types/Workflow/Components/Email.ts @@ -0,0 +1,154 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import Components from 'Common/Types/Workflow/Components/Email'; +import ComponentCode, { RunOptions, RunReturnType } from '../ComponentCode'; +import nodemailer, { Transporter } from 'nodemailer'; + +export default class Email extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = Components.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.SendEmail; + } + ); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'success'; + } + ); + + if (!successPort) { + throw options.onError( + new BadDataException('Success port not found') + ); + } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'error'; + } + ); + + if (!errorPort) { + throw options.onError(new BadDataException('Error port not found')); + } + + if (!args['to']) { + throw options.onError(new BadDataException('to not found')); + } + + if (args['to'] && typeof args['to'] !== 'string') { + throw options.onError( + new BadDataException('to is not type of string') + ); + } + + if (!args['from']) { + throw options.onError(new BadDataException('from not found')); + } + + if (args['from'] && typeof args['from'] !== 'string') { + throw options.onError( + new BadDataException('from is not type of string') + ); + } + + if (!args['smtp-username']) { + throw options.onError(new BadDataException('email not found')); + } + + if ( + args['smtp-username'] && + typeof args['smtp-username'] !== 'string' + ) { + throw options.onError( + new BadDataException('smtp-username is not type of string') + ); + } + + if (!args['smtp-password']) { + throw options.onError(new BadDataException('email not found')); + } + + if ( + args['smtp-password'] && + typeof args['smtp-password'] !== 'string' + ) { + throw options.onError( + new BadDataException('smtp-username is not type of string') + ); + } + + if (!args['smtp-host']) { + throw options.onError(new BadDataException('email not found')); + } + + if (args['smtp-host'] && typeof args['smtp-host'] !== 'string') { + throw options.onError( + new BadDataException('smtp-host is not type of string') + ); + } + + if (!args['smtp-port']) { + throw options.onError(new BadDataException('email not found')); + } + + if (args['smtp-port'] && typeof args['smtp-port'] === 'string') { + args['smtp-port'] = parseInt(args['smtp-port']); + } + + if (args['smtp-port'] && typeof args['smtp-port'] !== 'number') { + throw options.onError( + new BadDataException('smtp-host is not type of number') + ); + } + + try { + const mailer: Transporter = nodemailer.createTransport({ + host: args['smtp-host']?.toString(), + port: args['smtp-port'] as number, + secure: Boolean(args['secure']), + auth: { + user: args['smtp-username'] as string, + pass: args['smtp-password'] as string, + }, + }); + + await mailer.sendMail({ + from: args['from'].toString(), + to: args['to'].toString(), + subject: args['subject']?.toString() || '', + html: args['email-body']?.toString() || '', + }); + + options.log('Email sent.'); + + return Promise.resolve({ + returnValues: {}, + executePort: successPort, + }); + } catch (err) { + options.log(err); + return Promise.resolve({ + returnValues: {}, + executePort: successPort, + }); + } + } +} diff --git a/CommonServer/Types/Workflow/Components/Index.ts b/CommonServer/Types/Workflow/Components/Index.ts index d7ced7914a..7a98ff1c95 100644 --- a/CommonServer/Types/Workflow/Components/Index.ts +++ b/CommonServer/Types/Workflow/Components/Index.ts @@ -8,18 +8,27 @@ import JavaScirptCode from './JavaScript'; import BaseModelServices from '../../../Services/Index'; import BaseModel from 'Common/Models/BaseModel'; import Text from 'Common/Types/Text'; -import OnCreateBaseModel from './OnCreateBaseModel'; -import CreateOneBaseModel from './CreateOneBaseModel'; -import CreateManyBaseModel from './CreateManyBaseModel'; -import FindOneBaseModel from './FindOneBaseModel'; -import FindManyBaseModel from './FindManyBaseModel'; -import OnUpdateBaseModel from './OnUpdateBaseModel'; -import UpdateOneBaseModel from './UpdateOneBaseModel'; -import UpdateManyBaseModel from './UpdateManyBaseModel'; -import OnDeleteBaseModel from './OnDeleteBaseModel'; -import DeleteOneBaseModel from './DeleteOneBaseModel'; -import DeleteManyBaseModel from './DeleteManyBaseMoidel'; +import OnCreateBaseModel from './BaseModel/OnCreateBaseModel'; +import CreateOneBaseModel from './BaseModel/CreateOneBaseModel'; +import CreateManyBaseModel from './BaseModel/CreateManyBaseModel'; +import FindOneBaseModel from './BaseModel/FindOneBaseModel'; +import FindManyBaseModel from './BaseModel/FindManyBaseModel'; +import OnUpdateBaseModel from './BaseModel/OnUpdateBaseModel'; +import UpdateOneBaseModel from './BaseModel/UpdateOneBaseModel'; +import UpdateManyBaseModel from './BaseModel/UpdateManyBaseModel'; +import OnDeleteBaseModel from './BaseModel/OnDeleteBaseModel'; +import DeleteOneBaseModel from './BaseModel/DeleteOneBaseModel'; +import DeleteManyBaseModel from './BaseModel/DeleteManyBaseMoidel'; import ManualTrigger from './Manual'; +import JsonToText from './JSON/JsonToText'; +import MergeJSON from './JSON/MergeJson'; +import TextToJSON from './JSON/TextToJson'; +import ApiGet from './API/Get'; +import ApiDelete from './API/Delete'; +import ApiPost from './API/Post'; +import ApiPut from './API/Put'; +import Email from './Email'; +import IfElse from './Conditions/IfElse'; const Components: Dictionary = { [ComponentID.Webhook]: new WebhookTrigger(), @@ -27,6 +36,15 @@ const Components: Dictionary = { [ComponentID.Schedule]: new Schedule(), [ComponentID.JavaScriptCode]: new JavaScirptCode(), [ComponentID.Manual]: new ManualTrigger(), + [ComponentID.JsonToText]: new JsonToText(), + [ComponentID.MergeJson]: new MergeJSON(), + [ComponentID.TextToJson]: new TextToJSON(), + [ComponentID.ApiGet]: new ApiGet(), + [ComponentID.ApiPost]: new ApiPost(), + [ComponentID.ApiDelete]: new ApiDelete(), + [ComponentID.ApiPut]: new ApiPut(), + [ComponentID.SendEmail]: new Email(), + [ComponentID.IfElse]: new IfElse(), }; for (const baseModelService of BaseModelServices) { diff --git a/CommonServer/Types/Workflow/Components/JSON/JsonToText.ts b/CommonServer/Types/Workflow/Components/JSON/JsonToText.ts new file mode 100644 index 0000000000..1b9424a5c5 --- /dev/null +++ b/CommonServer/Types/Workflow/Components/JSON/JsonToText.ts @@ -0,0 +1,83 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import JSONComponents from 'Common/Types/Workflow/Components/JSON'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; + +export default class JsonToText extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = JSONComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.JsonToText; + } + ); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'success'; + } + ); + + if (!successPort) { + throw options.onError( + new BadDataException('Success port not found') + ); + } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'error'; + } + ); + + if (!errorPort) { + throw options.onError(new BadDataException('Error port not found')); + } + + if (!args['json']) { + throw options.onError(new BadDataException('JSON is undefined.')); + } + + if (typeof args['json'] === 'string') { + args['json'] = JSON.parse(args['json'] as string); + } + + if (typeof args['json'] !== 'object') { + throw options.onError( + new BadDataException('JSON is should be of type object.') + ); + } + + try { + const returnValue: string = JSON.stringify( + args['json'] as JSONObject + ); + return Promise.resolve({ + returnValues: { + text: returnValue, + }, + executePort: successPort, + }); + } catch (err) { + options.log('JSON is not in the correct format.'); + return Promise.resolve({ + returnValues: {}, + executePort: errorPort, + }); + } + } +} diff --git a/CommonServer/Types/Workflow/Components/JSON/MergeJson.ts b/CommonServer/Types/Workflow/Components/JSON/MergeJson.ts new file mode 100644 index 0000000000..d765d22ecc --- /dev/null +++ b/CommonServer/Types/Workflow/Components/JSON/MergeJson.ts @@ -0,0 +1,89 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import JSONComponents from 'Common/Types/Workflow/Components/JSON'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; + +export default class MergeJSON extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = JSONComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.MergeJson; + } + ); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'success'; + } + ); + + if (!successPort) { + throw options.onError( + new BadDataException('Success port not found') + ); + } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'error'; + } + ); + + if (!errorPort) { + throw options.onError(new BadDataException('Error port not found')); + } + + if (!args['json1']) { + throw options.onError(new BadDataException('JSON1 is undefined.')); + } + + if (typeof args['json1'] === 'string') { + args['json1'] = JSON.parse(args['json1'] as string); + } + + if (typeof args['json1'] !== 'object') { + throw options.onError( + new BadDataException('JSON1 is should be of type object.') + ); + } + + if (!args['json2']) { + throw options.onError(new BadDataException('JSON2 is undefined.')); + } + + if (typeof args['json2'] === 'string') { + args['json2'] = JSON.parse(args['json2'] as string); + } + + if (typeof args['json2'] !== 'object') { + throw options.onError( + new BadDataException('JSON2 is should be of type object.') + ); + } + + return Promise.resolve({ + returnValues: { + json: { + ...(args['json1'] as JSONObject), + ...(args['json2'] as JSONObject), + }, + }, + executePort: successPort, + }); + } +} diff --git a/CommonServer/Types/Workflow/Components/JSON/TextToJson.ts b/CommonServer/Types/Workflow/Components/JSON/TextToJson.ts new file mode 100644 index 0000000000..162111d29f --- /dev/null +++ b/CommonServer/Types/Workflow/Components/JSON/TextToJson.ts @@ -0,0 +1,77 @@ +import BadDataException from 'Common/Types/Exception/BadDataException'; +import { JSONObject } from 'Common/Types/JSON'; +import ComponentMetadata, { Port } from 'Common/Types/Workflow/Component'; +import ComponentID from 'Common/Types/Workflow/ComponentID'; +import JSONComponents from 'Common/Types/Workflow/Components/JSON'; +import ComponentCode, { RunOptions, RunReturnType } from '../../ComponentCode'; + +export default class TextToJSON extends ComponentCode { + public constructor() { + super(); + + const Component: ComponentMetadata | undefined = JSONComponents.find( + (i: ComponentMetadata) => { + return i.id === ComponentID.TextToJson; + } + ); + + if (!Component) { + throw new BadDataException('Component not found.'); + } + + this.setMetadata(Component); + } + + public override async run( + args: JSONObject, + options: RunOptions + ): Promise { + const successPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'success'; + } + ); + + if (!successPort) { + throw options.onError( + new BadDataException('Success port not found') + ); + } + + const errorPort: Port | undefined = this.getMetadata().outPorts.find( + (p: Port) => { + return p.id === 'error'; + } + ); + + if (!errorPort) { + throw options.onError(new BadDataException('Error port not found')); + } + + if (!args['text']) { + throw options.onError(new BadDataException('text is undefined.')); + } + + if (typeof args['text'] !== 'string') { + throw options.onError( + new BadDataException('text is should be of type string.') + ); + } + + try { + const returnValue: JSONObject = JSON.parse(args['text'] as string); + return Promise.resolve({ + returnValues: { + json: returnValue, + }, + executePort: successPort, + }); + } catch (err) { + options.log('text is not in the correct format.'); + return Promise.resolve({ + returnValues: {}, + executePort: errorPort, + }); + } + } +} diff --git a/CommonServer/package-lock.json b/CommonServer/package-lock.json index 7df174cfb3..cd3e373929 100644 --- a/CommonServer/package-lock.json +++ b/CommonServer/package-lock.json @@ -16,6 +16,7 @@ "@types/ejs": "^3.1.1", "@types/gridfs-stream": "^0.5.35", "@types/json2csv": "^5.0.3", + "@types/nodemailer": "^6.4.7", "airtable": "^0.11.3", "axios": "^1.3.3", "bullmq": "^3.6.6", @@ -3872,6 +3873,14 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" }, + "node_modules/@types/nodemailer": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz", + "integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/pg": { "version": "8.6.1", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", @@ -13102,6 +13111,14 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.45.tgz", "integrity": "sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==" }, + "@types/nodemailer": { + "version": "6.4.7", + "resolved": "https://registry.npmjs.org/@types/nodemailer/-/nodemailer-6.4.7.tgz", + "integrity": "sha512-f5qCBGAn/f0qtRcd4SEn88c8Fp3Swct1731X4ryPKqS61/A3LmmzN8zaEz7hneJvpjFbUUgY7lru/B/7ODTazg==", + "requires": { + "@types/node": "*" + } + }, "@types/pg": { "version": "8.6.1", "resolved": "https://registry.npmjs.org/@types/pg/-/pg-8.6.1.tgz", diff --git a/CommonServer/package.json b/CommonServer/package.json index 195773e0f9..5137e98faa 100644 --- a/CommonServer/package.json +++ b/CommonServer/package.json @@ -18,6 +18,7 @@ "@types/ejs": "^3.1.1", "@types/gridfs-stream": "^0.5.35", "@types/json2csv": "^5.0.3", + "@types/nodemailer": "^6.4.7", "airtable": "^0.11.3", "axios": "^1.3.3", "bullmq": "^3.6.6", diff --git a/CommonUI/src/Components/CodeEditor/CodeEditor.tsx b/CommonUI/src/Components/CodeEditor/CodeEditor.tsx index 76bc029f51..006204c4ce 100644 --- a/CommonUI/src/Components/CodeEditor/CodeEditor.tsx +++ b/CommonUI/src/Components/CodeEditor/CodeEditor.tsx @@ -31,6 +31,7 @@ const CodeEditor: FunctionComponent = ( let className: string = ''; const [placeholder, setPlaceholder] = useState(''); + const [helpText, setHelpText] = useState(''); useEffect(() => { if (props.type === CodeType.Markdown) { @@ -50,7 +51,7 @@ const CodeEditor: FunctionComponent = ( } if (props.type === CodeType.JSON) { - setPlaceholder(`// ${props.placeholder}. This is in JSON.`); + setHelpText(`${props.placeholder}`); } if (props.type === CodeType.CSS) { @@ -106,6 +107,13 @@ const CodeEditor: FunctionComponent = ( props.onFocus && props.onFocus(); }} > + {helpText && ( +

+ {' '} + {helpText}{' '} +

+ )} + = []; - argumentContent = deepFind( - storageMap as any, - argumentContent as any - ); + const regex: RegExp = /{{(.*?)}}/g; // Find all matches of the regular expression and capture the word between the braces {{x}} => x + + let match: RegExpExecArray | null = null; + + while ((match = regex.exec(argumentContentCopy)) !== null) { + if (match[1]) { + variablesInArgument.push(match[1]); + } + } + + for (const variable of variablesInArgument) { + const value: string = deepFind( + storageMap as any, + variable as any + ); + + argumentContentCopy = argumentContentCopy.replace( + '{{' + variable + '}}', + value + ); + } + + argumentContent = argumentContentCopy; } argumentObj[argument.id] = argumentContent;