From 366936d6222014c3dfa664855c21436ffe0d1ca6 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 17 Jun 2024 10:49:48 -0400 Subject: [PATCH] New Components - acymailing (#12377) * acymailing init * [Components] acymailing #12376 Polling Sources - New Confirmed User - New Unsubscribed User - New Subscribed User Actions - Add Update User - Email User - Subscribe User * pnpm update * Fix listIds prop on sources --------- Co-authored-by: Leo Vu --- .../add-update-user/add-update-user.mjs | 77 ++++++++++ .../actions/email-user/email-user.mjs | 55 +++++++ .../actions/subscribe-user/subscribe-user.mjs | 51 ++++++ components/acymailing/acymailing.app.mjs | 145 +++++++++++++++++- components/acymailing/common/constants.mjs | 1 + components/acymailing/common/utils.mjs | 24 +++ components/acymailing/package.json | 8 +- components/acymailing/sources/common/base.mjs | 68 ++++++++ .../new-confirmed-user/new-confirmed-user.mjs | 30 ++++ .../new-subscribed-user.mjs | 39 +++++ .../new-unsubscribed-user.mjs | 37 +++++ pnpm-lock.yaml | 5 +- 12 files changed, 532 insertions(+), 8 deletions(-) create mode 100644 components/acymailing/actions/add-update-user/add-update-user.mjs create mode 100644 components/acymailing/actions/email-user/email-user.mjs create mode 100644 components/acymailing/actions/subscribe-user/subscribe-user.mjs create mode 100644 components/acymailing/common/constants.mjs create mode 100644 components/acymailing/common/utils.mjs create mode 100644 components/acymailing/sources/common/base.mjs create mode 100644 components/acymailing/sources/new-confirmed-user/new-confirmed-user.mjs create mode 100644 components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs create mode 100644 components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs diff --git a/components/acymailing/actions/add-update-user/add-update-user.mjs b/components/acymailing/actions/add-update-user/add-update-user.mjs new file mode 100644 index 0000000000000..47852f70eb2d9 --- /dev/null +++ b/components/acymailing/actions/add-update-user/add-update-user.mjs @@ -0,0 +1,77 @@ +import acymailing from "../../acymailing.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "acymailing-add-update-user", + name: "Add or Update User", + description: "Creates a new user or updates an existing user in AcyMailing. If the user exists, will update the user's data with provided information. [See the documentation](https://docs.acymailing.com/v/rest-api/users#create-or-update-a-user)", + version: "0.0.1", + type: "action", + props: { + acymailing, + email: { + type: "string", + label: "Email", + description: "The email address is used when updating an existing user.", + }, + name: { + type: "string", + label: "Name", + description: "Any character should be available.", + optional: true, + }, + active: { + type: "boolean", + label: "Active", + description: "Defaults to true.", + optional: true, + }, + confirmed: { + type: "boolean", + label: "Confirmed", + description: "The confirmation is related to the \"Require confirmation\" option in the configuration, tab \"Subscription\".", + optional: true, + }, + cmsId: { + type: "integer", + label: "CMS Id", + description: "The cms_id must match the ID of the corresponding Joomla/WordPress user.", + optional: true, + }, + customFields: { + type: "object", + label: "Custom Fields", + description: "An object of field Ids and values.", + optional: true, + }, + triggers: { + type: "boolean", + label: "Triggers", + description: "Defaults to true. Defines if the saving of the user triggers automated tasks like follow-up campaigns and automations.", + optional: true, + }, + sendConf: { + type: "boolean", + label: "Send Conf", + description: "Defaults to true. Defines if the confirmation email should be sent when a new user is created.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.acymailing.createUserOrUpdate({ + $, + data: { + email: this.email, + name: this.name, + active: this.active, + confirmed: this.confirmed, + cmsId: this.cmsId, + customFields: parseObject(this.customFields), + triggers: this.triggers, + sendConf: this.sendConf, + }, + }); + $.export("$summary", `Successfully added or updated user with email with Id: ${response.userId}`); + return response; + }, +}; diff --git a/components/acymailing/actions/email-user/email-user.mjs b/components/acymailing/actions/email-user/email-user.mjs new file mode 100644 index 0000000000000..d5bb81c9006ff --- /dev/null +++ b/components/acymailing/actions/email-user/email-user.mjs @@ -0,0 +1,55 @@ +import acymailing from "../../acymailing.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "acymailing-email-user", + name: "Email User", + description: "Sends an email to a single AcyMailing user. The user must exist in the AcyMailing database. [See the documentation](https://docs.acymailing.com/v/rest-api/emails#send-an-email-to-a-user)", + version: "0.0.1", + type: "action", + props: { + acymailing, + email: { + type: "string", + label: "Email", + description: "The email address of the receiver.", + }, + autoAddUser: { + type: "boolean", + label: "Auto Add User", + description: "Defaults to false. If the email address doesn't match an existing AcyMailing user, one will be automatically created if this option is set to true.", + optional: true, + }, + emailId: { + type: "integer", + label: "Email Id", + description: "The mail ID to send. This is not a campaign ID but the mail ID of the table xxx_acym_mail in the database, or the mail_id of a campaign.", + }, + trackEmail: { + type: "boolean", + label: "Track Email", + description: "Defaults to true. If true, the open/click statistics will be collected for this email.", + optional: true, + }, + params: { + type: "object", + label: "Params", + description: "An object of shortcodes and values to replace in the body of the sent email. Example: { \"shortcode1\": \"value 1\" }. If the body of the sent email contains the text \"{shortcode1}\", it will be replaced by \"value 1\" in the sent version.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.acymailing.sendEmailToUser({ + $, + data: { + email: this.email, + autoAddUser: this.autoAddUser, + emailId: this.emailId, + trackEmail: this.trackEmail, + params: parseObject(this.params), + }, + }); + $.export("$summary", `Email successfully sent to ${this.email}`); + return response; + }, +}; diff --git a/components/acymailing/actions/subscribe-user/subscribe-user.mjs b/components/acymailing/actions/subscribe-user/subscribe-user.mjs new file mode 100644 index 0000000000000..f567a65d246e2 --- /dev/null +++ b/components/acymailing/actions/subscribe-user/subscribe-user.mjs @@ -0,0 +1,51 @@ +import acymailing from "../../acymailing.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "acymailing-subscribe-user", + name: "Subscribe User to Lists", + description: "Subscribes a user to one or more specified lists in AcyMailing. [See the documentation](https://docs.acymailing.com/v/rest-api/subscription#subscribe-users-to-lists)", + version: "0.0.1", + type: "action", + props: { + acymailing, + emails: { + propDefinition: [ + acymailing, + "emails", + ], + }, + listIds: { + propDefinition: [ + acymailing, + "listIds", + ], + }, + sendWelcomeEmail: { + type: "boolean", + label: "Send Welcome Email", + description: "Defaults to true. If true, the welcome emails will be sent if the lists have one.", + optional: true, + }, + trigger: { + type: "boolean", + label: "Trigger", + description: "Defaults to true. If you want to trigger or not the automation or follow-up when subscribing the user.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.acymailing.subscribeUserToLists({ + $, + data: { + emails: parseObject(this.emails), + listIds: parseObject(this.listIds), + sendWelcomeEmail: this.sendWelcomeEmail, + trigger: this.trigger, + }, + }); + + $.export("$summary", `Successfully subscribed ${this.emails.length} users to lists ${this.listIds.length} lists`); + return response; + }, +}; diff --git a/components/acymailing/acymailing.app.mjs b/components/acymailing/acymailing.app.mjs index 54c39141c9eeb..abd3af914826f 100644 --- a/components/acymailing/acymailing.app.mjs +++ b/components/acymailing/acymailing.app.mjs @@ -1,11 +1,146 @@ +import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; + export default { type: "app", app: "acymailing", - propDefinitions: {}, + propDefinitions: { + listIds: { + type: "integer[]", + label: "List Ids", + description: "Array of list IDs.", + async options({ page }) { + const lists = await this.listLists({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + + return lists.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + emails: { + type: "string[]", + label: "Emails", + description: "The email addresses of users to subscribe to the lists. These must match already existing AcyMailing users.", + async options({ page }) { + const data = await this.listUsers({ + params: { + limit: LIMIT, + offset: LIMIT * page, + }, + }); + + return data.map(({ email }) => email); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return `${this.$auth.url}`; + }, + _headers() { + return { + "Api-Key": `${this.$auth.api_key}`, + "Content-Type": "application/json", + }; + }, + _params(params) { + return { + page: "acymailing_front", + option: "com_acym", + ctrl: "api", + ...params, + }; + }, + _makeRequest({ + $ = this, params, task, ...opts + }) { + return axios($, { + url: `${this._baseUrl()}`, + params: this._params({ + ...params, + task, + }), + headers: this._headers(), + ...opts, + }); + }, + listUsers(opts = {}) { + return this._makeRequest({ + task: "getUsers", + ...opts, + }); + }, + listLists(opts = {}) { + return this._makeRequest({ + task: "getLists", + ...opts, + }); + }, + listSubscribersFromLists(opts = {}) { + return this._makeRequest({ + task: "getSubscribersFromLists", + ...opts, + }); + }, + listUnsubscribedUsersFromLists(opts = {}) { + return this._makeRequest({ + task: "getUnsubscribedUsersFromLists", + ...opts, + }); + }, + createUserOrUpdate(opts = {}) { + return this._makeRequest({ + method: "POST", + task: "createOrUpdateUser", + ...opts, + }); + }, + sendEmailToUser(opts = {}) { + return this._makeRequest({ + method: "POST", + task: "sendEmailToSingleUser", + ...opts, + }); + }, + subscribeUserToLists(opts = {}) { + return this._makeRequest({ + method: "POST", + task: "subscribeUsers", + ...opts, + }); + }, + async *paginate({ + fn, params = {}, ...opts + }) { + let hasMore = false; + let page = 0; + + do { + params.limit = LIMIT; + params.offset = LIMIT * page; + page++; + + const data = await fn({ + params, + ...opts, + }); + + for (const d of data) { + yield d; + } + + hasMore = data.length; + + } while (hasMore); }, }, -}; \ No newline at end of file + +}; diff --git a/components/acymailing/common/constants.mjs b/components/acymailing/common/constants.mjs new file mode 100644 index 0000000000000..ea830c15a04cb --- /dev/null +++ b/components/acymailing/common/constants.mjs @@ -0,0 +1 @@ +export const LIMIT = 100; diff --git a/components/acymailing/common/utils.mjs b/components/acymailing/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/acymailing/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/acymailing/package.json b/components/acymailing/package.json index 0ea087cc9207a..ac757258cdc20 100644 --- a/components/acymailing/package.json +++ b/components/acymailing/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/acymailing", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream AcyMailing Components", "main": "acymailing.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^2.0.0" } -} \ No newline at end of file +} + diff --git a/components/acymailing/sources/common/base.mjs b/components/acymailing/sources/common/base.mjs new file mode 100644 index 0000000000000..3d96e5892d799 --- /dev/null +++ b/components/acymailing/sources/common/base.mjs @@ -0,0 +1,68 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import acymailing from "../../acymailing.app.mjs"; + +export default { + props: { + acymailing, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || 0; + }, + _setLastDate(created) { + this.db.set("lastDate", created); + }, + generateMeta(item) { + return { + id: item.id, + summary: this.getSummary(item), + ts: item.createdAt, + }; + }, + async startEvent(maxResults = 0) { + const lastDate = this._getLastDate(); + + const response = this.acymailing.paginate({ + fn: this.getFn(), + params: this.getParams(), + }); + + const responseArray = []; + for await (const item of response) { + responseArray.push(item); + } + + const dateField = this.getDateField(); + + let filteredArray = responseArray + .sort((a, b) => Date.parse(b[dateField]) - Date.parse(a[dateField])); + + filteredArray = filteredArray.filter((item) => Date.parse(item[dateField]) > lastDate); + + if (maxResults && filteredArray.length > maxResults) { + filteredArray.length = maxResults; + } + + if (filteredArray.length) this._setLastDate(Date.parse(filteredArray[0][dateField])); + + for (const item of filteredArray.reverse()) { + this.$emit(item, this.generateMeta(item)); + } + }, + }, + hooks: { + async deploy() { + await this.startEvent(25); + }, + }, + async run() { + await this.startEvent(); + }, +}; diff --git a/components/acymailing/sources/new-confirmed-user/new-confirmed-user.mjs b/components/acymailing/sources/new-confirmed-user/new-confirmed-user.mjs new file mode 100644 index 0000000000000..26143fc507b4e --- /dev/null +++ b/components/acymailing/sources/new-confirmed-user/new-confirmed-user.mjs @@ -0,0 +1,30 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "acymailing-new-confirmed-user", + name: "New Confirmed User", + description: "Emit new event when a user confirms their email address.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getSummary({ + email, confirmation_date, + }) { + return `User ${email} confirmed at ${confirmation_date}.`; + }, + getParams() { + return { + "filters[confirmed]": 1, + }; + }, + getFn() { + return this.acymailing.listUsers; + }, + getDateField() { + return "confirmation_date"; + }, + }, +}; diff --git a/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs b/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs new file mode 100644 index 0000000000000..77bf549c36e21 --- /dev/null +++ b/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs @@ -0,0 +1,39 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "acymailing-new-subscribed-user", + name: "New Subscribed User", + description: "Emit new event when a user subscribes to a specified list.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + listIds: { + propDefinition: [ + common.props.acymailing, + "listIds", + ], + }, + }, + methods: { + ...common.methods, + getSummary({ + email, name, + }) { + return `New Subscriber: ${name} (${email})`; + }, + getParams() { + return { + "listIds[]": this.listIds, + }; + }, + getFn() { + return this.acymailing.listSubscribersFromLists; + }, + getDateField() { + return "subscription_date"; + }, + }, +}; diff --git a/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs b/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs new file mode 100644 index 0000000000000..8bfb7cdd774e4 --- /dev/null +++ b/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs @@ -0,0 +1,37 @@ +import common from "../common/base.mjs"; + +export default { + ...common, + key: "acymailing-new-unsubscribed-user", + name: "New Unsubscribed User", + description: "Emit new event when a user unsubscribes from the specified mailing list.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + listIds: { + propDefinition: [ + common.props.acymailing, + "listIds", + ], + }, + }, + methods: { + ...common.methods, + getSummary({ email }) { + return `User ${email} unsubscribed`; + }, + getParams() { + return { + "listIds[]": this.listIds, + }; + }, + getFn() { + return this.acymailing.listUnsubscribedUsersFromLists; + }, + getDateField() { + return "unsubscribe_date"; + }, + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2f567ba1e9d8a..57fb5205736a3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -155,7 +155,10 @@ importers: specifiers: {} components/acymailing: - specifiers: {} + specifiers: + '@pipedream/platform': ^2.0.0 + dependencies: + '@pipedream/platform': 2.0.0 components/adafruit_io: specifiers: {}