diff --git a/packages/server/package.json b/packages/server/package.json index 87a91da198..1d96038355 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -47,6 +47,7 @@ "@sendgrid/mail": "^7.1.1", "@sentry/node": "^5.19.2", "aws-sdk": "^2.767.0", + "axios": "^0.21.0", "bcryptjs": "^2.4.3", "chmodr": "^1.2.0", "csvtojson": "^2.0.10", diff --git a/packages/server/src/api/controllers/webhook.js b/packages/server/src/api/controllers/webhook.js index c759105fed..3259a89341 100644 --- a/packages/server/src/api/controllers/webhook.js +++ b/packages/server/src/api/controllers/webhook.js @@ -81,10 +81,9 @@ exports.trigger = async ctx => { const db = new CouchDB(ctx.params.instance) const webhook = await db.get(ctx.params.id) // validate against the schema - if (!webhook.bodySchema) { - ctx.throw(400, "Webhook has not been fully configured, no schema created") + if (webhook.bodySchema) { + validate(ctx.request.body, webhook.bodySchema) } - validate(ctx.request.body, webhook.bodySchema) const target = await db.get(webhook.action.target) if (webhook.action.type === exports.WebhookType.AUTOMATION) { // trigger with both the pure request and then expand it diff --git a/packages/server/src/automations/actions.js b/packages/server/src/automations/actions.js index 13df04e379..b2efae7809 100644 --- a/packages/server/src/automations/actions.js +++ b/packages/server/src/automations/actions.js @@ -3,6 +3,7 @@ const createRow = require("./steps/createRow") const updateRow = require("./steps/updateRow") const deleteRow = require("./steps/deleteRow") const createUser = require("./steps/createUser") +const outgoingWebhook = require("./steps/outgoingWebhook") const environment = require("../environment") const download = require("download") const fetch = require("node-fetch") @@ -21,6 +22,7 @@ const BUILTIN_ACTIONS = { UPDATE_ROW: updateRow.run, DELETE_ROW: deleteRow.run, CREATE_USER: createUser.run, + OUTGOING_WEBHOOK: outgoingWebhook.run, } const BUILTIN_DEFINITIONS = { SEND_EMAIL: sendEmail.definition, @@ -28,6 +30,7 @@ const BUILTIN_DEFINITIONS = { UPDATE_ROW: updateRow.definition, DELETE_ROW: deleteRow.definition, CREATE_USER: createUser.definition, + OUTGOING_WEBHOOK: outgoingWebhook.definition, } let AUTOMATION_BUCKET = environment.AUTOMATION_BUCKET diff --git a/packages/server/src/automations/steps/createRow.js b/packages/server/src/automations/steps/createRow.js index 29fbbbd7ec..f3049974a5 100644 --- a/packages/server/src/automations/steps/createRow.js +++ b/packages/server/src/automations/steps/createRow.js @@ -6,7 +6,7 @@ const usage = require("../../utilities/usageQuota") module.exports.definition = { name: "Create Row", tagline: "Create a {{inputs.enriched.table.name}} row", - icon: "ri-save-3-fill", + icon: "ri-save-3-line", description: "Add a row to your database", type: "ACTION", stepId: "CREATE_ROW", diff --git a/packages/server/src/automations/steps/createUser.js b/packages/server/src/automations/steps/createUser.js index f0bea286d7..2aee8e8d3a 100644 --- a/packages/server/src/automations/steps/createUser.js +++ b/packages/server/src/automations/steps/createUser.js @@ -6,7 +6,7 @@ const usage = require("../../utilities/usageQuota") module.exports.definition = { description: "Create a new user", tagline: "Create user {{inputs.username}}", - icon: "ri-user-add-fill", + icon: "ri-user-add-line", name: "Create User", type: "ACTION", stepId: "CREATE_USER", diff --git a/packages/server/src/automations/steps/delay.js b/packages/server/src/automations/steps/delay.js index bf4c1095b1..f653d0a980 100644 --- a/packages/server/src/automations/steps/delay.js +++ b/packages/server/src/automations/steps/delay.js @@ -2,7 +2,7 @@ let { wait } = require("../../utilities") module.exports.definition = { name: "Delay", - icon: "ri-time-fill", + icon: "ri-time-line", tagline: "Delay for {{inputs.time}} milliseconds", description: "Delay the automation until an amount of time has passed", stepId: "DELAY", diff --git a/packages/server/src/automations/steps/outgoingWebhook.js b/packages/server/src/automations/steps/outgoingWebhook.js new file mode 100644 index 0000000000..989e4231f8 --- /dev/null +++ b/packages/server/src/automations/steps/outgoingWebhook.js @@ -0,0 +1,96 @@ +const axios = require("axios") + +const RequestType = { + POST: "POST", + GET: "GET", + PUT: "PUT", + DELETE: "DELETE", + PATCH: "PATCH", +} + +const BODY_REQUESTS = [RequestType.POST, RequestType.PUT, RequestType.PATCH] + +/** + * Note, there is some functionality in this that is not currently exposed as it + * is complex and maybe better to be opinionated here. + * GET/DELETE requests cannot handle body elements so they will not be sent if configured. + */ + +module.exports.definition = { + name: "Outgoing webhook", + tagline: "Send a {{inputs.requestMethod}} request", + icon: "ri-send-plane-line", + description: "Send a request of specified method to a URL", + type: "ACTION", + stepId: "OUTGOING_WEBHOOK", + inputs: { + requestMethod: "POST", + url: "http://", + requestBody: "{}", + }, + schema: { + inputs: { + properties: { + requestMethod: { + type: "string", + enum: Object.values(RequestType), + title: "Request method", + }, + url: { + type: "string", + title: "URL", + }, + requestBody: { + type: "string", + title: "JSON Body", + customType: "wide", + }, + }, + required: ["requestMethod", "url"], + }, + outputs: { + properties: { + response: { + type: "object", + description: "The response from the webhook", + }, + success: { + type: "boolean", + description: "Whether the action was successful", + }, + }, + required: ["response", "success"], + }, + }, +} + +module.exports.run = async function({ inputs }) { + let { requestMethod, url, requestBody } = inputs + if (!url.startsWith("http")) { + url = `http://${url}` + } + const request = { + method: requestMethod, + url, + } + if ( + requestBody && + requestBody.length !== 0 && + BODY_REQUESTS.indexOf(requestMethod) !== -1 + ) { + request.data = JSON.parse(requestBody) + } + + try { + const response = await axios(request) + return { + response: response.data, + success: response.status === 200, + } + } catch (err) { + return { + success: false, + response: err, + } + } +} diff --git a/packages/server/src/automations/steps/sendEmail.js b/packages/server/src/automations/steps/sendEmail.js index a46c8600ab..7f7438e58a 100644 --- a/packages/server/src/automations/steps/sendEmail.js +++ b/packages/server/src/automations/steps/sendEmail.js @@ -1,7 +1,7 @@ module.exports.definition = { description: "Send an email", tagline: "Send email to {{inputs.to}}", - icon: "ri-mail-open-fill", + icon: "ri-mail-open-line", name: "Send Email", type: "ACTION", stepId: "SEND_EMAIL", diff --git a/packages/server/src/automations/steps/updateRow.js b/packages/server/src/automations/steps/updateRow.js index 7ae254333c..e443f7e745 100644 --- a/packages/server/src/automations/steps/updateRow.js +++ b/packages/server/src/automations/steps/updateRow.js @@ -4,7 +4,7 @@ const automationUtils = require("../automationUtils") module.exports.definition = { name: "Update Row", tagline: "Update a {{inputs.enriched.table.name}} row", - icon: "ri-refresh-fill", + icon: "ri-refresh-line", description: "Update a row in your database", type: "ACTION", stepId: "UPDATE_ROW", diff --git a/packages/server/yarn.lock b/packages/server/yarn.lock index 4c3d84e7f6..93ea581e97 100644 --- a/packages/server/yarn.lock +++ b/packages/server/yarn.lock @@ -1352,6 +1352,13 @@ axios@^0.19.2: dependencies: follow-redirects "1.5.10" +axios@^0.21.0: + version "0.21.0" + resolved "https://registry.yarnpkg.com/axios/-/axios-0.21.0.tgz#26df088803a2350dff2c27f96fef99fe49442aca" + integrity sha512-fmkJBknJKoZwem3/IKSSLpkdNXZeBu5Q7GA/aRsr2btgrptmSCxi2oFjZHqGdK9DoTil9PIHlPIZw2EcRJXRvw== + dependencies: + follow-redirects "^1.10.0" + babel-jest@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-24.9.0.tgz#3fc327cb8467b89d14d7bc70e315104a783ccd54" @@ -3178,6 +3185,11 @@ follow-redirects@1.5.10: dependencies: debug "=3.1.0" +follow-redirects@^1.10.0: + version "1.13.0" + resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.13.0.tgz#b42e8d93a2a7eea5ed88633676d6597bc8e384db" + integrity sha512-aq6gF1BEKje4a9i9+5jimNFIpq4Q1WiwBToeRK5NvZBd/TRsmW8BsJfOEGkr76TbOyPVD3OVDN910EcUNtRYEA== + for-in@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/for-in/-/for-in-1.0.2.tgz#81068d295a8142ec0ac726c6e2200c30fb6d5e80"