From d5b55494d982f1e516226c76074ac13f99713c84 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 11 Jun 2024 18:48:20 -0300 Subject: [PATCH 1/4] acymailing init --- .../add-update-user/add-update-user.mjs | 19 ++++ .../actions/email-user/email-user.mjs | 30 ++++++ .../actions/subscribe-user/subscribe-user.mjs | 44 +++++++++ components/acymailing/acymailing.app.mjs | 99 ++++++++++++++++++- components/acymailing/package.json | 2 +- .../new-confirmed-user/new-confirmed-user.mjs | 73 ++++++++++++++ .../new-subscribed-user.mjs | 55 +++++++++++ .../new-unsubscribed-user.mjs | 64 ++++++++++++ 8 files changed, 380 insertions(+), 6 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/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..7359b1f793334 --- /dev/null +++ b/components/acymailing/actions/add-update-user/add-update-user.mjs @@ -0,0 +1,19 @@ +import acymailing from "../../acymailing.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "acymailing-add-update-user", + name: "Add or Update User", + description: "Creates a new user or updates an existing user in AcyMailing. Required props: user data, which should include at least a unique identifier (e.g., email). If the user exists, will update the user's data with provided information.", + version: "0.0.{{ts}}", + type: "action", + props: { + acymailing, + userData: acymailing.propDefinitions.userData, + }, + async run({ $ }) { + const response = await this.acymailing.createUserOrUpdate(this.userData); + $.export("$summary", `Successfully added or updated user with email ${this.userData.email}`); + 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..cef89369dfbec --- /dev/null +++ b/components/acymailing/actions/email-user/email-user.mjs @@ -0,0 +1,30 @@ +import acymailing from "../../acymailing.app.mjs"; +import { axios } from "@pipedream/platform"; + +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.", + version: "0.0.1", + type: "action", + props: { + acymailing, + userEmail: { + propDefinition: [ + acymailing, + "userEmail", + ], + }, + emailContent: { + propDefinition: [ + acymailing, + "emailContent", + ], + }, + }, + async run({ $ }) { + const response = await this.acymailing.sendEmailToUser(this.userEmail, this.emailContent); + $.export("$summary", `Email successfully sent to ${this.userEmail}`); + 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..75263b2296f3b --- /dev/null +++ b/components/acymailing/actions/subscribe-user/subscribe-user.mjs @@ -0,0 +1,44 @@ +import acymailing from "../../acymailing.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "acymailing-subscribe-user", + name: "Subscribe User to Lists", + description: "Subscribes a user to one or more specified lists in AcyMailing. Adds user to database if they do not exist.", + version: "0.0.1", + type: "action", + props: { + acymailing, + userEmail: { + propDefinition: [ + acymailing, + "userEmail", + ], + }, + listIds: { + propDefinition: [ + acymailing, + "listIds", + ], + }, + }, + async run({ $ }) { + // Check if the user already exists and add them to the database if not + const userData = { + email: this.userEmail, + }; + const userResponse = await this.acymailing.createUserOrUpdate(userData); + if (userResponse.status !== 200) { + throw new Error(`Error creating or updating user: ${userResponse.data.message}`); + } + + // Subscribe the user to the specified lists + const subscribeResponse = await this.acymailing.subscribeUserToLists(this.userEmail, this.listIds); + if (subscribeResponse.status !== 200) { + throw new Error(`Error subscribing user to lists: ${subscribeResponse.data.message}`); + } + + $.export("$summary", `Successfully subscribed ${this.userEmail} to lists ${this.listIds.join(", ")}`); + return subscribeResponse.data; + }, +}; diff --git a/components/acymailing/acymailing.app.mjs b/components/acymailing/acymailing.app.mjs index 54c39141c9eeb..79a752798dec9 100644 --- a/components/acymailing/acymailing.app.mjs +++ b/components/acymailing/acymailing.app.mjs @@ -1,11 +1,100 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "acymailing", - propDefinitions: {}, + propDefinitions: { + lists: { + type: "string[]", + label: "Lists", + description: "Array of list names.", + async options() { + const { items } = await this.getLists(); + return items.map(({ + id, name, + }) => ({ + label: name, + value: id, + })); + }, + }, + userData: { + type: "object", + label: "User Data", + description: "User data including at least a unique identifier (e.g., email).", + }, + userEmail: { + type: "string", + label: "User Email", + description: "The user's unique identifier (e.g., email).", + }, + emailContent: { + type: "string", + label: "Email Content", + description: "The content of the email to send.", + }, + listIds: { + type: "string[]", + label: "List IDs", + description: "Array of list IDs.", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://www.example.com/index.php"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, + method = "GET", + path = "/", + headers, + ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: `${this._baseUrl()}?page=acymailing_front&option=com_acym&ctrl=api&task=${path}`, + headers: { + ...headers, + "Api-Key": `${this.$auth.api_key}`, + "Content-Type": "application/json", + }, + }); + }, + async getLists() { + return this._makeRequest({ + path: "getLists", + }); + }, + async createUserOrUpdate(userData) { + return this._makeRequest({ + method: "POST", + path: "createOrUpdateUser", + data: userData, + }); + }, + async sendEmailToUser(userEmail, emailContent) { + return this._makeRequest({ + method: "POST", + path: "sendEmailToSingleUser", + data: { + email: userEmail, + content: emailContent, + }, + }); + }, + async subscribeUserToLists(userEmail, listIds) { + return this._makeRequest({ + method: "POST", + path: "subscribeUsers", + data: { + emails: [ + userEmail, + ], + listIds, + }, + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/acymailing/package.json b/components/acymailing/package.json index 0ea087cc9207a..7e18117c8059a 100644 --- a/components/acymailing/package.json +++ b/components/acymailing/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} 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..f4cd9d75218b5 --- /dev/null +++ b/components/acymailing/sources/new-confirmed-user/new-confirmed-user.mjs @@ -0,0 +1,73 @@ +import acymailing from "../../acymailing.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "acymailing-new-confirmed-user", + name: "New Confirmed User", + description: "Emits an event when a user confirms their email address.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + acymailing, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60 * 15, // 15 minutes + }, + }, + }, + hooks: { + async deploy() { + // Fetch users during deployment to avoid emitting events for all existing users + await this.fetchAndEmitUsers(); + }, + }, + methods: { + ...acymailing.methods, + async fetchAndEmitUsers() { + const lastConfirmationDate = this.db.get("lastConfirmationDate") || null; + let offset = 0; + let hasMore = true; + + while (hasMore) { + const { + users, nextOffset, + } = await this.acymailing.getUsers({ + lastConfirmationDate, + offset, + }); + + users.forEach((user) => { + const meta = this.generateMeta(user); + this.$emit(user, meta); + }); + + hasMore = users.length > 0 && nextOffset !== undefined; + offset = nextOffset; + + if (users.length > 0) { + const mostRecentConfirmationDate = users + .map((user) => user.confirmation_date) + .sort() + .reverse()[0]; + this.db.set("lastConfirmationDate", mostRecentConfirmationDate); + } + } + }, + generateMeta(data) { + const { + id, email, confirmation_date, + } = data; + return { + id: id.toString(), + summary: `User ${email} confirmed at ${confirmation_date}`, + ts: Date.parse(confirmation_date), + }; + }, + }, + async run() { + await this.fetchAndEmitUsers(); + }, +}; 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..a7b93effef506 --- /dev/null +++ b/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs @@ -0,0 +1,55 @@ +import { axios } from "@pipedream/platform"; +import acymailing from "../../acymailing.app.mjs"; + +export default { + key: "acymailing-new-subscribed-user", + name: "New Subscribed User", + description: "Emits an event when a user subscribes to one or more specified list(s).", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + acymailing, + db: "$.service.db", + lists: { + propDefinition: [ + acymailing, + "lists", + ], + }, + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60 * 15, // 15 minutes + }, + }, + }, + methods: { + async fetchSubscriptions(listIds, lastExecutionTime) { + const subscriptions = await this.acymailing.subscribeUserToLists(null, listIds); + return subscriptions.filter((subscription) => new Date(subscription.subscription_date) > new Date(lastExecutionTime)); + }, + }, + hooks: { + async activate() { + this.db.set("lastExecutionTime", Date.now() - 1000 * 60 * 15); // Set initial state 15 minutes ago + }, + }, + async run() { + const lastExecutionTime = this.db.get("lastExecutionTime"); + const currentTime = Date.now(); + const listIds = this.lists.join(","); + + const subscriptions = await this.fetchSubscriptions(listIds, lastExecutionTime); + + subscriptions.forEach((subscription) => { + this.$emit(subscription, { + id: subscription.id, + summary: `New Subscriber: ${subscription.name} (${subscription.email})`, + ts: Date.parse(subscription.subscription_date), + }); + }); + + this.db.set("lastExecutionTime", currentTime); + }, +}; 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..3e64a922e3a44 --- /dev/null +++ b/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs @@ -0,0 +1,64 @@ +import acymailing from "../../acymailing.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "acymailing-new-unsubscribed-user", + name: "New Unsubscribed User", + description: "Emits an event when a user unsubscribes from the specified mailing list(s). [See the documentation](https://www.acymailing.com/documentation/)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + acymailing, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60 * 15, // 15 minutes + }, + }, + lists: { + propDefinition: [ + acymailing, + "lists", + ], + }, + }, + methods: { + ...acymailing.methods, + generateMeta(data) { + const { + email, id, unsubscribe_date, + } = data; + return { + id, + summary: `User ${email} unsubscribed`, + ts: Date.parse(unsubscribe_date), + }; + }, + }, + async run() { + const listIds = this.lists.map((list) => list.value); + const lastUnsubscribedDate = this.db.get("lastUnsubscribedDate") || null; + + const unsubscribedUsers = await this.acymailing.getUnsubscribedUsersFromLists({ + listIds, + lastUnsubscribedDate, + }); + + unsubscribedUsers.forEach((user) => { + this.$emit(user, this.generateMeta(user)); + }); + + if (unsubscribedUsers.length > 0) { + const mostRecentUnsubscriptionDate = unsubscribedUsers.reduce((max, user) => { + const currentDate = new Date(user.unsubscribe_date); + return currentDate > max + ? currentDate + : max; + }, new Date(0)); + + this.db.set("lastUnsubscribedDate", mostRecentUnsubscriptionDate.toISOString()); + } + }, +}; From 59283fcb3dd9d6626a8ea8ad71c478dc6a9f6957 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 14 Jun 2024 13:44:48 -0300 Subject: [PATCH 2/4] [Components] acymailing #12376 Polling Sources - New Confirmed User - New Unsubscribed User - New Subscribed User Actions - Add Update User - Email User - Subscribe User --- .../add-update-user/add-update-user.mjs | 70 ++++++- .../actions/email-user/email-user.mjs | 53 ++++-- .../actions/subscribe-user/subscribe-user.mjs | 47 +++-- components/acymailing/acymailing.app.mjs | 174 +++++++++++------- components/acymailing/common/constants.mjs | 1 + components/acymailing/common/utils.mjs | 24 +++ components/acymailing/package.json | 6 +- components/acymailing/sources/common/base.mjs | 68 +++++++ .../new-confirmed-user/new-confirmed-user.mjs | 75 ++------ .../new-subscribed-user.mjs | 64 +++---- .../new-unsubscribed-user.mjs | 67 +++---- 11 files changed, 400 insertions(+), 249 deletions(-) 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 diff --git a/components/acymailing/actions/add-update-user/add-update-user.mjs b/components/acymailing/actions/add-update-user/add-update-user.mjs index 7359b1f793334..47852f70eb2d9 100644 --- a/components/acymailing/actions/add-update-user/add-update-user.mjs +++ b/components/acymailing/actions/add-update-user/add-update-user.mjs @@ -1,19 +1,77 @@ import acymailing from "../../acymailing.app.mjs"; -import { axios } from "@pipedream/platform"; +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. Required props: user data, which should include at least a unique identifier (e.g., email). If the user exists, will update the user's data with provided information.", - version: "0.0.{{ts}}", + 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, - userData: acymailing.propDefinitions.userData, + 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(this.userData); - $.export("$summary", `Successfully added or updated user with email ${this.userData.email}`); + 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 index cef89369dfbec..d5bb81c9006ff 100644 --- a/components/acymailing/actions/email-user/email-user.mjs +++ b/components/acymailing/actions/email-user/email-user.mjs @@ -1,30 +1,55 @@ import acymailing from "../../acymailing.app.mjs"; -import { axios } from "@pipedream/platform"; +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.", + 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, - userEmail: { - propDefinition: [ - acymailing, - "userEmail", - ], + email: { + type: "string", + label: "Email", + description: "The email address of the receiver.", }, - emailContent: { - propDefinition: [ - acymailing, - "emailContent", - ], + 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(this.userEmail, this.emailContent); - $.export("$summary", `Email successfully sent to ${this.userEmail}`); + 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 index 75263b2296f3b..f567a65d246e2 100644 --- a/components/acymailing/actions/subscribe-user/subscribe-user.mjs +++ b/components/acymailing/actions/subscribe-user/subscribe-user.mjs @@ -1,18 +1,18 @@ import acymailing from "../../acymailing.app.mjs"; -import { axios } from "@pipedream/platform"; +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. Adds user to database if they do not exist.", + 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, - userEmail: { + emails: { propDefinition: [ acymailing, - "userEmail", + "emails", ], }, listIds: { @@ -21,24 +21,31 @@ export default { "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({ $ }) { - // Check if the user already exists and add them to the database if not - const userData = { - email: this.userEmail, - }; - const userResponse = await this.acymailing.createUserOrUpdate(userData); - if (userResponse.status !== 200) { - throw new Error(`Error creating or updating user: ${userResponse.data.message}`); - } - - // Subscribe the user to the specified lists - const subscribeResponse = await this.acymailing.subscribeUserToLists(this.userEmail, this.listIds); - if (subscribeResponse.status !== 200) { - throw new Error(`Error subscribing user to lists: ${subscribeResponse.data.message}`); - } + 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.userEmail} to lists ${this.listIds.join(", ")}`); - return subscribeResponse.data; + $.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 79a752798dec9..abd3af914826f 100644 --- a/components/acymailing/acymailing.app.mjs +++ b/components/acymailing/acymailing.app.mjs @@ -1,100 +1,146 @@ import { axios } from "@pipedream/platform"; +import { LIMIT } from "./common/constants.mjs"; export default { type: "app", app: "acymailing", propDefinitions: { - lists: { - type: "string[]", - label: "Lists", - description: "Array of list names.", - async options() { - const { items } = await this.getLists(); - return items.map(({ - id, name, + 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: name, - value: id, + label, + value, })); }, }, - userData: { - type: "object", - label: "User Data", - description: "User data including at least a unique identifier (e.g., email).", - }, - userEmail: { - type: "string", - label: "User Email", - description: "The user's unique identifier (e.g., email).", - }, - emailContent: { - type: "string", - label: "Email Content", - description: "The content of the email to send.", - }, - listIds: { + emails: { type: "string[]", - label: "List IDs", - description: "Array of list IDs.", + 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: { _baseUrl() { - return "https://www.example.com/index.php"; + return `${this.$auth.url}`; + }, + _headers() { + return { + "Api-Key": `${this.$auth.api_key}`, + "Content-Type": "application/json", + }; }, - async _makeRequest(opts = {}) { - const { - $ = this, - method = "GET", - path = "/", - headers, - ...otherOpts - } = opts; + _params(params) { + return { + page: "acymailing_front", + option: "com_acym", + ctrl: "api", + ...params, + }; + }, + _makeRequest({ + $ = this, params, task, ...opts + }) { return axios($, { - ...otherOpts, - method, - url: `${this._baseUrl()}?page=acymailing_front&option=com_acym&ctrl=api&task=${path}`, - headers: { - ...headers, - "Api-Key": `${this.$auth.api_key}`, - "Content-Type": "application/json", - }, + url: `${this._baseUrl()}`, + params: this._params({ + ...params, + task, + }), + headers: this._headers(), + ...opts, }); }, - async getLists() { + listUsers(opts = {}) { return this._makeRequest({ - path: "getLists", + task: "getUsers", + ...opts, }); }, - async createUserOrUpdate(userData) { + 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", - path: "createOrUpdateUser", - data: userData, + task: "createOrUpdateUser", + ...opts, }); }, - async sendEmailToUser(userEmail, emailContent) { + sendEmailToUser(opts = {}) { return this._makeRequest({ method: "POST", - path: "sendEmailToSingleUser", - data: { - email: userEmail, - content: emailContent, - }, + task: "sendEmailToSingleUser", + ...opts, }); }, - async subscribeUserToLists(userEmail, listIds) { + subscribeUserToLists(opts = {}) { return this._makeRequest({ method: "POST", - path: "subscribeUsers", - data: { - emails: [ - userEmail, - ], - listIds, - }, + 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); + }, }, + }; 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 7e18117c8059a..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" } } + 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 index f4cd9d75218b5..26143fc507b4e 100644 --- a/components/acymailing/sources/new-confirmed-user/new-confirmed-user.mjs +++ b/components/acymailing/sources/new-confirmed-user/new-confirmed-user.mjs @@ -1,73 +1,30 @@ -import acymailing from "../../acymailing.app.mjs"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; export default { + ...common, key: "acymailing-new-confirmed-user", name: "New Confirmed User", - description: "Emits an event when a user confirms their email address.", + description: "Emit new event when a user confirms their email address.", version: "0.0.1", type: "source", dedupe: "unique", - props: { - acymailing, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60 * 15, // 15 minutes - }, - }, - }, - hooks: { - async deploy() { - // Fetch users during deployment to avoid emitting events for all existing users - await this.fetchAndEmitUsers(); - }, - }, methods: { - ...acymailing.methods, - async fetchAndEmitUsers() { - const lastConfirmationDate = this.db.get("lastConfirmationDate") || null; - let offset = 0; - let hasMore = true; - - while (hasMore) { - const { - users, nextOffset, - } = await this.acymailing.getUsers({ - lastConfirmationDate, - offset, - }); - - users.forEach((user) => { - const meta = this.generateMeta(user); - this.$emit(user, meta); - }); - - hasMore = users.length > 0 && nextOffset !== undefined; - offset = nextOffset; - - if (users.length > 0) { - const mostRecentConfirmationDate = users - .map((user) => user.confirmation_date) - .sort() - .reverse()[0]; - this.db.set("lastConfirmationDate", mostRecentConfirmationDate); - } - } + ...common.methods, + getSummary({ + email, confirmation_date, + }) { + return `User ${email} confirmed at ${confirmation_date}.`; }, - generateMeta(data) { - const { - id, email, confirmation_date, - } = data; + getParams() { return { - id: id.toString(), - summary: `User ${email} confirmed at ${confirmation_date}`, - ts: Date.parse(confirmation_date), + "filters[confirmed]": 1, }; }, - }, - async run() { - await this.fetchAndEmitUsers(); + 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 index a7b93effef506..29f467e9cc0d1 100644 --- a/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs +++ b/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs @@ -1,55 +1,41 @@ -import { axios } from "@pipedream/platform"; -import acymailing from "../../acymailing.app.mjs"; +import common from "../common/base.mjs"; export default { + ...common, key: "acymailing-new-subscribed-user", name: "New Subscribed User", - description: "Emits an event when a user subscribes to one or more specified list(s).", - version: "0.0.{{ts}}", + description: "Emit new event when a user subscribes to a specified list.", + version: "0.0.1", type: "source", dedupe: "unique", props: { - acymailing, - db: "$.service.db", - lists: { + ...common.props, + listId: { propDefinition: [ - acymailing, - "lists", + common.props.acymailing, + "listIds", ], - }, - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60 * 15, // 15 minutes - }, + type: "integer", + label: "List Id", }, }, methods: { - async fetchSubscriptions(listIds, lastExecutionTime) { - const subscriptions = await this.acymailing.subscribeUserToLists(null, listIds); - return subscriptions.filter((subscription) => new Date(subscription.subscription_date) > new Date(lastExecutionTime)); + ...common.methods, + getSummary({ + email, name, + }) { + return `New Subscriber: ${name} (${email})`; }, - }, - hooks: { - async activate() { - this.db.set("lastExecutionTime", Date.now() - 1000 * 60 * 15); // Set initial state 15 minutes ago + getParams() { + return { + "listIds[]": this.listId, + }; + }, + getFn() { + return this.acymailing.listSubscribersFromLists; + }, + getDateField() { + return "subscription_date"; }, - }, - async run() { - const lastExecutionTime = this.db.get("lastExecutionTime"); - const currentTime = Date.now(); - const listIds = this.lists.join(","); - - const subscriptions = await this.fetchSubscriptions(listIds, lastExecutionTime); - - subscriptions.forEach((subscription) => { - this.$emit(subscription, { - id: subscription.id, - summary: `New Subscriber: ${subscription.name} (${subscription.email})`, - ts: Date.parse(subscription.subscription_date), - }); - }); - - this.db.set("lastExecutionTime", currentTime); }, }; diff --git a/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs b/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs index 3e64a922e3a44..2a055efa0da79 100644 --- a/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs +++ b/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs @@ -1,64 +1,39 @@ -import acymailing from "../../acymailing.app.mjs"; -import { axios } from "@pipedream/platform"; +import common from "../common/base.mjs"; export default { + ...common, key: "acymailing-new-unsubscribed-user", name: "New Unsubscribed User", - description: "Emits an event when a user unsubscribes from the specified mailing list(s). [See the documentation](https://www.acymailing.com/documentation/)", + description: "Emit new event when a user unsubscribes from the specified mailing list.", version: "0.0.1", type: "source", dedupe: "unique", props: { - acymailing, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60 * 15, // 15 minutes - }, - }, - lists: { + ...common.props, + listId: { propDefinition: [ - acymailing, - "lists", + common.props.acymailing, + "listIds", ], + type: "integer", + label: "List Id", }, }, methods: { - ...acymailing.methods, - generateMeta(data) { - const { - email, id, unsubscribe_date, - } = data; + ...common.methods, + getSummary({ email }) { + return `User ${email} unsubscribed`; + }, + getParams() { return { - id, - summary: `User ${email} unsubscribed`, - ts: Date.parse(unsubscribe_date), + "listIds[]": this.listId, }; }, - }, - async run() { - const listIds = this.lists.map((list) => list.value); - const lastUnsubscribedDate = this.db.get("lastUnsubscribedDate") || null; - - const unsubscribedUsers = await this.acymailing.getUnsubscribedUsersFromLists({ - listIds, - lastUnsubscribedDate, - }); - - unsubscribedUsers.forEach((user) => { - this.$emit(user, this.generateMeta(user)); - }); - - if (unsubscribedUsers.length > 0) { - const mostRecentUnsubscriptionDate = unsubscribedUsers.reduce((max, user) => { - const currentDate = new Date(user.unsubscribe_date); - return currentDate > max - ? currentDate - : max; - }, new Date(0)); - - this.db.set("lastUnsubscribedDate", mostRecentUnsubscriptionDate.toISOString()); - } + getFn() { + return this.acymailing.listUnsubscribedUsersFromLists; + }, + getDateField() { + return "unsubscribe_date"; + }, }, }; From 5a765589f92f5559e372b5f7710edd3341cf2f61 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 14 Jun 2024 13:47:21 -0300 Subject: [PATCH 3/4] pnpm update --- pnpm-lock.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 34679d91049ab..fd3e676a0d056 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: {} From 0ab312a81469e567acc42d515a44757cfed06306 Mon Sep 17 00:00:00 2001 From: Leo Vu Date: Mon, 17 Jun 2024 11:13:21 +0700 Subject: [PATCH 4/4] Fix listIds prop on sources --- .../sources/new-subscribed-user/new-subscribed-user.mjs | 6 ++---- .../sources/new-unsubscribed-user/new-unsubscribed-user.mjs | 6 ++---- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs b/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs index 29f467e9cc0d1..77bf549c36e21 100644 --- a/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs +++ b/components/acymailing/sources/new-subscribed-user/new-subscribed-user.mjs @@ -10,13 +10,11 @@ export default { dedupe: "unique", props: { ...common.props, - listId: { + listIds: { propDefinition: [ common.props.acymailing, "listIds", ], - type: "integer", - label: "List Id", }, }, methods: { @@ -28,7 +26,7 @@ export default { }, getParams() { return { - "listIds[]": this.listId, + "listIds[]": this.listIds, }; }, getFn() { diff --git a/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs b/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs index 2a055efa0da79..8bfb7cdd774e4 100644 --- a/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs +++ b/components/acymailing/sources/new-unsubscribed-user/new-unsubscribed-user.mjs @@ -10,13 +10,11 @@ export default { dedupe: "unique", props: { ...common.props, - listId: { + listIds: { propDefinition: [ common.props.acymailing, "listIds", ], - type: "integer", - label: "List Id", }, }, methods: { @@ -26,7 +24,7 @@ export default { }, getParams() { return { - "listIds[]": this.listId, + "listIds[]": this.listIds, }; }, getFn() {