diff --git a/components/adhook/actions/create-calendar-event/create-calendar-event.mjs b/components/adhook/actions/create-calendar-event/create-calendar-event.mjs new file mode 100644 index 0000000000000..8cfe8e244f647 --- /dev/null +++ b/components/adhook/actions/create-calendar-event/create-calendar-event.mjs @@ -0,0 +1,108 @@ +import adhook from "../../adhook.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "adhook-create-calendar-event", + name: "Create Calendar Event", + description: "Generates a personalized calendar event in AdHook. [See the documentation](https://app.adhook.io/api-doc/)", + version: "0.0.1", + type: "action", + props: { + adhook, + title: { + type: "string", + label: "Event Title", + description: "The title of the calendar event", + optional: true, + }, + description: { + type: "string", + label: "Event Description", + description: "The description of the calendar event", + optional: true, + }, + externalId: { + propDefinition: [ + adhook, + "externalId", + ], + optional: true, + }, + start: { + type: "string", + label: "Start Date", + description: "Start date of the event. **Format: YYYY-MM-DDTHH:MM:SSZ**", + optional: true, + }, + end: { + type: "string", + label: "End Date", + description: "End date of the event. **Format: YYYY-MM-DDTHH:MM:SSZ**", + optional: true, + }, + allDay: { + type: "boolean", + label: "All Day", + description: "Whether the event lasts all day or not", + optional: true, + }, + color: { + type: "string", + label: "Color", + description: "The color of the event", + optional: true, + }, + subtenantId: { + propDefinition: [ + adhook, + "subtenantId", + ], + optional: true, + }, + tags: { + propDefinition: [ + adhook, + "tags", + ], + optional: true, + }, + topics: { + propDefinition: [ + adhook, + "topics", + ], + optional: true, + }, + attachments: { + type: "string[]", + label: "Attachments", + description: "A list of objects of attachments for the event. **Format: {\"name\": \"Attachment name\", \"url\":\"https://attachment.com/file.pdf\", \"fileExtension\":\"pdf\"}**", + optional: true, + }, + }, + async run({ $ }) { + const { + adhook, + tags, + topics, + attachments, + ...data + } = this; + + const response = await adhook.createCalendarEvent({ + $, + data: { + type: "EVENT", + ...data, + tags: parseObject(tags), + topics: parseObject(topics)?.map((topic) => ({ + name: topic, + })), + attachments: parseObject(attachments), + }, + }); + + $.export("$summary", `Successfully created calendar event: ${response.id}`); + return response; + }, +}; diff --git a/components/adhook/actions/create-update-post/create-update-post.mjs b/components/adhook/actions/create-update-post/create-update-post.mjs new file mode 100644 index 0000000000000..13bec5285823f --- /dev/null +++ b/components/adhook/actions/create-update-post/create-update-post.mjs @@ -0,0 +1,138 @@ +import adhook from "../../adhook.app.mjs"; +import constants from "../../common/constants.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "adhook-create-update-post", + name: "Create or Update Post", + description: "Adds a new post or modifies an existing post in Adhook. [See the documentation](https://app.adhook.io/api-doc/)", + version: "0.0.1", + type: "action", + props: { + adhook, + postId: { + propDefinition: [ + adhook, + "postId", + ], + optional: true, + }, + name: { + type: "string", + label: "Name", + description: "The name of the post", + optional: true, + }, + type: { + type: "string", + label: "Type", + description: "The type of the post", + optional: true, + options: constants.TYPE_OPTIONS, + }, + subtenantId: { + propDefinition: [ + adhook, + "subtenantId", + ], + optional: true, + }, + status: { + type: "string", + label: "Status", + description: "The status of the post", + optional: true, + options: constants.STATUS_OPTIONS, + }, + tags: { + propDefinition: [ + adhook, + "tags", + ], + optional: true, + }, + topics: { + propDefinition: [ + adhook, + "topics", + ], + optional: true, + }, + schedule: { + type: "string", + label: "Schedule", + description: "Whether you want to schedule or publish the post", + optional: true, + options: constants.SCHEDULE_OPTIONS, + }, + message: { + type: "string", + label: "Message", + description: "A message to send with the post", + optional: true, + }, + promotionType: { + type: "string", + label: "Promotion Type", + description: "The type of the promotion in the post", + optional: true, + options: constants.PROMOTION_TYPE_OPTIONS, + }, + campaignName: { + type: "string", + label: "Campaign Name", + description: "The name of the campaign", + optional: true, + }, + externalId: { + propDefinition: [ + adhook, + "externalId", + ], + optional: true, + }, + }, + async run({ $ }) { + const { + adhook, + postId, + tags, + topics, + ...data + } = this; + + let postData = {}; + + if (postId) { + const post = await adhook.getPost({ + postId, + }); + postData = post; + } + + postData = { + ...postData, + ...data, + tags: parseObject(tags) || postData.tags, + topics: parseObject(topics)?.map((topic) => ({ + name: topic, + })) || postData.topics, + }; + + const fn = postId + ? adhook.updatePost + : adhook.createPost; + + const response = await fn({ + $, + postId, + data: postData, + }); + + $.export("$summary", `Successfully ${postId + ? "updated" + : "created"} post`); + + return response; + }, +}; diff --git a/components/adhook/adhook.app.mjs b/components/adhook/adhook.app.mjs index bfbca6eb55ab1..0a3b5aa878e5a 100644 --- a/components/adhook/adhook.app.mjs +++ b/components/adhook/adhook.app.mjs @@ -1,11 +1,128 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "adhook", - propDefinitions: {}, + propDefinitions: { + subtenantId: { + type: "string", + label: "Subtenant Id", + description: "The id of the subtenant.", + async options() { + const data = await this.listSubtenants(); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + postId: { + type: "string", + label: "Post ID", + description: "The ID of the post", + async options({ page }) { + const data = await this.listPosts({ + params: { + limit: constants.LIMIT, + skip: constants.LIMIT * page, + }, + }); + + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + tags: { + type: "string[]", + label: "Tags", + description: "A list of tags.", + }, + topics: { + type: "string[]", + label: "Topics", + description: "A list of topics.", + }, + externalId: { + type: "string", + label: "External Id", + description: "An external Id to the event", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://app.adhook.io/api/v1"; + }, + _headers() { + return { + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }); + }, + createCalendarEvent(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/customEvents", + ...opts, + }); + }, + listPosts(opts = {}) { + return this._makeRequest({ + path: "/posts", + ...opts, + }); + }, + listSubtenants(opts = {}) { + return this._makeRequest({ + path: "/subtenants", + ...opts, + }); + }, + createPost(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/posts", + ...opts, + }); + }, + getPost({ postId }) { + return this._makeRequest({ + path: `/posts/${postId}`, + }); + }, + updatePost({ + postId, ...opts + }) { + return this._makeRequest({ + method: "PUT", + path: `/posts/${postId}`, + ...opts, + }); + }, + listCreatedPosts() { + return this._makeRequest({ + path: "/posts/recentlyCreated", + }); + }, + listUpdatedPosts() { + return this._makeRequest({ + path: "/posts/recentlyUpdated", + }); }, }, -}; \ No newline at end of file +}; diff --git a/components/adhook/common/constants.mjs b/components/adhook/common/constants.mjs new file mode 100644 index 0000000000000..a917c3200b959 --- /dev/null +++ b/components/adhook/common/constants.mjs @@ -0,0 +1,59 @@ +const LIMIT = 100; + +const TYPE_OPTIONS = [ + "PERSONAL_POST", + "PAGE_POST", + "STORY", + "REELS", + "SHORTS", + "LINK_CAROUSEL", + "DOCUMENT", + "POLL", +]; + +const STATUS_OPTIONS = [ + "CREATED", + "READY", + "PLANNED", + "PUBLISHED", + "IN_REVIEW", + "ACTIVATING", + "MANUALLY_ACTIVATING", + "ERROR", + "DELETED", + "CHECK_REVIEW_FEEDBACK", + "IMPORTING", +]; + +const SCHEDULE_OPTIONS = [ + "PUBLISH_AS_SOON_AS_POSSIBLE", + "PLANNED_POST_PUBLISH", +]; + +const PROMOTION_TYPE_OPTIONS = [ + "TRAFFIC", + "CONVERSIONS", + "RETARGETING", + "SHOPPING", + "CARS", + "REAL_ESTATE", + "JOBS", + "EVENTS", + "APP", + "ENGAGEMENT", + "VIDEO_VIEWS", + "CALLS", + "REACH", + "FOLLOWERS", + "PROFILE_VIEWS", + "LEADS", + "PERFORMANCE_MAX", +]; + +export default { + LIMIT, + TYPE_OPTIONS, + STATUS_OPTIONS, + SCHEDULE_OPTIONS, + PROMOTION_TYPE_OPTIONS, +}; diff --git a/components/adhook/common/utils.mjs b/components/adhook/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/adhook/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/adhook/package.json b/components/adhook/package.json index bf2afd366cb66..9348175fade26 100644 --- a/components/adhook/package.json +++ b/components/adhook/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/adhook", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Adhook Components", "main": "adhook.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.1" } -} \ No newline at end of file +} diff --git a/components/adhook/sources/common/base.mjs b/components/adhook/sources/common/base.mjs new file mode 100644 index 0000000000000..9d139d58d36a3 --- /dev/null +++ b/components/adhook/sources/common/base.mjs @@ -0,0 +1,54 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import adhook from "../../adhook.app.mjs"; + +export default { + props: { + adhook, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastDate() { + return this.db.get("lastDate") || 0; + }, + _setLastDate(lastDate) { + this.db.set("lastDate", lastDate); + }, + async emitEvent(maxResults = false) { + const lastDate = this._getLastDate(); + const fieldDate = this.getFieldDate(); + const fn = this.getFunction(); + let response = await fn(); + + response = response.filter((item) => Date.parse(item[fieldDate]) > lastDate); + + if (response.length) { + if (maxResults && (response.length > maxResults)) { + response.length = maxResults; + } + this._setLastDate(Date.parse(response[0][fieldDate])); + } + + for (const item of response.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item[fieldDate]), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/adhook/sources/new-post/new-post.mjs b/components/adhook/sources/new-post/new-post.mjs new file mode 100644 index 0000000000000..b4abb56b048f5 --- /dev/null +++ b/components/adhook/sources/new-post/new-post.mjs @@ -0,0 +1,25 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "adhook-new-post", + name: "New Post Created", + description: "Emit new event when a new post is created. [See the documentation](https://app.adhook.io/api-doc/)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.adhook.listCreatedPosts; + }, + getFieldDate() { + return "createdAt"; + }, + getSummary(item) { + return `New Post: ${item.id}`; + }, + }, + sampleEmit, +}; diff --git a/components/adhook/sources/new-post/test-event.mjs b/components/adhook/sources/new-post/test-event.mjs new file mode 100644 index 0000000000000..62430e0b0638a --- /dev/null +++ b/components/adhook/sources/new-post/test-event.mjs @@ -0,0 +1,50 @@ +export default { + "id": "66c57199f555a956148eaba0", + "message": "Message text", + "type": "PAGE_POST", + "status": "PUBLISHED", + "createdAt": "2024-08-21T04:48:25.666860864Z", + "createdByUserEmail": "user@email.com", + "createdByUserId": "66c57199f555a956148eaba0", + "tenantId": "66c57199f555a956148eaba0", + "tags": [ + { + "id": "66c57199f555a956148eaba0", + "userId": null, + "tenantId": "66c57199f555a956148eaba0", + "subtenantId": "66c57199f555a956148eaba0", + "text": "LinkedIn", + "color": null + } + ], + "topics": null, + "subtenantId": "66c57199f555a956148eaba0", + "processReviewFeedback": null, + "processTaskAssigneeUserId": null, + "processTaskAssigneeUserIds": null, + "imageUrl": null, + "videoUrl": null, + "videoThumbnailUrl": null, + "promotePost": false, + "nbrOfDays": 7, + "totalBudget": 0.0, + "currency": "USD", + "remark": null, + "impressions": 0, + "reach": 0, + "clicks": 0, + "likes": 0, + "comments": 0, + "shares": 0, + "videoViews": 0, + "reactions": 0, + "publishAt": "2024-02-02T18:39:23.22Z", + "publishedAt": "2024-02-02T18:39:23.22Z", + "schedule": "PLANNED_POST_PUBLISH", + "activationErrorMessage": null, + "activationErrorChannel": null, + "translationStatus": null, + "translationDate": null, + "deliverContentTillDate": null, + "reviewTillDate": null +} \ No newline at end of file diff --git a/components/adhook/sources/updated-post/test-event.mjs b/components/adhook/sources/updated-post/test-event.mjs new file mode 100644 index 0000000000000..62430e0b0638a --- /dev/null +++ b/components/adhook/sources/updated-post/test-event.mjs @@ -0,0 +1,50 @@ +export default { + "id": "66c57199f555a956148eaba0", + "message": "Message text", + "type": "PAGE_POST", + "status": "PUBLISHED", + "createdAt": "2024-08-21T04:48:25.666860864Z", + "createdByUserEmail": "user@email.com", + "createdByUserId": "66c57199f555a956148eaba0", + "tenantId": "66c57199f555a956148eaba0", + "tags": [ + { + "id": "66c57199f555a956148eaba0", + "userId": null, + "tenantId": "66c57199f555a956148eaba0", + "subtenantId": "66c57199f555a956148eaba0", + "text": "LinkedIn", + "color": null + } + ], + "topics": null, + "subtenantId": "66c57199f555a956148eaba0", + "processReviewFeedback": null, + "processTaskAssigneeUserId": null, + "processTaskAssigneeUserIds": null, + "imageUrl": null, + "videoUrl": null, + "videoThumbnailUrl": null, + "promotePost": false, + "nbrOfDays": 7, + "totalBudget": 0.0, + "currency": "USD", + "remark": null, + "impressions": 0, + "reach": 0, + "clicks": 0, + "likes": 0, + "comments": 0, + "shares": 0, + "videoViews": 0, + "reactions": 0, + "publishAt": "2024-02-02T18:39:23.22Z", + "publishedAt": "2024-02-02T18:39:23.22Z", + "schedule": "PLANNED_POST_PUBLISH", + "activationErrorMessage": null, + "activationErrorChannel": null, + "translationStatus": null, + "translationDate": null, + "deliverContentTillDate": null, + "reviewTillDate": null +} \ No newline at end of file diff --git a/components/adhook/sources/updated-post/updated-post.mjs b/components/adhook/sources/updated-post/updated-post.mjs new file mode 100644 index 0000000000000..61458ffcf22ee --- /dev/null +++ b/components/adhook/sources/updated-post/updated-post.mjs @@ -0,0 +1,25 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "adhook-updated-post", + name: "New Updated Post", + description: "Emit new event when a post is updated.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.adhook.listUpdatedPosts; + }, + getFieldDate() { + return "updatedAt"; + }, + getSummary(post) { + return `Post Updated: ${post.id}`; + }, + }, + sampleEmit, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 7e0d493a69875..3d84505ac6305 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -236,7 +236,10 @@ importers: '@pipedream/platform': 3.0.0 components/adhook: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.1 + dependencies: + '@pipedream/platform': 3.0.1 components/adobe_pdf_services: specifiers: