diff --git a/components/veedea/actions/list-campaigns/list-campaigns.mjs b/components/veedea/actions/list-campaigns/list-campaigns.mjs new file mode 100644 index 0000000000000..3c0aec2ed3d34 --- /dev/null +++ b/components/veedea/actions/list-campaigns/list-campaigns.mjs @@ -0,0 +1,31 @@ +import veedea from "../../veedea.app.mjs"; + +export default { + key: "veedea-list-campaigns", + name: "List Campaigns", + description: "Get the list of campaigns created in the Veedea Dashboard. [See the documentation](https://veedea.com/api/doc)", + version: "0.0.1", + type: "action", + props: { + veedea, + maxResults: { + propDefinition: [ + veedea, + "maxResults", + ], + }, + }, + async run({ $ }) { + const token = await this.veedea.getToken(); + const campaigns = await this.veedea.getPaginatedResources({ + fn: this.veedea.listCampaigns, + args: { + $, + token, + }, + max: this.maxResults, + }); + $.export("$summary", `Successfully retrieved ${campaigns.length} campaigns`); + return campaigns; + }, +}; diff --git a/components/veedea/actions/list-leads/list-leads.mjs b/components/veedea/actions/list-leads/list-leads.mjs new file mode 100644 index 0000000000000..33c747b7ab063 --- /dev/null +++ b/components/veedea/actions/list-leads/list-leads.mjs @@ -0,0 +1,40 @@ +import veedea from "../../veedea.app.mjs"; + +export default { + key: "veedea-list-leads", + name: "List Leads", + description: "Get a list of leads in a campaign. [See the documentation](https://veedea.com/api/doc)", + version: "0.0.1", + type: "action", + props: { + veedea, + campaignId: { + propDefinition: [ + veedea, + "campaignId", + ], + }, + maxResults: { + propDefinition: [ + veedea, + "maxResults", + ], + }, + }, + async run({ $ }) { + const token = await this.veedea.getToken(); + const leads = await this.veedea.getPaginatedResources({ + fn: this.veedea.listLeads, + args: { + $, + token, + params: { + campaign_id: this.campaignId, + }, + }, + max: this.maxResults, + }); + $.export("$summary", `Successfully retrieved ${leads.length} leads`); + return leads; + }, +}; diff --git a/components/veedea/actions/list-product-purchases/list-product-purchases.mjs b/components/veedea/actions/list-product-purchases/list-product-purchases.mjs new file mode 100644 index 0000000000000..e802d9c142377 --- /dev/null +++ b/components/veedea/actions/list-product-purchases/list-product-purchases.mjs @@ -0,0 +1,40 @@ +import veedea from "../../veedea.app.mjs"; + +export default { + key: "veedea-list-product-purchases", + name: "List Product Purchases", + description: "Retrieves a list of leads who purchased products through the specified campaign. [See the documentation](https://veedea.com/api/doc)", + version: "0.0.1", + type: "action", + props: { + veedea, + campaignId: { + propDefinition: [ + veedea, + "campaignId", + ], + }, + maxResults: { + propDefinition: [ + veedea, + "maxResults", + ], + }, + }, + async run({ $ }) { + const token = await this.veedea.getToken(); + const productPurchases = await this.veedea.getPaginatedResources({ + fn: this.veedea.listProductPurchases, + args: { + $, + token, + params: { + campaign_id: this.campaignId, + }, + }, + max: this.maxResults, + }); + $.export("$summary", `Successfully retrieved ${productPurchases.length} product purchases`); + return productPurchases; + }, +}; diff --git a/components/veedea/package.json b/components/veedea/package.json index 1e611385771cb..c9ee3b367bacf 100644 --- a/components/veedea/package.json +++ b/components/veedea/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/veedea", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Veedea Components", "main": "veedea.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.1.0" } -} \ No newline at end of file +} diff --git a/components/veedea/sources/common/base.mjs b/components/veedea/sources/common/base.mjs new file mode 100644 index 0000000000000..767c93ee55f67 --- /dev/null +++ b/components/veedea/sources/common/base.mjs @@ -0,0 +1,88 @@ +import { + DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, ConfigurationError, +} from "@pipedream/platform"; +import veedea from "../../veedea.app.mjs"; + +export default { + props: { + veedea, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastTs() { + return this.db.get("lastTs"); + }, + _setLastTs(ts) { + this.db.set("lastTs", ts); + }, + getTsField() { + return null; + }, + getArgs() { + return {}; + }, + async processEvent(max) { + const lastTs = this._getLastTs(); + const tsField = this.getTsField(); + const fn = this.getResourceFn(); + const args = this.getArgs(); + const token = await this.veedea.getToken(); + + const results = this.veedea.paginate({ + fn, + args: { + ...args, + token, + }, + max, + }); + + const items = []; + for await (const item of results) { + const ts = tsField + ? Date.parse(item[tsField]) + : null; + if (!ts) { + items.push(item); + } else { + if (ts > lastTs) { + items.push(item); + } else { + break; + } + } + } + + if (!items.length) { + return; + } + + this._setLastTs(Date.parse(items[0][tsField])); + + items.reverse().forEach((item) => { + const meta = this.generateMeta(item); + this.$emit(item, meta); + }); + }, + getResourceFn() { + throw new ConfigurationError("getResourceFn is not implemented"); + }, + generateMeta() { + throw new ConfigurationError("generateMeta is not implemented"); + }, + }, + hooks: { + async deploy() { + await this.processEvent(25); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/veedea/sources/new-campaign-created/new-campaign-created.mjs b/components/veedea/sources/new-campaign-created/new-campaign-created.mjs new file mode 100644 index 0000000000000..ff4a2932477dc --- /dev/null +++ b/components/veedea/sources/new-campaign-created/new-campaign-created.mjs @@ -0,0 +1,26 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "veedea-new-campaign-created", + name: "New Campaign Created", + description: "Emit new event when a new campaign is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.veedea.listCampaigns; + }, + generateMeta(item) { + return { + id: item.id, + summary: `New Campaign Created: ${item.camp_name}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/veedea/sources/new-campaign-created/test-event.mjs b/components/veedea/sources/new-campaign-created/test-event.mjs new file mode 100644 index 0000000000000..18a0db2d03865 --- /dev/null +++ b/components/veedea/sources/new-campaign-created/test-event.mjs @@ -0,0 +1,4 @@ +export default { + "id": 11320, + "camp_name": "My Campaign" + } \ No newline at end of file diff --git a/components/veedea/sources/new-lead-created/new-lead-created.mjs b/components/veedea/sources/new-lead-created/new-lead-created.mjs new file mode 100644 index 0000000000000..d981911728afc --- /dev/null +++ b/components/veedea/sources/new-lead-created/new-lead-created.mjs @@ -0,0 +1,45 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "veedea-new-lead-created", + name: "New Lead Created", + description: "Emit new event when a new lead is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + campaignId: { + propDefinition: [ + common.props.veedea, + "campaignId", + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.veedea.listLeads; + }, + getTsField() { + return "registerDate"; + }, + getArgs() { + return { + params: { + campaignId: this.campaignId, + }, + }; + }, + generateMeta(item) { + return { + id: item.id, + summary: `New Lead Created: ${item.user_name}`, + ts: Date.parse(item[this.getTsField()]), + }; + }, + }, + sampleEmit, +}; diff --git a/components/veedea/sources/new-lead-created/test-event.mjs b/components/veedea/sources/new-lead-created/test-event.mjs new file mode 100644 index 0000000000000..c69e27dc05899 --- /dev/null +++ b/components/veedea/sources/new-lead-created/test-event.mjs @@ -0,0 +1,10 @@ +export default { + campid: "330", + user_name: "John Doe", + user_email: "john.doe@example.com", + source: "", + device: "", + region: "", + id: 26, + registerDate: "2025-06-19T12:00:00Z", +} \ No newline at end of file diff --git a/components/veedea/sources/new-product-purchase-created/new-product-purchase-created.mjs b/components/veedea/sources/new-product-purchase-created/new-product-purchase-created.mjs new file mode 100644 index 0000000000000..3a8efc5d3a762 --- /dev/null +++ b/components/veedea/sources/new-product-purchase-created/new-product-purchase-created.mjs @@ -0,0 +1,45 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "veedea-new-product-purchase-created", + name: "New Product Purchase Created", + description: "Emit new event when a new product purchase is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + campaignId: { + propDefinition: [ + common.props.veedea, + "campaignId", + ], + }, + }, + methods: { + ...common.methods, + getResourceFn() { + return this.veedea.listProductPurchases; + }, + getTsField() { + return "purchase_datetime"; + }, + getArgs() { + return { + params: { + campaignId: this.campaignId, + }, + }; + }, + generateMeta(item) { + return { + id: item.id, + summary: `New Product Purchase Created: ${item.prod_name}`, + ts: Date.parse(item[this.getTsField()]), + }; + }, + }, + sampleEmit, +}; diff --git a/components/veedea/sources/new-product-purchase-created/test-event.mjs b/components/veedea/sources/new-product-purchase-created/test-event.mjs new file mode 100644 index 0000000000000..0f376fb0e204d --- /dev/null +++ b/components/veedea/sources/new-product-purchase-created/test-event.mjs @@ -0,0 +1,11 @@ +export default { + id: 37, + camp_name: "My Campaign", + prod_name: "Product Name", + prod_price: "5.00", + prod_method: "paypal", + prod_txs_id: "1234567890", + cus_name: "John Doe", + cus_email: "john.doe@example.com", + purchase_datetime: "2025-06-19T12:00:00Z", +} \ No newline at end of file diff --git a/components/veedea/veedea.app.mjs b/components/veedea/veedea.app.mjs index ec82ddbb13192..30cd0e90b7ad7 100644 --- a/components/veedea/veedea.app.mjs +++ b/components/veedea/veedea.app.mjs @@ -1,11 +1,111 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "veedea", - propDefinitions: {}, + propDefinitions: { + campaignId: { + type: "string", + label: "Campaign ID", + description: "The ID of a campaign", + async options({ page }) { + const token = await this.getToken(); + const { data } = await this.listCampaigns({ + params: { + page: page + 1, + }, + token, + }); + return data?.map(({ + id: value, camp_name: label, + }) => ({ + value, + label, + })) || []; + }, + }, + maxResults: { + type: "integer", + label: "Max Results", + description: "The maximum number of results to return", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://veedea.com/api"; + }, + _makeRequest({ + $ = this, path, token, params, ...opts + }) { + return axios($, { + url: `${this._baseUrl()}${path}`, + params: { + ...params, + api_key: this.$auth.api_key, + token, + }, + ...opts, + }); + }, + async getToken() { + const { token } = await this._makeRequest({ + path: "/auth", + }); + return token; + }, + listCampaigns(opts = {}) { + return this._makeRequest({ + path: "/getcampaign", + ...opts, + }); + }, + listLeads(opts = {}) { + return this._makeRequest({ + path: "/getleads", + ...opts, + }); + }, + listProductPurchases(opts = {}) { + return this._makeRequest({ + path: "/productpurchase", + ...opts, + }); + }, + async *paginate({ + fn, args, max, + }) { + args = { + ...args, + params: { + ...args?.params, + limit: 100, + page: 1, + }, + }; + let total, count = 0; + do { + const { data } = await fn(args); + if (!data?.length) { + return; + } + for (const item of data) { + yield item; + if (max && ++count >= max) { + return; + } + } + total = data?.length; + args.params.page++; + } while (total === args.params.limit); + }, + async getPaginatedResources(opts) { + const resources = []; + const results = this.paginate(opts); + for await (const item of results) { + resources.push(item); + } + return resources; }, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 6af7438eb1f98..2fbc70da48b97 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -313,8 +313,7 @@ importers: specifier: ^3.0.1 version: 3.0.3 - components/add_to_calendar_pro: - specifiers: {} + components/add_to_calendar_pro: {} components/addevent: dependencies: @@ -14300,7 +14299,11 @@ importers: specifier: ^0.0.1-security version: 0.0.1-security - components/veedea: {} + components/veedea: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/vend: dependencies: @@ -15846,7 +15849,7 @@ importers: version: 3.1.7 ts-jest: specifier: ^29.2.5 - version: 29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2) + version: 29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2) tsup: specifier: ^8.3.6 version: 8.3.6(@microsoft/api-extractor@7.47.12(@types/node@20.17.30))(jiti@1.21.6)(postcss@8.4.49)(tsx@4.19.4)(typescript@5.7.2)(yaml@2.6.1) @@ -15889,7 +15892,7 @@ importers: version: 3.1.0 jest: specifier: ^29.1.2 - version: 29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0) + version: 29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0) type-fest: specifier: ^4.15.0 version: 4.27.0 @@ -49810,7 +49813,7 @@ snapshots: ts-interface-checker@0.1.13: {} - ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2): + ts-jest@29.2.5(@babel/core@7.26.0)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.26.0))(jest@29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0))(typescript@5.7.2): dependencies: bs-logger: 0.2.6 ejs: 3.1.10 @@ -49824,10 +49827,10 @@ snapshots: typescript: 5.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 8.0.0-alpha.13 + '@babel/core': 7.26.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@8.0.0-alpha.13) + babel-jest: 29.7.0(@babel/core@7.26.0) ts-jest@29.2.5(@babel/core@8.0.0-alpha.13)(@jest/transform@29.7.0)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@8.0.0-alpha.13))(jest@29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0))(typescript@5.6.3): dependencies: