diff --git a/components/xola/actions/add-event-guide/add-event-guide.mjs b/components/xola/actions/add-event-guide/add-event-guide.mjs new file mode 100644 index 0000000000000..ce573bfe13850 --- /dev/null +++ b/components/xola/actions/add-event-guide/add-event-guide.mjs @@ -0,0 +1,57 @@ +import app from "../../xola.app.mjs"; + +export default { + key: "xola-add-event-guide", + name: "Add Event Guide", + description: "Adds a guide to an event. [See the documentation](https://developers.xola.com/reference/assign-a-guide-to-a-trip)", + version: "0.0.1", + type: "action", + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + props: { + app, + eventId: { + propDefinition: [ + app, + "eventId", + ], + }, + guideId: { + propDefinition: [ + app, + "guideId", + ], + }, + forceConfirm: { + type: "boolean", + label: "Force Confirm", + description: "Force assignment even if guide has conflicts", + optional: true, + }, + }, + async run({ $ }) { + const { + app, + eventId, + guideId, + forceConfirm, + } = this; + + const response = await app.addEventGuide({ + $, + eventId, + data: { + guide: { + id: guideId, + forceConfirm, + }, + }, + }); + + $.export("$summary", `Successfully added guide to event with ID \`${response.id}\``); + return response; + }, +}; diff --git a/components/xola/actions/create-experience-schedule/create-experience-schedule.mjs b/components/xola/actions/create-experience-schedule/create-experience-schedule.mjs new file mode 100644 index 0000000000000..b2ef4168bf5d4 --- /dev/null +++ b/components/xola/actions/create-experience-schedule/create-experience-schedule.mjs @@ -0,0 +1,173 @@ +import app from "../../xola.app.mjs"; + +export default { + key: "xola-create-experience-schedule", + name: "Create Experience Schedule", + description: "Creates a new schedule for an experience. [See the documentation](https://developers.xola.com/reference/create-an-experience)", + version: "0.0.1", + type: "action", + props: { + app, + experienceId: { + propDefinition: [ + app, + "experienceId", + ], + }, + name: { + type: "string", + label: "Name", + description: "Name for this schedule", + }, + type: { + type: "string", + label: "Type", + description: "Can be for an open (`available`) schedule or a `blackout` schedule", + options: [ + "available", + "blackout", + ], + }, + days: { + type: "string[]", + label: "Days", + description: "Days of week", + optional: true, + options: [ + { + label: "Sunday", + value: "0", + }, + { + label: "Monday", + value: "1", + }, + { + label: "Tuesday", + value: "2", + }, + { + label: "Wednesday", + value: "3", + }, + { + label: "Thursday", + value: "4", + }, + { + label: "Friday", + value: "5", + }, + { + label: "Saturday", + value: "6", + }, + ], + }, + departure: { + type: "string", + label: "Departure", + description: "Whether departure time is fixed or varies", + options: [ + "fixed", + "varies", + ], + optional: true, + }, + times: { + type: "string[]", + label: "Times", + description: "Start times in HHMM format (e.g., `900` = 9:00 AM, `1400` = 2:00 PM, `1800` = 6:00 PM).", + options: [ + { + label: "9:00 AM", + value: "900", + }, + { + label: "2:00 PM", + value: "1400", + }, + { + label: "6:00 PM", + value: "1800", + }, + ], + optional: true, + }, + priceDelta: { + type: "string", + label: "Price Delta", + description: "Price adjustment for this schedule (can be positive or negative). Only available when **Type** is `available`", + optional: true, + }, + repeat: { + type: "string", + label: "Repeat", + description: "When and how the schedule should repeat. Options are `weekly` (repeat the same schedule every week until **End**, if specified), or custom, which can be used in conjunction with **Dates** to specify individual days for the schedule to run", + optional: true, + options: [ + "weekly", + "custom", + ], + }, + start: { + type: "string", + label: "Start", + description: "Start date of the schedule in ISO 8601 format. Example: `2024-01-01T00:00:00Z`", + optional: true, + }, + end: { + type: "string", + label: "End", + description: "End date of the schedule in ISO 8601 format. Example: `2024-12-31T23:59:59Z`", + optional: true, + }, + dates: { + type: "string[]", + label: "Dates", + description: "Specific dates when this schedule applies. Only available when **Repeat** is `custom`. Format is `YYYY-MM-DD`. Cannot be combined with **End**.", + optional: true, + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + async run({ $ }) { + const { + app, + experienceId, + name, + type, + days, + times, + departure, + priceDelta, + repeat, + start, + end, + dates, + } = this; + + const response = await app.createExperienceSchedule({ + $, + experienceId, + data: { + start, + end, + dates, + type, + name, + repeat, + days, + times, + departure, + priceDelta, + }, + }); + + $.export("$summary", `Successfully created schedule for experience with ID \`${response.id}\``); + return response; + }, +}; diff --git a/components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs b/components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs new file mode 100644 index 0000000000000..b44d677ea09a5 --- /dev/null +++ b/components/xola/actions/delete-experience-schedule/delete-experience-schedule.mjs @@ -0,0 +1,45 @@ +import app from "../../xola.app.mjs"; + +export default { + key: "xola-delete-experience-schedule", + name: "Delete Experience Schedule", + description: "Deletes a schedule from an experience. [See the documentation](https://xola.github.io/xola-docs/#tag/schedules/operation/deleteExperienceSchedule)", + version: "0.0.1", + type: "action", + props: { + app, + experienceId: { + propDefinition: [ + app, + "experienceId", + ], + }, + scheduleId: { + propDefinition: [ + app, + "scheduleId", + ], + }, + }, + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + async run({ $ }) { + const { + app, + experienceId, + scheduleId, + } = this; + + const response = await app.deleteExperienceSchedule({ + $, + experienceId, + scheduleId, + }); + + $.export("$summary", `Successfully deleted schedule ${scheduleId}`); + return response; + }, +}; diff --git a/components/xola/actions/patch-event/patch-event.mjs b/components/xola/actions/patch-event/patch-event.mjs new file mode 100644 index 0000000000000..cd1a1d0b00587 --- /dev/null +++ b/components/xola/actions/patch-event/patch-event.mjs @@ -0,0 +1,89 @@ +import app from "../../xola.app.mjs"; + +export default { + key: "xola-patch-event", + name: "Patch Event", + description: "Updates specific fields of an event. [See the documentation](https://xola.github.io/xola-docs/#tag/events/operation/patchEvent)", + version: "0.0.1", + type: "action", + props: { + app, + eventId: { + propDefinition: [ + app, + "eventId", + ], + }, + name: { + type: "string", + label: "Name", + description: "The name of the event", + optional: true, + }, + description: { + type: "string", + label: "Description", + description: "The description of the event", + optional: true, + }, + start: { + type: "string", + label: "Start Date/Time", + description: "Start date and time in ISO 8601 format. Example: `2024-01-01T09:00:00Z`", + optional: true, + }, + end: { + type: "string", + label: "End Date/Time", + description: "End date and time in ISO 8601 format. Example: `2024-01-01T17:00:00Z`", + optional: true, + }, + capacity: { + type: "integer", + label: "Capacity", + description: "The maximum number of participants for the event", + optional: true, + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + async run({ $ }) { + const { + app, + eventId, + name, + description, + start, + end, + capacity, + } = this; + + const response = await app.patchEvent({ + $, + eventId, + data: { + ...name && { + name, + }, + ...description && { + description, + }, + ...start && { + start, + }, + ...end && { + end, + }, + ...capacity && { + capacity, + }, + }, + }); + + $.export("$summary", `Successfully patched event ${eventId}`); + return response; + }, +}; diff --git a/components/xola/actions/remove-event-guide/remove-event-guide.mjs b/components/xola/actions/remove-event-guide/remove-event-guide.mjs new file mode 100644 index 0000000000000..f1557b8e0697e --- /dev/null +++ b/components/xola/actions/remove-event-guide/remove-event-guide.mjs @@ -0,0 +1,45 @@ +import app from "../../xola.app.mjs"; + +export default { + key: "xola-remove-event-guide", + name: "Remove Event Guide", + description: "Removes a guide from an event. [See the documentation](https://xola.github.io/xola-docs/#tag/events/operation/removeEventGuide)", + version: "0.0.1", + type: "action", + props: { + app, + eventId: { + propDefinition: [ + app, + "eventId", + ], + }, + guideId: { + propDefinition: [ + app, + "guideId", + ], + }, + }, + annotations: { + destructiveHint: true, + openWorldHint: true, + readOnlyHint: false, + }, + async run({ $ }) { + const { + app, + eventId, + guideId, + } = this; + + const response = await app.removeEventGuide({ + $, + eventId, + guideId, + }); + + $.export("$summary", `Successfully removed guide ${guideId} from event ${eventId}`); + return response; + }, +}; diff --git a/components/xola/actions/update-experience-schedule/update-experience-schedule.mjs b/components/xola/actions/update-experience-schedule/update-experience-schedule.mjs new file mode 100644 index 0000000000000..b30f36f46074a --- /dev/null +++ b/components/xola/actions/update-experience-schedule/update-experience-schedule.mjs @@ -0,0 +1,126 @@ +import app from "../../xola.app.mjs"; + +export default { + key: "xola-update-experience-schedule", + name: "Update Experience Schedule", + description: "Updates an existing schedule for an experience. [See the documentation](https://xola.github.io/xola-docs/#tag/schedules/operation/updateExperienceSchedule)", + version: "0.0.1", + type: "action", + props: { + app, + experienceId: { + propDefinition: [ + app, + "experienceId", + ], + }, + scheduleId: { + propDefinition: [ + app, + "scheduleId", + ], + }, + name: { + type: "string", + label: "Name", + description: "The name of the schedule", + optional: true, + }, + type: { + type: "string", + label: "Type", + description: "The type of schedule", + options: [ + "available", + "unavailable", + ], + optional: true, + }, + repeat: { + type: "string", + label: "Repeat", + description: "How often the schedule repeats", + options: [ + "daily", + "weekly", + "monthly", + ], + optional: true, + }, + days: { + type: "integer[]", + label: "Days", + description: "Days of the week (1-7, where 1 is Monday). Example: `[1, 2, 3]` for Monday through Wednesday", + optional: true, + }, + start: { + type: "string", + label: "Start Date", + description: "Start date in ISO 8601 format. Example: `2024-01-01T00:00:00Z`", + optional: true, + }, + end: { + type: "string", + label: "End Date", + description: "End date in ISO 8601 format. Example: `2024-12-31T23:59:59Z`", + optional: true, + }, + times: { + type: "string[]", + label: "Times", + description: "Array of time slots. Example: `[\"09:00\", \"14:00\"]`", + optional: true, + }, + }, + annotations: { + destructiveHint: false, + openWorldHint: true, + readOnlyHint: false, + }, + async run({ $ }) { + const { + app, + experienceId, + scheduleId, + name, + type, + repeat, + days, + start, + end, + times, + } = this; + + const response = await app.updateExperienceSchedule({ + $, + experienceId, + scheduleId, + data: { + ...name && { + name, + }, + ...type && { + type, + }, + ...repeat && { + repeat, + }, + ...days && { + days, + }, + ...start && { + start, + }, + ...end && { + end, + }, + ...times && { + times, + }, + }, + }); + + $.export("$summary", `Successfully updated schedule ${scheduleId}`); + return response; + }, +}; diff --git a/components/xola/common/constants.mjs b/components/xola/common/constants.mjs new file mode 100644 index 0000000000000..98a3700107f4f --- /dev/null +++ b/components/xola/common/constants.mjs @@ -0,0 +1,6 @@ +export default { + VERSION_PATH: "/api", + API_VERSION: "2025-07-07", + DEFAULT_LIMIT: 20, + DEFAULT_MAX: 100, +}; diff --git a/components/xola/package.json b/components/xola/package.json index 3bb32c4ed2bc6..158d192d5b1ef 100644 --- a/components/xola/package.json +++ b/components/xola/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/xola", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Xola Components", "main": "xola.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/xola/sources/common/base.mjs b/components/xola/sources/common/base.mjs new file mode 100644 index 0000000000000..ad0811f2943f5 --- /dev/null +++ b/components/xola/sources/common/base.mjs @@ -0,0 +1,50 @@ +import app from "../../xola.app.mjs"; +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; + +export default { + props: { + app, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastCreatedAt() { + return this.db.get("lastCreatedAt"); + }, + _setLastCreatedAt(lastCreatedAt) { + this.db.set("lastCreatedAt", lastCreatedAt); + }, + _getLastUpdatedAt() { + return this.db.get("lastUpdatedAt"); + }, + _setLastUpdatedAt(lastUpdatedAt) { + this.db.set("lastUpdatedAt", lastUpdatedAt); + }, + _getProcessedIds() { + return this.db.get("processedIds") || []; + }, + _setProcessedIds(ids) { + this.db.set("processedIds", ids); + }, + generateMeta() { + throw new Error("generateMeta is not implemented"); + }, + getResourceFn() { + throw new Error("getResourceFn is not implemented"); + }, + getParams() { + return {}; + }, + processEvent() { + throw new Error("processEvent is not implemented"); + }, + }, + async run() { + await this.processEvent(); + }, +}; diff --git a/components/xola/sources/common/webhook.mjs b/components/xola/sources/common/webhook.mjs new file mode 100644 index 0000000000000..27c831877d9fe --- /dev/null +++ b/components/xola/sources/common/webhook.mjs @@ -0,0 +1,59 @@ +import app from "../../xola.app.mjs"; + +export default { + props: { + app, + db: "$.service.db", + http: { + type: "$.interface.http", + customResponse: true, + }, + userId: { + propDefinition: [ + app, + "userId", + ], + }, + }, + hooks: { + async activate() { + const { userId = this.app.$auth.user_id } = this; + const response = await this.app.createWebhook({ + userId, + data: { + url: this.http.endpoint, + event: this.getEventName(), + }, + }); + this.db.set("hookId", response.id); + this.db.set("userId", userId); + }, + async deactivate() { + const hookId = this.db.get("hookId"); + const userId = this.db.get("userId"); + if (hookId && userId) { + await this.app.deleteWebhook({ + userId, + hookId, + }); + } + }, + }, + methods: { + getEventName() { + throw new Error("getEventName is not implemented"); + }, + generateMeta() { + throw new Error("generateMeta is not implemented"); + }, + }, + async run(event) { + const { body } = event; + this.http.respond({ + status: 200, + }); + + const meta = this.generateMeta(body); + this.$emit(body, meta); + }, +}; diff --git a/components/xola/sources/experience-created/experience-created.mjs b/components/xola/sources/experience-created/experience-created.mjs new file mode 100644 index 0000000000000..8f18a67eddfd0 --- /dev/null +++ b/components/xola/sources/experience-created/experience-created.mjs @@ -0,0 +1,59 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-experience-created", + name: "Experience Created", + description: "Emit new event when a new experience is created. [See the documentation](https://xola.github.io/xola-docs/#tag/experiences/operation/listExperiences)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.app.listExperiences; + }, + getParams() { + return { + limit: 100, + skip: 0, + }; + }, + generateMeta(experience) { + return { + id: experience.id, + summary: `New Experience: ${experience.name}`, + ts: Date.parse(experience.createdAt), + }; + }, + async processEvent() { + const lastCreatedAt = this._getLastCreatedAt(); + let maxCreatedAt = lastCreatedAt; + const params = this.getParams(); + + const { data } = await this.getResourceFn()({ + params, + }); + + const filteredExperiences = data.filter((experience) => { + const createdAt = experience.createdAt; + return !lastCreatedAt || new Date(createdAt) > new Date(lastCreatedAt); + }); + + filteredExperiences.forEach((experience) => { + const createdAt = experience.createdAt; + if (!maxCreatedAt || new Date(createdAt) > new Date(maxCreatedAt)) { + maxCreatedAt = createdAt; + } + const meta = this.generateMeta(experience); + this.$emit(experience, meta); + }); + + if (maxCreatedAt) { + this._setLastCreatedAt(maxCreatedAt); + } + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/experience-created/test-event.mjs b/components/xola/sources/experience-created/test-event.mjs new file mode 100644 index 0000000000000..a9274c1060e78 --- /dev/null +++ b/components/xola/sources/experience-created/test-event.mjs @@ -0,0 +1,12 @@ +export default { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + description: "Explore the historic downtown area", + duration: 120, + price: 5000, + currency: "USD", + category: "Tours", + status: "active", + createdAt: "2024-01-15T08:00:00Z", + updatedAt: "2024-01-15T08:00:00Z", +}; diff --git a/components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs b/components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs new file mode 100644 index 0000000000000..625d6e9a82084 --- /dev/null +++ b/components/xola/sources/experience-deleted-instant/experience-deleted-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-experience-deleted-instant", + name: "Experience Deleted (Instant)", + description: "Emit new event when an experience is deleted. [See the documentation](https://developers.xola.com/reference/webhook-introduction)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return "experience.delete"; + }, + generateMeta(body) { + const { data } = body; + return { + id: `${data.id}-deleted-${Date.now()}`, + summary: `Experience Deleted: ${data.name || data.id}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/experience-deleted-instant/test-event.mjs b/components/xola/sources/experience-deleted-instant/test-event.mjs new file mode 100644 index 0000000000000..557cb5777409b --- /dev/null +++ b/components/xola/sources/experience-deleted-instant/test-event.mjs @@ -0,0 +1,8 @@ +export default { + eventName: "experience.delete", + data: { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + }, + audit: {}, +}; diff --git a/components/xola/sources/experience-deleted/experience-deleted.mjs b/components/xola/sources/experience-deleted/experience-deleted.mjs new file mode 100644 index 0000000000000..a3f6652cfd1a4 --- /dev/null +++ b/components/xola/sources/experience-deleted/experience-deleted.mjs @@ -0,0 +1,64 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-experience-deleted", + name: "Experience Deleted", + description: "Emit new event when an experience is deleted. Note: This source tracks experiences by comparing current list with previous snapshots. [See the documentation](https://xola.github.io/xola-docs/#tag/experiences/operation/listExperiences)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.app.listExperiences; + }, + getParams() { + return { + limit: 100, + skip: 0, + }; + }, + _getPreviousExperienceIds() { + return this.db.get("previousExperienceIds") || []; + }, + _setPreviousExperienceIds(ids) { + this.db.set("previousExperienceIds", ids); + }, + generateMeta(experience) { + return { + id: `${experience.id}-deleted-${Date.now()}`, + summary: `Experience Deleted: ${experience.name || experience.id}`, + ts: Date.now(), + }; + }, + async processEvent() { + const previousIds = this._getPreviousExperienceIds(); + const params = this.getParams(); + + const { data } = await this.getResourceFn()({ + params, + }); + + const currentIds = data.map((experience) => experience.id); + + if (previousIds.length > 0) { + const deletedIds = previousIds.filter((id) => !currentIds.includes(id)); + + deletedIds.forEach((id) => { + const deletedExperience = { + id, + name: "Deleted Experience", + deletedAt: new Date().toISOString(), + }; + const meta = this.generateMeta(deletedExperience); + this.$emit(deletedExperience, meta); + }); + } + + this._setPreviousExperienceIds(currentIds); + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/experience-deleted/test-event.mjs b/components/xola/sources/experience-deleted/test-event.mjs new file mode 100644 index 0000000000000..d733571eb4295 --- /dev/null +++ b/components/xola/sources/experience-deleted/test-event.mjs @@ -0,0 +1,5 @@ +export default { + id: "507f1f77bcf86cd799439012", + name: "Deleted Experience", + deletedAt: "2024-01-17T10:00:00Z", +}; diff --git a/components/xola/sources/experience-updated-instant/experience-updated-instant.mjs b/components/xola/sources/experience-updated-instant/experience-updated-instant.mjs new file mode 100644 index 0000000000000..a846891871782 --- /dev/null +++ b/components/xola/sources/experience-updated-instant/experience-updated-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-experience-updated-instant", + name: "Experience Updated (Instant)", + description: "Emit new event when an experience is updated. [See the documentation](https://developers.xola.com/reference/webhook-introduction)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return "experience.update"; + }, + generateMeta(body) { + const { data } = body; + return { + id: `${data.id}-${Date.now()}`, + summary: `Experience Updated: ${data.name}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/experience-updated-instant/test-event.mjs b/components/xola/sources/experience-updated-instant/test-event.mjs new file mode 100644 index 0000000000000..86dd818610432 --- /dev/null +++ b/components/xola/sources/experience-updated-instant/test-event.mjs @@ -0,0 +1,29 @@ +export default { + eventName: "experience.update", + data: { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + description: "Explore the historic downtown area with expert guides", + duration: 150, + price: 5500, + currency: "USD", + category: "Tours", + status: "active", + createdAt: "2024-01-15T08:00:00Z", + updatedAt: "2024-01-16T11:30:00Z", + }, + audit: { + description: { + old: "Explore the historic downtown area", + new: "Explore the historic downtown area with expert guides", + }, + duration: { + old: 120, + new: 150, + }, + price: { + old: 5000, + new: 5500, + }, + }, +}; diff --git a/components/xola/sources/experience-updated/experience-updated.mjs b/components/xola/sources/experience-updated/experience-updated.mjs new file mode 100644 index 0000000000000..4eaea062b3da1 --- /dev/null +++ b/components/xola/sources/experience-updated/experience-updated.mjs @@ -0,0 +1,62 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-experience-updated", + name: "Experience Updated", + description: "Emit new event when an experience is updated. [See the documentation](https://xola.github.io/xola-docs/#tag/experiences/operation/listExperiences)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.app.listExperiences; + }, + getParams() { + return { + limit: 100, + skip: 0, + }; + }, + generateMeta(experience) { + return { + id: `${experience.id}-${experience.updatedAt}`, + summary: `Experience Updated: ${experience.name}`, + ts: Date.parse(experience.updatedAt), + }; + }, + async processEvent() { + const lastUpdatedAt = this._getLastUpdatedAt(); + let maxUpdatedAt = lastUpdatedAt; + const params = this.getParams(); + + const { data } = await this.getResourceFn()({ + params, + }); + + const filteredExperiences = data.filter((experience) => { + const updatedAt = experience.updatedAt; + const createdAt = experience.createdAt; + return updatedAt !== createdAt + && experience.status !== "deleted" + && (!lastUpdatedAt || new Date(updatedAt) > new Date(lastUpdatedAt)); + }); + + filteredExperiences.forEach((experience) => { + const updatedAt = experience.updatedAt; + if (!maxUpdatedAt || new Date(updatedAt) > new Date(maxUpdatedAt)) { + maxUpdatedAt = updatedAt; + } + const meta = this.generateMeta(experience); + this.$emit(experience, meta); + }); + + if (maxUpdatedAt) { + this._setLastUpdatedAt(maxUpdatedAt); + } + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/experience-updated/test-event.mjs b/components/xola/sources/experience-updated/test-event.mjs new file mode 100644 index 0000000000000..15148dfa34c29 --- /dev/null +++ b/components/xola/sources/experience-updated/test-event.mjs @@ -0,0 +1,12 @@ +export default { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + description: "Explore the historic downtown area with expert guides", + duration: 150, + price: 5500, + currency: "USD", + category: "Tours", + status: "active", + createdAt: "2024-01-15T08:00:00Z", + updatedAt: "2024-01-16T11:30:00Z", +}; diff --git a/components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs b/components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs new file mode 100644 index 0000000000000..633c77a0b52f5 --- /dev/null +++ b/components/xola/sources/new-experience-created-instant/new-experience-created-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-new-experience-created-instant", + name: "New Experience Created (Instant)", + description: "Emit new event when a new experience is created. [See the documentation](https://developers.xola.com/reference/webhook-introduction)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return "experience.create"; + }, + generateMeta(body) { + const { data } = body; + return { + id: data.id, + summary: `New Experience Created: ${data.name}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/new-experience-created-instant/test-event.mjs b/components/xola/sources/new-experience-created-instant/test-event.mjs new file mode 100644 index 0000000000000..e7c41a0316b41 --- /dev/null +++ b/components/xola/sources/new-experience-created-instant/test-event.mjs @@ -0,0 +1,16 @@ +export default { + eventName: "experience.create", + data: { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + description: "Explore the historic downtown area", + duration: 120, + price: 5000, + currency: "USD", + category: "Tours", + status: "active", + createdAt: "2024-01-15T08:00:00Z", + updatedAt: "2024-01-15T08:00:00Z", + }, + audit: {}, +}; diff --git a/components/xola/sources/new-order-created-instant/new-order-created-instant.mjs b/components/xola/sources/new-order-created-instant/new-order-created-instant.mjs new file mode 100644 index 0000000000000..97a66735ba739 --- /dev/null +++ b/components/xola/sources/new-order-created-instant/new-order-created-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-new-order-created-instant", + name: "New Order Created (Instant)", + description: "Emit new event when a new order is created. [See the documentation](https://developers.xola.com/reference/webhook-introduction)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return "order.create"; + }, + generateMeta(body) { + const { data } = body; + return { + id: data.id, + summary: `New Order Created: ${data.id}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/new-order-created-instant/test-event.mjs b/components/xola/sources/new-order-created-instant/test-event.mjs new file mode 100644 index 0000000000000..7f84aef1f813e --- /dev/null +++ b/components/xola/sources/new-order-created-instant/test-event.mjs @@ -0,0 +1,21 @@ +export default { + eventName: "order.create", + data: { + id: "507f1f77bcf86cd799439011", + status: "active", + amount: 12000, + currency: "USD", + experience: { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + }, + user: { + id: "507f1f77bcf86cd799439013", + name: "John Doe", + email: "john@example.com", + }, + createdAt: "2024-01-15T10:30:00Z", + updatedAt: "2024-01-15T10:30:00Z", + }, + audit: {}, +}; diff --git a/components/xola/sources/new-purchase-created/new-purchase-created.mjs b/components/xola/sources/new-purchase-created/new-purchase-created.mjs new file mode 100644 index 0000000000000..5deb3caa1bb16 --- /dev/null +++ b/components/xola/sources/new-purchase-created/new-purchase-created.mjs @@ -0,0 +1,59 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-new-purchase-created", + name: "New Purchase Created", + description: "Emit new event when a new purchase is created. [See the documentation](https://xola.github.io/xola-docs/#tag/purchases/operation/listPurchases)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.app.listPurchases; + }, + getParams() { + return { + limit: 100, + skip: 0, + }; + }, + generateMeta(purchase) { + return { + id: purchase.id, + summary: `New Purchase: ${purchase.id}`, + ts: Date.parse(purchase.createdAt), + }; + }, + async processEvent() { + const lastCreatedAt = this._getLastCreatedAt(); + let maxCreatedAt = lastCreatedAt; + const params = this.getParams(); + + const { data } = await this.getResourceFn()({ + params, + }); + + const filteredPurchases = data.filter((purchase) => { + const createdAt = purchase.createdAt; + return !lastCreatedAt || new Date(createdAt) > new Date(lastCreatedAt); + }); + + filteredPurchases.forEach((purchase) => { + const createdAt = purchase.createdAt; + if (!maxCreatedAt || new Date(createdAt) > new Date(maxCreatedAt)) { + maxCreatedAt = createdAt; + } + const meta = this.generateMeta(purchase); + this.$emit(purchase, meta); + }); + + if (maxCreatedAt) { + this._setLastCreatedAt(maxCreatedAt); + } + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/new-purchase-created/test-event.mjs b/components/xola/sources/new-purchase-created/test-event.mjs new file mode 100644 index 0000000000000..5c99c92e60561 --- /dev/null +++ b/components/xola/sources/new-purchase-created/test-event.mjs @@ -0,0 +1,17 @@ +export default { + id: "507f1f77bcf86cd799439011", + status: "active", + amount: 12000, + currency: "USD", + experience: { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + }, + user: { + id: "507f1f77bcf86cd799439013", + name: "John Doe", + email: "john@example.com", + }, + createdAt: "2024-01-15T10:30:00Z", + updatedAt: "2024-01-15T10:30:00Z", +}; diff --git a/components/xola/sources/order-deleted-instant/order-deleted-instant.mjs b/components/xola/sources/order-deleted-instant/order-deleted-instant.mjs new file mode 100644 index 0000000000000..5cd50e56375dc --- /dev/null +++ b/components/xola/sources/order-deleted-instant/order-deleted-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-order-deleted-instant", + name: "Order Deleted (Instant)", + description: "Emit new event when an order is deleted. [See the documentation](https://developers.xola.com/reference/webhook-introduction)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return "order.delete"; + }, + generateMeta(body) { + const { data } = body; + return { + id: `${data.id}-deleted-${Date.now()}`, + summary: `Order Deleted: ${data.id}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/order-deleted-instant/test-event.mjs b/components/xola/sources/order-deleted-instant/test-event.mjs new file mode 100644 index 0000000000000..022571f7466ca --- /dev/null +++ b/components/xola/sources/order-deleted-instant/test-event.mjs @@ -0,0 +1,8 @@ +export default { + eventName: "order.delete", + data: { + id: "507f1f77bcf86cd799439011", + status: "canceled", + }, + audit: {}, +}; diff --git a/components/xola/sources/order-updated-instant/order-updated-instant.mjs b/components/xola/sources/order-updated-instant/order-updated-instant.mjs new file mode 100644 index 0000000000000..f64f24040557c --- /dev/null +++ b/components/xola/sources/order-updated-instant/order-updated-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-order-updated-instant", + name: "Order Updated (Instant)", + description: "Emit new event when an order is updated. [See the documentation](https://developers.xola.com/reference/webhook-introduction)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return "order.update"; + }, + generateMeta(body) { + const { data } = body; + return { + id: `${data.id}-${Date.now()}`, + summary: `Order Updated: ${data.id}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/order-updated-instant/test-event.mjs b/components/xola/sources/order-updated-instant/test-event.mjs new file mode 100644 index 0000000000000..9982edfad9aac --- /dev/null +++ b/components/xola/sources/order-updated-instant/test-event.mjs @@ -0,0 +1,26 @@ +export default { + eventName: "order.update", + data: { + id: "507f1f77bcf86cd799439011", + status: "confirmed", + amount: 12000, + currency: "USD", + experience: { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + }, + user: { + id: "507f1f77bcf86cd799439013", + name: "John Doe", + email: "john@example.com", + }, + createdAt: "2024-01-15T10:30:00Z", + updatedAt: "2024-01-15T14:45:00Z", + }, + audit: { + status: { + old: "active", + new: "confirmed", + }, + }, +}; diff --git a/components/xola/sources/purchase-canceled/purchase-canceled.mjs b/components/xola/sources/purchase-canceled/purchase-canceled.mjs new file mode 100644 index 0000000000000..4a12214055ad3 --- /dev/null +++ b/components/xola/sources/purchase-canceled/purchase-canceled.mjs @@ -0,0 +1,58 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-purchase-canceled", + name: "Purchase Canceled", + description: "Emit new event when a purchase is canceled. [See the documentation](https://xola.github.io/xola-docs/#tag/purchases/operation/listPurchases)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.app.listPurchases; + }, + getParams() { + return { + limit: 100, + skip: 0, + status: "canceled", + }; + }, + generateMeta(purchase) { + return { + id: purchase.id, + summary: `Purchase Canceled: ${purchase.id}`, + ts: Date.parse(purchase.updatedAt), + }; + }, + async processEvent() { + const processedIds = this._getProcessedIds(); + const newProcessedIds = []; + const params = this.getParams(); + + const { data } = await this.getResourceFn()({ + params, + }); + + const filteredPurchases = data.filter((purchase) => { + return !processedIds.includes(purchase.id); + }); + + filteredPurchases.forEach((purchase) => { + newProcessedIds.push(purchase.id); + const meta = this.generateMeta(purchase); + this.$emit(purchase, meta); + }); + + const updatedProcessedIds = [ + ...processedIds, + ...newProcessedIds, + ].slice(-1000); + this._setProcessedIds(updatedProcessedIds); + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/purchase-canceled/test-event.mjs b/components/xola/sources/purchase-canceled/test-event.mjs new file mode 100644 index 0000000000000..1ada32d99760e --- /dev/null +++ b/components/xola/sources/purchase-canceled/test-event.mjs @@ -0,0 +1,17 @@ +export default { + id: "507f1f77bcf86cd799439011", + status: "canceled", + amount: 12000, + currency: "USD", + experience: { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + }, + user: { + id: "507f1f77bcf86cd799439013", + name: "John Doe", + email: "john@example.com", + }, + createdAt: "2024-01-15T10:30:00Z", + updatedAt: "2024-01-16T09:15:00Z", +}; diff --git a/components/xola/sources/purchase-updated/purchase-updated.mjs b/components/xola/sources/purchase-updated/purchase-updated.mjs new file mode 100644 index 0000000000000..e31fcfbdc2555 --- /dev/null +++ b/components/xola/sources/purchase-updated/purchase-updated.mjs @@ -0,0 +1,62 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-purchase-updated", + name: "Purchase Updated", + description: "Emit new event when a purchase is updated. [See the documentation](https://xola.github.io/xola-docs/#tag/purchases/operation/listPurchases)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getResourceFn() { + return this.app.listPurchases; + }, + getParams() { + return { + limit: 100, + skip: 0, + }; + }, + generateMeta(purchase) { + return { + id: `${purchase.id}-${purchase.updatedAt}`, + summary: `Purchase Updated: ${purchase.id}`, + ts: Date.parse(purchase.updatedAt), + }; + }, + async processEvent() { + const lastUpdatedAt = this._getLastUpdatedAt(); + let maxUpdatedAt = lastUpdatedAt; + const params = this.getParams(); + + const { data } = await this.getResourceFn()({ + params, + }); + + const filteredPurchases = data.filter((purchase) => { + const updatedAt = purchase.updatedAt; + const createdAt = purchase.createdAt; + return updatedAt !== createdAt + && purchase.status !== "deleted" + && (!lastUpdatedAt || new Date(updatedAt) > new Date(lastUpdatedAt)); + }); + + filteredPurchases.forEach((purchase) => { + const updatedAt = purchase.updatedAt; + if (!maxUpdatedAt || new Date(updatedAt) > new Date(maxUpdatedAt)) { + maxUpdatedAt = updatedAt; + } + const meta = this.generateMeta(purchase); + this.$emit(purchase, meta); + }); + + if (maxUpdatedAt) { + this._setLastUpdatedAt(maxUpdatedAt); + } + }, + }, + sampleEmit, +}; diff --git a/components/xola/sources/purchase-updated/test-event.mjs b/components/xola/sources/purchase-updated/test-event.mjs new file mode 100644 index 0000000000000..b238681e5d919 --- /dev/null +++ b/components/xola/sources/purchase-updated/test-event.mjs @@ -0,0 +1,17 @@ +export default { + id: "507f1f77bcf86cd799439011", + status: "active", + amount: 12000, + currency: "USD", + experience: { + id: "507f1f77bcf86cd799439012", + name: "City Walking Tour", + }, + user: { + id: "507f1f77bcf86cd799439013", + name: "John Doe", + email: "john@example.com", + }, + createdAt: "2024-01-15T10:30:00Z", + updatedAt: "2024-01-15T14:45:00Z", +}; diff --git a/components/xola/sources/user-updated-instant/test-event.mjs b/components/xola/sources/user-updated-instant/test-event.mjs new file mode 100644 index 0000000000000..1c9abba3edbb3 --- /dev/null +++ b/components/xola/sources/user-updated-instant/test-event.mjs @@ -0,0 +1,17 @@ +export default { + eventName: "user.update", + data: { + id: "507f1f77bcf86cd799439013", + name: "John Doe", + email: "john@example.com", + phone: "+1234567890", + country: "US", + updatedAt: "2024-01-15T16:20:00Z", + }, + audit: { + phone: { + old: null, + new: "+1234567890", + }, + }, +}; diff --git a/components/xola/sources/user-updated-instant/user-updated-instant.mjs b/components/xola/sources/user-updated-instant/user-updated-instant.mjs new file mode 100644 index 0000000000000..fbd4b8db69be2 --- /dev/null +++ b/components/xola/sources/user-updated-instant/user-updated-instant.mjs @@ -0,0 +1,27 @@ +import common from "../common/webhook.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "xola-user-updated-instant", + name: "User Updated (Instant)", + description: "Emit new event when a user is updated. [See the documentation](https://developers.xola.com/reference/webhook-introduction)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventName() { + return "user.update"; + }, + generateMeta(body) { + const { data } = body; + return { + id: `${data.id}-${Date.now()}`, + summary: `User Updated: ${data.name || data.email}`, + ts: Date.now(), + }; + }, + }, + sampleEmit, +}; diff --git a/components/xola/xola.app.mjs b/components/xola/xola.app.mjs index 448e77fc7256d..c30166d37fad1 100644 --- a/components/xola/xola.app.mjs +++ b/components/xola/xola.app.mjs @@ -1,11 +1,211 @@ +import { axios } from "@pipedream/platform"; +import constants from "./common/constants.mjs"; + export default { type: "app", app: "xola", - propDefinitions: {}, + propDefinitions: { + experienceId: { + type: "string", + label: "Experience ID", + description: "The unique identifier of the experience", + async options({ page }) { + const { data } = await this.listExperiences({ + params: { + limit: constants.DEFAULT_LIMIT, + skip: page * constants.DEFAULT_LIMIT, + }, + }); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + eventId: { + type: "string", + label: "Event ID", + description: "The unique identifier of the event", + async options({ page }) { + const { data } = await this.listEvents({ + params: { + limit: constants.DEFAULT_LIMIT, + skip: page * constants.DEFAULT_LIMIT, + }, + }); + return data.map(({ + id: value, title: label, + }) => ({ + label, + value, + })); + }, + }, + scheduleId: { + type: "string", + label: "Schedule ID", + description: "The unique identifier of the schedule", + }, + guideId: { + type: "string", + label: "Guide ID", + description: "The unique identifier of the guide", + async options({ page }) { + const { data } = await this.listGuides({ + params: { + limit: constants.DEFAULT_LIMIT, + skip: page * constants.DEFAULT_LIMIT, + }, + }); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, + }, + userId: { + type: "string", + label: "User ID", + description: "The unique identifier of the user. If not provided, will use the authenticated user's ID.", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + getUrl(path) { + const { api_url: apiUrl } = this.$auth; + return `${apiUrl}${constants.VERSION_PATH}${path}`; + }, + getHeaders(headers) { + return { + "x-api-key": this.$auth.api_key, + "x-api-version": constants.API_VERSION, + ...headers, + }; + }, + makeRequest({ + $ = this, path, headers, ...opts + }) { + return axios($, { + url: this.getUrl(path), + headers: this.getHeaders(headers), + ...opts, + }); + }, + post(args = {}) { + return this.makeRequest({ + method: "POST", + ...args, + }); + }, + put(args = {}) { + return this.makeRequest({ + method: "PUT", + ...args, + }); + }, + patch(args = {}) { + return this.makeRequest({ + method: "PATCH", + ...args, + }); + }, + delete(args = {}) { + return this.makeRequest({ + method: "DELETE", + ...args, + }); + }, + listExperiences(args = {}) { + return this.makeRequest({ + path: "/experiences", + ...args, + }); + }, + listGuides(args = {}) { + return this.makeRequest({ + path: "/guides", + ...args, + }); + }, + listEvents(args = {}) { + return this.makeRequest({ + path: "/events", + ...args, + }); + }, + listPurchases(args = {}) { + return this.makeRequest({ + path: "/purchases", + ...args, + }); + }, + createExperienceSchedule({ + experienceId, ...args + }) { + return this.post({ + path: `/experiences/${experienceId}/schedules`, + ...args, + }); + }, + updateExperienceSchedule({ + experienceId, scheduleId, ...args + }) { + return this.put({ + path: `/experiences/${experienceId}/schedules/${scheduleId}`, + ...args, + }); + }, + deleteExperienceSchedule({ + experienceId, scheduleId, ...args + }) { + return this.delete({ + path: `/experiences/${experienceId}/schedules/${scheduleId}`, + ...args, + }); + }, + patchEvent({ + eventId, ...args + }) { + return this.patch({ + path: `/events/${eventId}`, + ...args, + }); + }, + addEventGuide({ + eventId, ...args + }) { + return this.post({ + path: `/events/${eventId}/guides`, + ...args, + }); + }, + removeEventGuide({ + eventId, guideId, ...args + }) { + return this.delete({ + path: `/events/${eventId}/guides/${guideId}`, + ...args, + }); + }, + createWebhook({ + userId, ...args + }) { + return this.post({ + path: `/users/${userId}/hooks`, + ...args, + }); + }, + deleteWebhook({ + userId, hookId, ...args + }) { + return this.delete({ + path: `/users/${userId}/hooks/${hookId}`, + ...args, + }); }, }, -}; \ No newline at end of file +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd4bc8502fc26..d1d58325ddd47 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16175,7 +16175,11 @@ importers: specifier: ^3.1.0 version: 3.1.0 - components/xola: {} + components/xola: + dependencies: + '@pipedream/platform': + specifier: ^3.1.0 + version: 3.1.0 components/xperiencify: dependencies: