From 6437ac6d02c0447c98a0680e549eeac593678d24 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 27 Sep 2024 16:38:34 -0300 Subject: [PATCH 1/5] moaform init --- components/moaform/moaform.app.mjs | 77 +++++++++++++++- components/moaform/package.json | 2 +- .../new-submission-instant.mjs | 91 +++++++++++++++++++ 3 files changed, 166 insertions(+), 4 deletions(-) create mode 100644 components/moaform/sources/new-submission-instant/new-submission-instant.mjs diff --git a/components/moaform/moaform.app.mjs b/components/moaform/moaform.app.mjs index 61ac154dc3314..53239d506277b 100644 --- a/components/moaform/moaform.app.mjs +++ b/components/moaform/moaform.app.mjs @@ -1,11 +1,82 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "moaform", - propDefinitions: {}, + propDefinitions: { + formId: { + type: "string", + label: "Form ID", + description: "The ID of the form to monitor for new submissions", + async options() { + const forms = await this.getForms(); + return forms.map((form) => ({ + label: form.name, + value: form.id, + })); + }, + }, + fields: { + type: "string[]", + label: "Fields to Capture", + description: "Optional fields to capture from the submission", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.moaform.com"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.api_token}`, + }, + }); + }, + async getForms(opts = {}) { + return this._makeRequest({ + ...opts, + path: "/forms", + }); + }, + async createWebhook(opts = {}) { + const { + formId, url, fields, + } = opts; + return this._makeRequest({ + method: "POST", + path: `/forms/${formId}/webhooks`, + data: { + url, + fields, + }, + }); + }, + async deleteWebhook(opts = {}) { + const { + formId, webhookId, + } = opts; + return this._makeRequest({ + method: "DELETE", + path: `/forms/${formId}/webhooks/${webhookId}`, + }); + }, + async getWebhooks(opts = {}) { + const { formId } = opts; + return this._makeRequest({ + path: `/forms/${formId}/webhooks`, + }); + }, }, -}; \ No newline at end of file +}; diff --git a/components/moaform/package.json b/components/moaform/package.json index e140fabbf0869..f137bda15dcfa 100644 --- a/components/moaform/package.json +++ b/components/moaform/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/moaform/sources/new-submission-instant/new-submission-instant.mjs b/components/moaform/sources/new-submission-instant/new-submission-instant.mjs new file mode 100644 index 0000000000000..57dc9467d9daa --- /dev/null +++ b/components/moaform/sources/new-submission-instant/new-submission-instant.mjs @@ -0,0 +1,91 @@ +import { axios } from "@pipedream/platform"; +import moaform from "../../moaform.app.mjs"; + +export default { + key: "moaform-new-submission-instant", + name: "New Submission Instant", + description: "Emit new event every time a new form submission is received. [See the documentation](https://help.moaform.com)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + moaform, + db: "$.service.db", + formId: { + propDefinition: [ + moaform, + "formId", + ], + }, + fields: { + propDefinition: [ + moaform, + "fields", + ], + }, + httpSource: { + type: "$.interface.http", + label: "HTTP Source", + description: "HTTP source to set up webhooks", + }, + }, + hooks: { + async deploy() { + const webhooks = await this.moaform.getWebhooks({ + formId: this.formId, + }); + + for (const webhook of webhooks) { + this.$emit(webhook, { + id: webhook.id, + summary: `Existing webhook: ${webhook.id}`, + ts: Date.now(), + }); + } + }, + async activate() { + const { + formId, fields, + } = this; + const url = this.httpSource.url; + const webhook = await this.moaform.createWebhook({ + formId, + url, + fields, + }); + + this.db.set("webhookId", webhook.id); + }, + async deactivate() { + const webhookId = this.db.get("webhookId"); + await this.moaform.deleteWebhook({ + formId: this.formId, + webhookId, + }); + }, + }, + async run(event) { + const { + headers, body, + } = event; + const id = headers["x-moaform-submission-id"]; + const formId = body.formId; + const submissionFields = this.fields.length ? + this.fields.reduce((acc, field) => ({ + ...acc, + [field]: body.fields[field], + }), {}) + : body.fields; + + const eventToEmit = { + formId, + ...submissionFields, + }; + + this.$emit(eventToEmit, { + id, + summary: `New submission received for form ${formId}`, + ts: Date.now(), + }); + }, +}; From 06d4f36af326434b748e83503664621365920077 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 27 Sep 2024 18:37:59 -0300 Subject: [PATCH 2/5] [Components] moaform #14132 Sources - New Submission (Instant) --- components/moaform/moaform.app.mjs | 74 +++++------ components/moaform/package.json | 5 +- .../new-submission-instant.mjs | 124 ++++++++++-------- .../new-submission-instant/test-event.mjs | 18 +++ 4 files changed, 122 insertions(+), 99 deletions(-) create mode 100644 components/moaform/sources/new-submission-instant/test-event.mjs diff --git a/components/moaform/moaform.app.mjs b/components/moaform/moaform.app.mjs index 53239d506277b..09007f770c539 100644 --- a/components/moaform/moaform.app.mjs +++ b/components/moaform/moaform.app.mjs @@ -8,75 +8,61 @@ export default { type: "string", label: "Form ID", description: "The ID of the form to monitor for new submissions", - async options() { - const forms = await this.getForms(); - return forms.map((form) => ({ - label: form.name, - value: form.id, + async options({ page }) { + const { items } = await this.getForms({ + params: { + page: page + 1, + }, + }); + return items.map(({ + id: value, title: label, + }) => ({ + label, + value, })); }, }, - fields: { - type: "string[]", - label: "Fields to Capture", - description: "Optional fields to capture from the submission", - optional: true, - }, }, methods: { - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { - return "https://api.moaform.com"; + return "https://api.moaform.com/v1"; + }, + _headers() { + return { + Authorization: `Bearer ${this.$auth.api_key}`, + }; }, - async _makeRequest(opts = {}) { - const { - $ = this, method = "GET", path = "/", headers, ...otherOpts - } = opts; + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - ...otherOpts, - method, url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.api_token}`, - }, + headers: this._headers(), + ...opts, }); }, - async getForms(opts = {}) { + getForms(opts = {}) { return this._makeRequest({ ...opts, path: "/forms", }); }, - async createWebhook(opts = {}) { - const { - formId, url, fields, - } = opts; + createWebhook({ + formId, ...opts + }) { return this._makeRequest({ method: "POST", path: `/forms/${formId}/webhooks`, - data: { - url, - fields, - }, + ...opts, }); }, - async deleteWebhook(opts = {}) { - const { - formId, webhookId, - } = opts; + deleteWebhook({ + formId, webhookId, + }) { return this._makeRequest({ method: "DELETE", path: `/forms/${formId}/webhooks/${webhookId}`, }); }, - async getWebhooks(opts = {}) { - const { formId } = opts; - return this._makeRequest({ - path: `/forms/${formId}/webhooks`, - }); - }, }, }; diff --git a/components/moaform/package.json b/components/moaform/package.json index f137bda15dcfa..209996644a6c8 100644 --- a/components/moaform/package.json +++ b/components/moaform/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/moaform", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Moaform Components", "main": "moaform.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/moaform/sources/new-submission-instant/new-submission-instant.mjs b/components/moaform/sources/new-submission-instant/new-submission-instant.mjs index 57dc9467d9daa..d9e5530ab3bf4 100644 --- a/components/moaform/sources/new-submission-instant/new-submission-instant.mjs +++ b/components/moaform/sources/new-submission-instant/new-submission-instant.mjs @@ -1,15 +1,20 @@ -import { axios } from "@pipedream/platform"; +import crypto from "crypto"; import moaform from "../../moaform.app.mjs"; +import sampleEmit from "./test-event.mjs"; export default { key: "moaform-new-submission-instant", - name: "New Submission Instant", - description: "Emit new event every time a new form submission is received. [See the documentation](https://help.moaform.com)", + name: "New Submission (Instant)", + description: "Emit new event every time a new form submission is received.", version: "0.0.1", type: "source", dedupe: "unique", props: { moaform, + http: { + type: "$.interface.http", + customResponse: true, + }, db: "$.service.db", formId: { propDefinition: [ @@ -17,44 +22,50 @@ export default { "formId", ], }, - fields: { - propDefinition: [ - moaform, - "fields", + retentionDays: { + type: "integer", + label: "Retention Days", + description: "Resend restriction days", + options: [ + 1, + 3, + 5, + 7, + 10, + 15, + 30, ], + optional: true, }, - httpSource: { - type: "$.interface.http", - label: "HTTP Source", - description: "HTTP source to set up webhooks", + secret: { + type: "string", + label: "Secret Code", + description: "This code is used to verify that the data received at the specified endpoint has indeed been sent from Moaform and has not been tampered with.", + secret: true, + optional: true, }, }, - hooks: { - async deploy() { - const webhooks = await this.moaform.getWebhooks({ - formId: this.formId, - }); - - for (const webhook of webhooks) { - this.$emit(webhook, { - id: webhook.id, - summary: `Existing webhook: ${webhook.id}`, - ts: Date.now(), - }); - } + methods: { + _getWebhookId() { + return this.db.get("webhookId"); + }, + _setWebhookId(id) { + this.db.set("webhookId", id); }, + }, + hooks: { async activate() { - const { - formId, fields, - } = this; - const url = this.httpSource.url; - const webhook = await this.moaform.createWebhook({ - formId, - url, - fields, + const response = await this.moaform.createWebhook({ + formId: this.formId, + data: { + endpoint: this.http.endpoint, + enabled: true, + secret: this.secret, + verify_ssl: true, + retention_days: this.retentionDays, + }, }); - - this.db.set("webhookId", webhook.id); + this._setWebhookId(response.id); }, async deactivate() { const webhookId = this.db.get("webhookId"); @@ -64,28 +75,33 @@ export default { }); }, }, - async run(event) { - const { - headers, body, - } = event; - const id = headers["x-moaform-submission-id"]; - const formId = body.formId; - const submissionFields = this.fields.length ? - this.fields.reduce((acc, field) => ({ - ...acc, - [field]: body.fields[field], - }), {}) - : body.fields; + async run({ + bodyRaw, body, headers, + }) { + + if (this.secret) { + const signature = headers["moaform-signature"]; + const receivedSig = signature.split("sha256=")[1]; + + const calculatedSig = crypto + .createHmac("sha256", this.secret) + .update(bodyRaw) + .digest("base64"); - const eventToEmit = { - formId, - ...submissionFields, - }; + if (receivedSig !== calculatedSig) { + return this.http.respond({ + status: 401, + body: "Unauthorized", + }); + } + } - this.$emit(eventToEmit, { - id, - summary: `New submission received for form ${formId}`, - ts: Date.now(), + const ts = Date.parse(body.submitted_at); + this.$emit(body, { + id: body.event_id, + summary: `New submission received for form ${this.formId}`, + ts: ts, }); }, + sampleEmit, }; diff --git a/components/moaform/sources/new-submission-instant/test-event.mjs b/components/moaform/sources/new-submission-instant/test-event.mjs new file mode 100644 index 0000000000000..6fc8f83f5c9f2 --- /dev/null +++ b/components/moaform/sources/new-submission-instant/test-event.mjs @@ -0,0 +1,18 @@ +export default { + "event_id": "105fa72f-9e6e-4667-8ead-22b0a3ed254a", + "event_type": "response_completed", + "hidden": {}, + "response_id": "test#123-45-67890", + "submitted_at": "2024-09-27T21:00:55Z", + "form": { + "id": "MG7eAk", + "title": "form title", + "report_url": "https://www.moaform.com/reports/q945EegynYD8G2xM", + "answer_url": "https://moaform.com/q/not-started-collecting" + }, + "answers": [], + "thankyou": { + "id": "cm1l7axyf2t9x0fqrly27vdn7", + "url": "https://answer.moaform.com/answers/MG7eAk/thankyou/Wo2b1Z09wx5" + } +} \ No newline at end of file From 9634ceeaab472def3f83822c79a215e76dae60b9 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 27 Sep 2024 18:41:05 -0300 Subject: [PATCH 3/5] pnpm update --- pnpm-lock.yaml | 107 +++++++++++++++++++++++++------------------------ 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 846a2b4eb41ca..b73501558d144 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -6067,7 +6067,10 @@ importers: '@pipedream/platform': 1.5.1 components/moaform: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 components/mobile_text_alerts: specifiers: {} @@ -12833,6 +12836,55 @@ packages: - aws-crt dev: false + /@aws-sdk/client-sso-oidc/3.600.0_tdq3komn4zwyd65w7klbptsu34: + resolution: {integrity: sha512-7+I8RWURGfzvChyNQSyj5/tKrqRbzRl7H+BnTOf/4Vsw1nFOi5ROhlhD4X/Y0QCTacxnaoNcIrqnY7uGGvVRzw==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/core': 3.598.0 + '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 + '@aws-sdk/middleware-host-header': 3.598.0 + '@aws-sdk/middleware-logger': 3.598.0 + '@aws-sdk/middleware-recursion-detection': 3.598.0 + '@aws-sdk/middleware-user-agent': 3.598.0 + '@aws-sdk/region-config-resolver': 3.598.0 + '@aws-sdk/types': 3.598.0 + '@aws-sdk/util-endpoints': 3.598.0 + '@aws-sdk/util-user-agent-browser': 3.598.0 + '@aws-sdk/util-user-agent-node': 3.598.0 + '@smithy/config-resolver': 3.0.3 + '@smithy/core': 2.2.3 + '@smithy/fetch-http-handler': 3.2.1 + '@smithy/hash-node': 3.0.2 + '@smithy/invalid-dependency': 3.0.2 + '@smithy/middleware-content-length': 3.0.2 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-retry': 3.0.6 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.3 + '@smithy/node-http-handler': 3.1.2 + '@smithy/protocol-http': 4.0.3 + '@smithy/smithy-client': 3.1.6 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.6 + '@smithy/util-defaults-mode-node': 3.0.6 + '@smithy/util-endpoints': 2.0.3 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.2 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - '@aws-sdk/client-sts' + - aws-crt + dev: false + /@aws-sdk/client-sso/3.423.0: resolution: {integrity: sha512-znIufHkwhCIePgaYciIs3x/+BpzR57CZzbCKHR9+oOvGyufEPPpUT5bFLvbwTgfiVkTjuk6sG/ES3U5Bc+xtrA==} engines: {node: '>=14.0.0'} @@ -13068,7 +13120,7 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0 + '@aws-sdk/client-sso-oidc': 3.600.0_tdq3komn4zwyd65w7klbptsu34 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 '@aws-sdk/middleware-host-header': 3.598.0 @@ -13110,55 +13162,6 @@ packages: - aws-crt dev: false - /@aws-sdk/client-sts/3.600.0_dseaa2p5u2yk67qiepewcq3hkq: - resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} - engines: {node: '>=16.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0 - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.3 - '@smithy/core': 2.2.3 - '@smithy/fetch-http-handler': 3.2.1 - '@smithy/hash-node': 3.0.2 - '@smithy/invalid-dependency': 3.0.2 - '@smithy/middleware-content-length': 3.0.2 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.6 - '@smithy/middleware-serde': 3.0.3 - '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.2 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.6 - '@smithy/types': 3.3.0 - '@smithy/url-parser': 3.0.3 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.6 - '@smithy/util-defaults-mode-node': 3.0.6 - '@smithy/util-endpoints': 2.0.3 - '@smithy/util-middleware': 3.0.3 - '@smithy/util-retry': 3.0.2 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - transitivePeerDependencies: - - '@aws-sdk/client-sso-oidc' - - aws-crt - dev: false - /@aws-sdk/core/3.556.0: resolution: {integrity: sha512-vJaSaHw2kPQlo11j/Rzuz0gk1tEaKdz+2ser0f0qZ5vwFlANjt08m/frU17ctnVKC1s58bxpctO/1P894fHLrA==} engines: {node: '>=14.0.0'} @@ -17485,7 +17488,7 @@ packages: '@aws-sdk/client-sns': 3.423.0 '@aws-sdk/client-sqs': 3.423.0 '@aws-sdk/client-ssm': 3.423.0 - '@aws-sdk/client-sts': 3.600.0_dseaa2p5u2yk67qiepewcq3hkq + '@aws-sdk/client-sts': 3.600.0 '@aws-sdk/s3-request-presigner': 3.609.0 '@pipedream/helper_functions': 0.3.12 '@pipedream/platform': 1.6.6 From f57484eb412f8e74cc726d030023e860c258c7b1 Mon Sep 17 00:00:00 2001 From: Leo Vu Date: Tue, 1 Oct 2024 10:02:29 +0700 Subject: [PATCH 4/5] Fix retention days label Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../new-submission-instant.mjs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/components/moaform/sources/new-submission-instant/new-submission-instant.mjs b/components/moaform/sources/new-submission-instant/new-submission-instant.mjs index d9e5530ab3bf4..401777f922212 100644 --- a/components/moaform/sources/new-submission-instant/new-submission-instant.mjs +++ b/components/moaform/sources/new-submission-instant/new-submission-instant.mjs @@ -27,13 +27,13 @@ export default { label: "Retention Days", description: "Resend restriction days", options: [ - 1, - 3, - 5, - 7, - 10, - 15, - 30, + { label: "1", value: 1 }, + { label: "3", value: 3 }, + { label: "5", value: 5 }, + { label: "7", value: 7 }, + { label: "10", value: 10 }, + { label: "15", value: 15 }, + { label: "30", value: 30 }, ], optional: true, }, From b3aa9560bab2e26f9ee64fd623a55b1a5ba5d7ff Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 1 Oct 2024 09:34:49 -0300 Subject: [PATCH 5/5] some adjusts --- components/moaform/common/constants.mjs | 30 +++++++++++++++++++ .../new-submission-instant.mjs | 11 ++----- 2 files changed, 32 insertions(+), 9 deletions(-) create mode 100644 components/moaform/common/constants.mjs diff --git a/components/moaform/common/constants.mjs b/components/moaform/common/constants.mjs new file mode 100644 index 0000000000000..dbce28a0a61e4 --- /dev/null +++ b/components/moaform/common/constants.mjs @@ -0,0 +1,30 @@ +export const RETENTION_DAYS_OPTIONS = [ + { + label: "1", + value: 1, + }, + { + label: "3", + value: 3, + }, + { + label: "5", + value: 5, + }, + { + label: "7", + value: 7, + }, + { + label: "10", + value: 10, + }, + { + label: "15", + value: 15, + }, + { + label: "30", + value: 30, + }, +]; diff --git a/components/moaform/sources/new-submission-instant/new-submission-instant.mjs b/components/moaform/sources/new-submission-instant/new-submission-instant.mjs index 401777f922212..85668ca7d3524 100644 --- a/components/moaform/sources/new-submission-instant/new-submission-instant.mjs +++ b/components/moaform/sources/new-submission-instant/new-submission-instant.mjs @@ -1,4 +1,5 @@ import crypto from "crypto"; +import { RETENTION_DAYS_OPTIONS } from "../../common/constants.mjs"; import moaform from "../../moaform.app.mjs"; import sampleEmit from "./test-event.mjs"; @@ -26,15 +27,7 @@ export default { type: "integer", label: "Retention Days", description: "Resend restriction days", - options: [ - { label: "1", value: 1 }, - { label: "3", value: 3 }, - { label: "5", value: 5 }, - { label: "7", value: 7 }, - { label: "10", value: 10 }, - { label: "15", value: 15 }, - { label: "30", value: 30 }, - ], + options: RETENTION_DAYS_OPTIONS, optional: true, }, secret: {