From a31d6d139d43bb300eaf723f7a3834beb81f2cce Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Mon, 2 Jun 2025 17:51:21 -0300 Subject: [PATCH 1/7] limoexpress init --- .../actions/assign-driver/assign-driver.mjs | 41 +++++ .../actions/cancel-booking/cancel-booking.mjs | 35 +++++ .../actions/create-booking/create-booking.mjs | 71 +++++++++ components/limoexpress/limoexpress.app.mjs | 141 +++++++++++++++++- components/limoexpress/package.json | 2 +- .../new-booking-instant.mjs | 77 ++++++++++ .../new-cancellation-instant.mjs | 74 +++++++++ .../new-driver-assignment-instant.mjs | 67 +++++++++ 8 files changed, 504 insertions(+), 4 deletions(-) create mode 100644 components/limoexpress/actions/assign-driver/assign-driver.mjs create mode 100644 components/limoexpress/actions/cancel-booking/cancel-booking.mjs create mode 100644 components/limoexpress/actions/create-booking/create-booking.mjs create mode 100644 components/limoexpress/sources/new-booking-instant/new-booking-instant.mjs create mode 100644 components/limoexpress/sources/new-cancellation-instant/new-cancellation-instant.mjs create mode 100644 components/limoexpress/sources/new-driver-assignment-instant/new-driver-assignment-instant.mjs diff --git a/components/limoexpress/actions/assign-driver/assign-driver.mjs b/components/limoexpress/actions/assign-driver/assign-driver.mjs new file mode 100644 index 0000000000000..f5ba4237c9936 --- /dev/null +++ b/components/limoexpress/actions/assign-driver/assign-driver.mjs @@ -0,0 +1,41 @@ +import limoexpress from "../../limoexpress.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "limoexpress-assign-driver", + name: "Assign Driver", + description: "Assign a driver to a specific ride. [See the documentation](https://api.limoexpress.me/api/docs/v1)", + version: "0.0.1", + type: "action", + props: { + limoexpress, + bookingId: { + propDefinition: [ + limoexpress, + "bookingId", + ], + }, + driverId: { + propDefinition: [ + limoexpress, + "driverId", + ], + }, + assignmentNotes: { + propDefinition: [ + limoexpress, + "assignmentNotes", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.limoexpress.assignDriver({ + bookingId: this.bookingId, + driverId: this.driverId, + assignmentNotes: this.assignmentNotes, + }); + $.export("$summary", `Successfully assigned driver ID ${this.driverId} to booking ID ${this.bookingId}`); + return response; + }, +}; diff --git a/components/limoexpress/actions/cancel-booking/cancel-booking.mjs b/components/limoexpress/actions/cancel-booking/cancel-booking.mjs new file mode 100644 index 0000000000000..b0d3e44b06984 --- /dev/null +++ b/components/limoexpress/actions/cancel-booking/cancel-booking.mjs @@ -0,0 +1,35 @@ +import limoexpress from "../../limoexpress.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "limoexpress-cancel-booking", + name: "Cancel Booking", + description: "Cancel an existing booking using its ID. [See the documentation](https://api.limoexpress.me/api/docs/v1)", + version: "0.0.1", // Use this due to the lack of template tag functionality + type: "action", + props: { + limoexpress, + bookingId: { + propDefinition: [ + limoexpress, + "bookingId", + ], + }, + cancellationReason: { + propDefinition: [ + limoexpress, + "cancellationReason", + ], + optional: true, + }, + }, + async run({ $ }) { + const response = await this.limoexpress.cancelBooking({ + bookingId: this.bookingId, + cancellationReason: this.cancellationReason, + }); + + $.export("$summary", `Successfully canceled booking with ID: ${this.bookingId}`); + return response; + }, +}; diff --git a/components/limoexpress/actions/create-booking/create-booking.mjs b/components/limoexpress/actions/create-booking/create-booking.mjs new file mode 100644 index 0000000000000..7b10be01e6fcb --- /dev/null +++ b/components/limoexpress/actions/create-booking/create-booking.mjs @@ -0,0 +1,71 @@ +import limoexpress from "../../limoexpress.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "limoexpress-create-booking", + name: "Create Limo Booking", + description: "Creates a new limo booking with specified details. [See the documentation](https://api.limoexpress.me/api/docs/v1)", + version: "0.0.1", + type: "action", + props: { + limoexpress, + bookingTypeId: { + propDefinition: [ + limoexpress, + "bookingTypeId", + ], + }, + bookingStatusId: { + propDefinition: [ + limoexpress, + "bookingStatusId", + ], + optional: true, + }, + fromLocation: { + propDefinition: [ + limoexpress, + "fromLocation", + ], + }, + pickupTime: { + propDefinition: [ + limoexpress, + "pickupTime", + ], + }, + customerId: { + type: "string", + label: "Customer ID", + description: "ID of the customer for the booking.", + }, + toLocation: { + type: "string", + label: "To Location", + description: "The dropoff location.", + optional: true, + }, + vehicleId: { + type: "string", + label: "Vehicle ID", + description: "ID of the vehicle to be used for the booking.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.limoexpress.createBooking({ + bookingTypeId: this.bookingTypeId, + fromLocation: this.fromLocation, + pickupTime: this.pickupTime, + additionalFields: { + booking_status_id: this.bookingStatusId, + customer_id: this.customerId, + to_location: this.toLocation, + vehicle_id: this.vehicleId, + }, + }); + + $.export("$summary", `Successfully created booking with ID ${response.id}`); + return response; + }, +}; diff --git a/components/limoexpress/limoexpress.app.mjs b/components/limoexpress/limoexpress.app.mjs index a1d9fee4e6bbf..ab1065cff9dd5 100644 --- a/components/limoexpress/limoexpress.app.mjs +++ b/components/limoexpress/limoexpress.app.mjs @@ -1,11 +1,146 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "limoexpress", - propDefinitions: {}, + propDefinitions: { + bookingTypeId: { + type: "string", + label: "Booking Type ID", + description: "ID of the booking type.", + async options() { + const types = await this.listBookingTypes(); + return types.map((type) => ({ + label: type.name, + value: type.id, + })); + }, + }, + bookingStatusId: { + type: "string", + label: "Booking Status ID", + description: "ID of the booking status.", + async options() { + const statuses = await this.listBookingStatuses(); + return statuses.map((status) => ({ + label: status.name, + value: status.id, + })); + }, + }, + bookingId: { + type: "string", + label: "Booking ID", + description: "The ID of the booking.", + }, + driverId: { + type: "string", + label: "Driver ID", + description: "The ID of the driver to assign to the booking.", + }, + fromLocation: { + type: "string", + label: "From Location", + description: "The pickup location.", + }, + pickupTime: { + type: "string", + label: "Pickup Time", + description: "The time scheduled for pickup.", + }, + cancellationReason: { + type: "string", + label: "Cancellation Reason", + description: "Reason for canceling the booking.", + optional: true, + }, + assignmentNotes: { + type: "string", + label: "Assignment Notes", + description: "Additional notes for the driver assignment.", + optional: true, + }, + }, methods: { - // this.$auth contains connected account data authKeys() { console.log(Object.keys(this.$auth)); }, + _baseUrl() { + return "https://api.limoexpress.me/api/v1"; + }, + async _makeRequest(opts = {}) { + const { + $ = this, method = "GET", path = "/", headers, ...otherOpts + } = opts; + return axios($, { + ...otherOpts, + method, + url: this._baseUrl() + path, + headers: { + ...headers, + Authorization: `Bearer ${this.$auth.oauth_access_token}`, + }, + }); + }, + async listBookingTypes(opts = {}) { + return this._makeRequest({ + path: "/booking_types", + ...opts, + }); + }, + async listBookingStatuses(opts = {}) { + return this._makeRequest({ + path: "/booking_statuses", + ...opts, + }); + }, + async createBooking({ + bookingTypeId, fromLocation, pickupTime, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: "/bookings", + data: { + booking_type_id: bookingTypeId, + from_location: fromLocation, + pickup_time: pickupTime, + }, + ...opts, + }); + }, + async cancelBooking({ + bookingId, cancellationReason, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/bookings/${bookingId}/cancel`, + data: { + cancellation_reason: cancellationReason, + }, + ...opts, + }); + }, + async assignDriver({ + bookingId, driverId, assignmentNotes, ...opts + }) { + return this._makeRequest({ + method: "POST", + path: `/bookings/${bookingId}/assign_driver`, + data: { + driver_id: driverId, + assignment_notes: assignmentNotes, + }, + ...opts, + }); + }, + async emitNewBookingEvent() { + // Implementation for emitting new booking event + }, + async emitCancelledBookingEvent() { + // Implementation for emitting canceled booking event + }, + async emitDriverAssignedEvent() { + // Implementation for emitting driver assigned event + }, }, -}; \ No newline at end of file +}; diff --git a/components/limoexpress/package.json b/components/limoexpress/package.json index a5e6a9b1bcf25..f0ff0060e7882 100644 --- a/components/limoexpress/package.json +++ b/components/limoexpress/package.json @@ -12,4 +12,4 @@ "publishConfig": { "access": "public" } -} \ No newline at end of file +} diff --git a/components/limoexpress/sources/new-booking-instant/new-booking-instant.mjs b/components/limoexpress/sources/new-booking-instant/new-booking-instant.mjs new file mode 100644 index 0000000000000..90ca4d4c7e66e --- /dev/null +++ b/components/limoexpress/sources/new-booking-instant/new-booking-instant.mjs @@ -0,0 +1,77 @@ +import limoexpress from "../../limoexpress.app.mjs"; +import { + axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, +} from "@pipedream/platform"; + +export default { + key: "limoexpress-new-booking-instant", + name: "New Limo Booking Created", + description: "Emit new event when a customer creates a new limo booking. [See the documentation](https://api.limoexpress.me/api/docs/v1)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + limoexpress, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastBookingTime() { + return this.db.get("lastBookingTime"); + }, + _setLastBookingTime(time) { + this.db.set("lastBookingTime", time); + }, + async fetchNewBookings(since) { + return await this.limoexpress._makeRequest({ + path: "/bookings", + params: { + created_after: since, + }, + }); + }, + }, + hooks: { + async deploy() { + const { results } = await this.limoexpress._makeRequest({ + path: "/bookings", + }); + + results.slice(0, 50).forEach((booking) => { + this.$emit(booking, { + id: booking.id, + summary: `New Booking: ${booking.id}`, + ts: new Date(booking.created_at).getTime(), + }); + }); + + if (results.length) { + this._setLastBookingTime(results[0].created_at); + } + }, + }, + async run() { + const lastBookingTime = this._getLastBookingTime(); + const { results: newBookings } = await this.fetchNewBookings(lastBookingTime); + + newBookings.forEach((booking) => { + const ts = new Date(booking.created_at).getTime(); + if (ts > new Date(lastBookingTime).getTime()) { + this.$emit(booking, { + id: booking.id, + summary: `New Booking: ${booking.id}`, + ts, + }); + } + }); + + if (newBookings.length) { + this._setLastBookingTime(newBookings[0].created_at); + } + }, +}; diff --git a/components/limoexpress/sources/new-cancellation-instant/new-cancellation-instant.mjs b/components/limoexpress/sources/new-cancellation-instant/new-cancellation-instant.mjs new file mode 100644 index 0000000000000..bcfdb2c4bf4fd --- /dev/null +++ b/components/limoexpress/sources/new-cancellation-instant/new-cancellation-instant.mjs @@ -0,0 +1,74 @@ +import { axios } from "@pipedream/platform"; +import limoexpress from "../../limoexpress.app.mjs"; + +export default { + key: "limoexpress-new-cancellation-instant", + name: "New Booking Cancellation", + description: "Emit new event when a booking is canceled. [See the documentation](https://api.limoexpress.me/api/docs/v1)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + limoexpress, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 60, + }, + }, + }, + methods: { + _getLastTimestamp() { + return this.db.get("lastTimestamp") || 0; + }, + _setLastTimestamp(ts) { + this.db.set("lastTimestamp", ts); + }, + async _makeRequest(opts = {}) { + return this.limoexpress._makeRequest(opts); + }, + async fetchCancellations(since) { + return this._makeRequest({ + path: "/cancellations", + method: "GET", + params: { + since, + }, + }); + }, + }, + hooks: { + async deploy() { + const currentTime = new Date().getTime(); + const cancellations = await this.fetchCancellations(currentTime - 86400000); // fetch cancellations from the last 24 hours + cancellations.slice(0, 50).forEach((cancellation) => { + this.$emit(cancellation, { + id: cancellation.id, + summary: `New Cancellation: ${cancellation.id}`, + ts: new Date(cancellation.cancelled_at).getTime(), + }); + }); + if (cancellations.length > 0) { + const latestTimestamp = Math.max(...cancellations.map((c) => new Date(c.cancelled_at).getTime())); + this._setLastTimestamp(latestTimestamp); + } + }, + }, + async run() { + const lastTimestamp = this._getLastTimestamp(); + const cancellations = await this.fetchCancellations(lastTimestamp); + + cancellations.forEach((cancellation) => { + const cancellationTime = new Date(cancellation.cancelled_at).getTime(); + if (cancellationTime > lastTimestamp) { + this.$emit(cancellation, { + id: cancellation.id, + summary: `New Cancellation: ${cancellation.id}`, + ts: cancellationTime, + }); + this._setLastTimestamp(cancellationTime); + } + }); + }, +}; diff --git a/components/limoexpress/sources/new-driver-assignment-instant/new-driver-assignment-instant.mjs b/components/limoexpress/sources/new-driver-assignment-instant/new-driver-assignment-instant.mjs new file mode 100644 index 0000000000000..61eeec14b5916 --- /dev/null +++ b/components/limoexpress/sources/new-driver-assignment-instant/new-driver-assignment-instant.mjs @@ -0,0 +1,67 @@ +import limoexpress from "../../limoexpress.app.mjs"; +import { axios } from "@pipedream/platform"; + +export default { + key: "limoexpress-new-driver-assignment-instant", + name: "New Driver Assignment Instant", + description: "Emit new event when a driver is assigned to a limo ride. Useful for dispatch coordination. [See the documentation](https://api.limoexpress.me/api/docs/v1)", + version: "0.0.{{ts}}", + type: "source", + dedupe: "unique", + props: { + limoexpress, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: 15 * 60, // 15 minutes + }, + }, + }, + hooks: { + async deploy() { + this._setLastTimestamp(Date.now()); + }, + async activate() { + // No activation logic needed + }, + async deactivate() { + // No deactivation logic needed + }, + }, + methods: { + _setLastTimestamp(ts) { + this.db.set("lastTimestamp", ts); + }, + _getLastTimestamp() { + return this.db.get("lastTimestamp") || 0; + }, + async fetchDriverAssignments() { + const lastTimestamp = this._getLastTimestamp(); + const now = Date.now(); + + const driverAssignments = await this.limoexpress._makeRequest({ + path: "/driver_assignments", + params: { + since: lastTimestamp, + }, + }); + + this._setLastTimestamp(now); + return driverAssignments; + }, + }, + async run() { + const driverAssignments = await this.fetchDriverAssignments(); + + for (const assignment of driverAssignments) { + this.$emit(assignment, { + id: assignment.id, + summary: `Driver assigned to booking ID: ${assignment.bookingId}`, + ts: assignment.timestamp + ? Date.parse(assignment.timestamp) + : Date.now(), + }); + } + }, +}; From 8b767452109eb2e7e2f7d4b29005f146f83e3cad Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 3 Jun 2025 18:14:56 -0300 Subject: [PATCH 2/7] [Components] limoexpress #16907 Sources - New Booking Actions - Create Booking - Create Client - Mark Invoice As Paid --- .../actions/assign-driver/assign-driver.mjs | 41 --- .../actions/cancel-booking/cancel-booking.mjs | 35 --- .../actions/create-booking/create-booking.mjs | 278 ++++++++++++++++-- .../actions/create-client/create-client.mjs | 68 +++++ .../mark-invoice-paid/mark-invoice-paid.mjs | 29 ++ components/limoexpress/common/constants.mjs | 15 + components/limoexpress/common/utils.mjs | 12 + components/limoexpress/limoexpress.app.mjs | 276 ++++++++++++----- components/limoexpress/package.json | 5 +- .../new-booking-instant.mjs | 77 ----- .../sources/new-booking/new-booking.mjs | 73 +++++ .../sources/new-booking/test-event.mjs | 74 +++++ .../new-cancellation-instant.mjs | 74 ----- .../new-driver-assignment-instant.mjs | 67 ----- 14 files changed, 723 insertions(+), 401 deletions(-) delete mode 100644 components/limoexpress/actions/assign-driver/assign-driver.mjs delete mode 100644 components/limoexpress/actions/cancel-booking/cancel-booking.mjs create mode 100644 components/limoexpress/actions/create-client/create-client.mjs create mode 100644 components/limoexpress/actions/mark-invoice-paid/mark-invoice-paid.mjs create mode 100644 components/limoexpress/common/constants.mjs create mode 100644 components/limoexpress/common/utils.mjs delete mode 100644 components/limoexpress/sources/new-booking-instant/new-booking-instant.mjs create mode 100644 components/limoexpress/sources/new-booking/new-booking.mjs create mode 100644 components/limoexpress/sources/new-booking/test-event.mjs delete mode 100644 components/limoexpress/sources/new-cancellation-instant/new-cancellation-instant.mjs delete mode 100644 components/limoexpress/sources/new-driver-assignment-instant/new-driver-assignment-instant.mjs diff --git a/components/limoexpress/actions/assign-driver/assign-driver.mjs b/components/limoexpress/actions/assign-driver/assign-driver.mjs deleted file mode 100644 index f5ba4237c9936..0000000000000 --- a/components/limoexpress/actions/assign-driver/assign-driver.mjs +++ /dev/null @@ -1,41 +0,0 @@ -import limoexpress from "../../limoexpress.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "limoexpress-assign-driver", - name: "Assign Driver", - description: "Assign a driver to a specific ride. [See the documentation](https://api.limoexpress.me/api/docs/v1)", - version: "0.0.1", - type: "action", - props: { - limoexpress, - bookingId: { - propDefinition: [ - limoexpress, - "bookingId", - ], - }, - driverId: { - propDefinition: [ - limoexpress, - "driverId", - ], - }, - assignmentNotes: { - propDefinition: [ - limoexpress, - "assignmentNotes", - ], - optional: true, - }, - }, - async run({ $ }) { - const response = await this.limoexpress.assignDriver({ - bookingId: this.bookingId, - driverId: this.driverId, - assignmentNotes: this.assignmentNotes, - }); - $.export("$summary", `Successfully assigned driver ID ${this.driverId} to booking ID ${this.bookingId}`); - return response; - }, -}; diff --git a/components/limoexpress/actions/cancel-booking/cancel-booking.mjs b/components/limoexpress/actions/cancel-booking/cancel-booking.mjs deleted file mode 100644 index b0d3e44b06984..0000000000000 --- a/components/limoexpress/actions/cancel-booking/cancel-booking.mjs +++ /dev/null @@ -1,35 +0,0 @@ -import limoexpress from "../../limoexpress.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "limoexpress-cancel-booking", - name: "Cancel Booking", - description: "Cancel an existing booking using its ID. [See the documentation](https://api.limoexpress.me/api/docs/v1)", - version: "0.0.1", // Use this due to the lack of template tag functionality - type: "action", - props: { - limoexpress, - bookingId: { - propDefinition: [ - limoexpress, - "bookingId", - ], - }, - cancellationReason: { - propDefinition: [ - limoexpress, - "cancellationReason", - ], - optional: true, - }, - }, - async run({ $ }) { - const response = await this.limoexpress.cancelBooking({ - bookingId: this.bookingId, - cancellationReason: this.cancellationReason, - }); - - $.export("$summary", `Successfully canceled booking with ID: ${this.bookingId}`); - return response; - }, -}; diff --git a/components/limoexpress/actions/create-booking/create-booking.mjs b/components/limoexpress/actions/create-booking/create-booking.mjs index 7b10be01e6fcb..7cbb0ab9804c0 100644 --- a/components/limoexpress/actions/create-booking/create-booking.mjs +++ b/components/limoexpress/actions/create-booking/create-booking.mjs @@ -1,10 +1,11 @@ +import { PRICE_TYPE_OPTIONS } from "../../common/constants.mjs"; +import { throwError } from "../../common/utils.mjs"; import limoexpress from "../../limoexpress.app.mjs"; -import { axios } from "@pipedream/platform"; export default { key: "limoexpress-create-booking", name: "Create Limo Booking", - description: "Creates a new limo booking with specified details. [See the documentation](https://api.limoexpress.me/api/docs/v1)", + description: "Creates a new limo booking with specified details. [See the documentation](https://api.limoexpress.me/api/docs/v1#/Bookings/createBooking)", version: "0.0.1", type: "action", props: { @@ -22,50 +23,271 @@ export default { ], optional: true, }, - fromLocation: { + fromLocationName: { + type: "string", + label: "From Location Name", + description: "The pickup location name.", + }, + fromLocationFullAddress: { + type: "string", + label: "From Location Full Address", + description: "The pickup location full address.", + }, + fromLocationLatitude: { + type: "string", + label: "From Location Latitude", + description: "The pickup location latitude.", + optional: true, + }, + fromLocationLongitude: { + type: "string", + label: "From Location Longitude", + description: "The pickup location longitude.", + optional: true, + }, + toLocationName: { + type: "string", + label: "To Location Name", + description: "The dropoff location name.", + }, + toLocationFullAddress: { + type: "string", + label: "To Location Full Address", + description: "The dropoff location full address.", + }, + toLocationLatitude: { + type: "string", + label: "To Location Latitude", + description: "The dropoff location latitude.", + optional: true, + }, + toLocationLongitude: { + type: "string", + label: "To Location Longitude", + description: "The dropoff location longitude.", + optional: true, + }, + pickupTime: { + type: "string", + label: "Pickup Time", + description: "The time scheduled for pickup. **Format: YYYY-MM-DD HH:MM:SS**", + }, + expectedDropOffTime: { + type: "string", + label: "Expected Drop Off Time", + description: "The expected drop off time. **Format: YYYY-MM-DD HH:MM:SS**", + optional: true, + }, + expectedComebackTime: { + type: "string", + label: "Expected Comeback Time", + description: "The expected comeback time. **Format: YYYY-MM-DD HH:MM:SS**", + optional: true, + }, + vehicleClassId: { propDefinition: [ limoexpress, - "fromLocation", + "vehicleClassId", ], + optional: true, }, - pickupTime: { + vehicleId: { propDefinition: [ limoexpress, - "pickupTime", + "vehicleId", + ({ vehicleClassId }) => ({ + vehicleClassId, + }), ], + optional: true, }, - customerId: { + price: { type: "string", - label: "Customer ID", - description: "ID of the customer for the booking.", + label: "Price", + description: "The price of the booking.", + optional: true, }, - toLocation: { + priceType: { type: "string", - label: "To Location", - description: "The dropoff location.", + label: "Price Type", + description: "The type of price for the booking.", + options: PRICE_TYPE_OPTIONS, optional: true, }, - vehicleId: { + commissionAmount: { + type: "string", + label: "Commission Amount", + description: "The commission amount for the booking.", + optional: true, + }, + currencyId: { + propDefinition: [ + limoexpress, + "currencyId", + ], + optional: true, + }, + vatPercentage: { type: "string", - label: "Vehicle ID", - description: "ID of the vehicle to be used for the booking.", + label: "VAT Percentage", + description: "The VAT percentage for the booking.", + optional: true, + }, + paymentMethodId: { + propDefinition: [ + limoexpress, + "paymentMethodId", + ], + optional: true, + }, + distance: { + type: "integer", + label: "Distance", + description: "Number of kilometers/miles that booking will take.", + optional: true, + }, + duration: { + type: "string", + label: "Duration", + description: "Number of hours and minutes that booking will take. **Format: HH:MM**", + optional: true, + }, + paid: { + type: "boolean", + label: "Paid", + description: "Flag that says is the booking paid or not.", + optional: true, + }, + confirmed: { + type: "boolean", + label: "Confirmed", + description: "Flag that says is the booking confirmed or not.", + optional: true, + }, + roundTrip: { + type: "boolean", + label: "Round Trip", + description: "Flag that says is the booking a round trip or not.", + optional: true, + }, + note: { + type: "string", + label: "Note", + description: "Note for the dispatcher.", + optional: true, + }, + noteForDriver: { + type: "string", + label: "Note for Driver", + description: "Note for the driver.", + optional: true, + }, + flightNumber: { + type: "string", + label: "Flight Number", + description: "Flight number for the booking.", + optional: true, + }, + numOfWaitingHours: { + type: "integer", + label: "Number of Waiting Hours", + description: "Number of waiting hours.", + optional: true, + }, + clientId: { + propDefinition: [ + limoexpress, + "clientId", + ], + optional: true, + }, + waitingBoardText: { + type: "string", + label: "Waiting Board Text", + description: "Text that will be places on the waiting board.", + optional: true, + }, + babySeatCount: { + type: "integer", + label: "Baby Seat Count", + description: "Number of baby seats that will be used for the booking.", + optional: true, + }, + suitcaseCount: { + type: "integer", + label: "Suitcase Count", + description: "Number of suitcases that will be used for the booking.", + optional: true, + }, + checkpoints: { + type: "string[]", + label: "Checkpoints", + description: "List of objects of checkpoints location and time. **Format: [{\"location\": { \"name\": string, \"full_address\": string, \"coordinates\": { \"lat\": number, \"lng\": number } }, \"time\": \"01:14\"}]**", + optional: true, + }, + passengers: { + type: "string[]", + label: "Passengers", + description: "List of objects of passengers. **Format: [{\"first_name\": string, \"last_name\": string, \"phone\": string, \"email\": string, \"nationality\": string, \"passport\": string, \"country_id\": UUID }]", optional: true, }, }, async run({ $ }) { - const response = await this.limoexpress.createBooking({ - bookingTypeId: this.bookingTypeId, - fromLocation: this.fromLocation, - pickupTime: this.pickupTime, - additionalFields: { - booking_status_id: this.bookingStatusId, - customer_id: this.customerId, - to_location: this.toLocation, - vehicle_id: this.vehicleId, - }, - }); + try { + const response = await this.limoexpress.createBooking({ + $, + data: { + booking_type_id: this.bookingTypeId, + booking_status_id: this.bookingStatusId, + from_location: { + name: this.fromLocationName, + full_address: this.fromLocationFullAddress, + coordinates: { + lat: this.fromLocationLatitude, + lng: this.fromLocationLongitude, + }, + }, + to_location: { + name: this.toLocationName, + full_address: this.toLocationFullAddress, + coordinates: { + lat: this.toLocationLatitude, + lng: this.toLocationLongitude, + }, + }, + pickup_time: this.pickupTime, + expected_drop_off_time: this.expectedDropOffTime, + expected_comeback_time: this.expectedComebackTime, + vehicle_class_id: this.vehicleClassId, + vehicle_id: this.vehicleId, + price: this.price && parseFloat(this.price), + price_type: this.priceType, + commission_amount: this.commissionAmount && parseFloat(this.commissionAmount), + currency_id: this.currencyId, + vat_percentage: this.vatPercentage && parseFloat(this.vatPercentage), + payment_method_id: this.paymentMethodId, + distance: this.distance, + duration: this.duration, + paid: this.paid, + confirmed: this.confirmed, + round_trip: this.roundTrip, + note: this.note, + note_for_driver: this.noteForDriver, + flight_number: this.flightNumber, + num_of_waiting_hours: this.numOfWaitingHours, + client_id: this.clientId, + waiting_board_text: this.waitingBoardText, + baby_seat_count: this.babySeatCount, + suitcase_count: this.suitcaseCount, + checkpoints: this.checkpoints, + passengers: this.passengers, + }, + }); - $.export("$summary", `Successfully created booking with ID ${response.id}`); - return response; + $.export("$summary", `Successfully created booking with ID ${response.data.id}`); + return response; + } catch ({ response }) { + throwError(response); + } }, }; diff --git a/components/limoexpress/actions/create-client/create-client.mjs b/components/limoexpress/actions/create-client/create-client.mjs new file mode 100644 index 0000000000000..e4418f9856b17 --- /dev/null +++ b/components/limoexpress/actions/create-client/create-client.mjs @@ -0,0 +1,68 @@ +import { TYPE_OPTIONS } from "../../common/constants.mjs"; +import { throwError } from "../../common/utils.mjs"; +import limoexpress from "../../limoexpress.app.mjs"; + +export default { + key: "limoexpress-create-client", + name: "Create Client", + description: "Creates a new client with specified details. [See the documentation](https://api.limoexpress.me/api/docs/v1#/Clients/createAOrganisationClient)", + version: "0.0.1", + type: "action", + props: { + limoexpress, + name: { + type: "string", + label: "Name", + description: "Name of the client.", + }, + address: { + type: "string", + label: "Address", + description: "Address of the client.", + optional: true, + }, + phone: { + type: "string", + label: "Phone", + description: "Phone of the client.", + optional: true, + }, + email: { + type: "string", + label: "Email", + description: "Email of the client.", + optional: true, + }, + type: { + type: "string", + label: "Type", + description: "Type of the client.", + options: TYPE_OPTIONS, + }, + active: { + type: "boolean", + label: "Active", + description: "Active flag is the class active or not.", + }, + }, + async run({ $ }) { + try { + const response = await this.limoexpress.createClient({ + $, + data: { + name: this.name, + address: this.address, + phone: this.phone, + email: this.email, + type: this.type, + active: this.active, + }, + }); + + $.export("$summary", `Successfully created client with ID ${response.data.id}`); + return response; + } catch ({ response }) { + throwError(response); + } + }, +}; diff --git a/components/limoexpress/actions/mark-invoice-paid/mark-invoice-paid.mjs b/components/limoexpress/actions/mark-invoice-paid/mark-invoice-paid.mjs new file mode 100644 index 0000000000000..08058bd7b520b --- /dev/null +++ b/components/limoexpress/actions/mark-invoice-paid/mark-invoice-paid.mjs @@ -0,0 +1,29 @@ +import limoexpress from "../../limoexpress.app.mjs"; + +export default { + key: "limoexpress-mark-invoice-paid", + name: "Mark Invoice Paid", + description: "Marks an invoice as paid. [See the documentation](https://api.limoexpress.me/api/docs/v1#/Invoices/markInvoiceAsPaid)", + version: "0.0.1", + type: "action", + props: { + limoexpress, + invoiceId: { + propDefinition: [ + limoexpress, + "invoiceId", + ], + }, + }, + async run({ $ }) { + const response = await this.limoexpress.markInvoiceAsPaid({ + $, + data: { + invoice_id: this.invoiceId, + }, + }); + + $.export("$summary", `Successfully marked invoice with ID ${this.invoiceId} as paid`); + return response; + }, +}; diff --git a/components/limoexpress/common/constants.mjs b/components/limoexpress/common/constants.mjs new file mode 100644 index 0000000000000..4da94f76d2d53 --- /dev/null +++ b/components/limoexpress/common/constants.mjs @@ -0,0 +1,15 @@ +export const PRICE_TYPE_OPTIONS = [ + "NET", + "GROSS", +]; + +export const TYPE_OPTIONS = [ + { + label: "Individual", + value: "natural_person", + }, + { + label: "Business Entity", + value: "legal_entity", + }, +]; diff --git a/components/limoexpress/common/utils.mjs b/components/limoexpress/common/utils.mjs new file mode 100644 index 0000000000000..84ef8cdc17e0d --- /dev/null +++ b/components/limoexpress/common/utils.mjs @@ -0,0 +1,12 @@ +import { ConfigurationError } from "@pipedream/platform"; + +export const throwError = (response) => { + const errors = Object.keys(response.data.errors); + throw new ConfigurationError(response.data.errors[errors[0]]?.[0]); +}; + +export const checkNumbers = (numbers, lastNumber) => { + return (parseInt(numbers[1]) < parseInt(lastNumber[1])) || + ((parseInt(numbers[1]) >= parseInt(lastNumber[1])) && + (parseInt(numbers[0]) <= parseInt(lastNumber[0]))); +}; diff --git a/components/limoexpress/limoexpress.app.mjs b/components/limoexpress/limoexpress.app.mjs index ab1065cff9dd5..d500ad5be9d4c 100644 --- a/components/limoexpress/limoexpress.app.mjs +++ b/components/limoexpress/limoexpress.app.mjs @@ -9,10 +9,12 @@ export default { label: "Booking Type ID", description: "ID of the booking type.", async options() { - const types = await this.listBookingTypes(); - return types.map((type) => ({ - label: type.name, - value: type.id, + const { data } = await this.listBookingTypes(); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, @@ -21,126 +23,244 @@ export default { label: "Booking Status ID", description: "ID of the booking status.", async options() { - const statuses = await this.listBookingStatuses(); - return statuses.map((status) => ({ - label: status.name, - value: status.id, + const { data } = await this.listBookingStatuses(); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, })); }, }, - bookingId: { + vehicleClassId: { type: "string", - label: "Booking ID", - description: "The ID of the booking.", + label: "Vehicle Class ID", + description: "ID of the vehicle class to be used for the booking.", + async options({ page }) { + const { data } = await this.listVehicleClasses({ + params: { + page, + }, + }); + return data.map(({ name }) => name); + }, }, - driverId: { + vehicleId: { type: "string", - label: "Driver ID", - description: "The ID of the driver to assign to the booking.", + label: "Vehicle ID", + description: "ID of the vehicle to be used for the booking.", + async options({ + page, vehicleClassId, + }) { + const { data } = await this.listVehicles({ + params: { + page, + search_string: vehicleClassId, + }, + }); + return data.map(({ + id: value, name, plate_number, + }) => ({ + label: `${name} (${plate_number})`, + value, + })); + }, }, - fromLocation: { + currencyId: { type: "string", - label: "From Location", - description: "The pickup location.", + label: "Currency ID", + description: "ID of the currency to be used for the booking.", + async options() { + const { data } = await this.listCurrencies(); + return data.map(({ + id: value, code: label, + }) => ({ + label, + value, + })); + }, }, - pickupTime: { + paymentMethodId: { type: "string", - label: "Pickup Time", - description: "The time scheduled for pickup.", + label: "Payment Method ID", + description: "ID of the payment method for the booking.", + async options({ page }) { + const { data } = await this.listPaymentMethods({ + params: { + page, + }, + }); + return data.filter(({ hidden }) => !hidden).map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, - cancellationReason: { + clientId: { type: "string", - label: "Cancellation Reason", - description: "Reason for canceling the booking.", - optional: true, + label: "Client ID", + description: "ID of the client for the booking.", + async options({ page }) { + const { data } = await this.listClients({ + params: { + page, + }, + }); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); + }, }, - assignmentNotes: { + invoiceId: { type: "string", - label: "Assignment Notes", - description: "Additional notes for the driver assignment.", - optional: true, + label: "Invoice ID", + description: "The ID of the invoice.", + async options({ page }) { + const { data } = await this.listInvoices({ + params: { + page, + }, + }); + return data.map(({ + id: value, number: label, + }) => ({ + label, + value, + })); + }, }, }, methods: { - authKeys() { - console.log(Object.keys(this.$auth)); - }, _baseUrl() { - return "https://api.limoexpress.me/api/v1"; + return "https://api.limoexpress.me/api/integration"; + }, + _headers() { + return { + "Authorization": `Bearer ${this.$auth.api_key}`, + "Accept": "*/*", + }; }, - async _makeRequest(opts = {}) { - const { - $ = this, method = "GET", path = "/", headers, ...otherOpts - } = opts; + _makeRequest({ + $ = this, path, ...opts + }) { return axios($, { - ...otherOpts, - method, url: this._baseUrl() + path, - headers: { - ...headers, - Authorization: `Bearer ${this.$auth.oauth_access_token}`, - }, + headers: this._headers(), + ...opts, }); }, - async listBookingTypes(opts = {}) { + listBookings(opts = {}) { return this._makeRequest({ - path: "/booking_types", + path: "/bookings", ...opts, }); }, - async listBookingStatuses(opts = {}) { + listBookingTypes(opts = {}) { return this._makeRequest({ - path: "/booking_statuses", + path: "/booking-types", ...opts, }); }, - async createBooking({ - bookingTypeId, fromLocation, pickupTime, ...opts - }) { + listBookingStatuses(opts = {}) { return this._makeRequest({ - method: "POST", + path: "/booking-statuses", + ...opts, + }); + }, + listVehicleClasses(opts = {}) { + return this._makeRequest({ + path: "/vehicle-classes", + ...opts, + }); + }, + listVehicles(opts = {}) { + return this._makeRequest({ + path: "/vehicles", + ...opts, + }); + }, + listCurrencies(opts = {}) { + return this._makeRequest({ + path: "/currencies", + ...opts, + }); + }, + listPaymentMethods(opts = {}) { + return this._makeRequest({ + path: "/payment-methods", + ...opts, + }); + }, + listClients(opts = {}) { + return this._makeRequest({ + path: "/clients", + ...opts, + }); + }, + listInvoices(opts = {}) { + return this._makeRequest({ + path: "/invoices", + ...opts, + }); + }, + createBooking(opts = {}) { + return this._makeRequest({ + method: "PUT", path: "/bookings", - data: { - booking_type_id: bookingTypeId, - from_location: fromLocation, - pickup_time: pickupTime, - }, ...opts, }); }, - async cancelBooking({ - bookingId, cancellationReason, ...opts - }) { + createClient(opts = {}) { return this._makeRequest({ - method: "POST", - path: `/bookings/${bookingId}/cancel`, - data: { - cancellation_reason: cancellationReason, - }, + method: "PUT", + path: "/clients", ...opts, }); }, - async assignDriver({ - bookingId, driverId, assignmentNotes, ...opts + markInvoiceAsPaid({ + invoiceId, ...opts }) { return this._makeRequest({ method: "POST", - path: `/bookings/${bookingId}/assign_driver`, - data: { - driver_id: driverId, - assignment_notes: assignmentNotes, - }, + path: `/invoices/${invoiceId}/mark_as_paid`, ...opts, }); }, - async emitNewBookingEvent() { - // Implementation for emitting new booking event - }, - async emitCancelledBookingEvent() { - // Implementation for emitting canceled booking event - }, - async emitDriverAssignedEvent() { - // Implementation for emitting driver assigned event + async *paginate({ + fn, params = {}, maxResults = null, ...opts + }) { + let hasMore = false; + let count = 0; + let page = 0; + + do { + params.page = ++page; + params.per_page = 100; + const { + data, + meta: { + current_page, last_page, + }, + } = await fn({ + params, + ...opts, + }); + for (const d of data) { + yield d; + + if (maxResults && ++count === maxResults) { + return count; + } + } + + hasMore = !(current_page == last_page); + + } while (hasMore); }, }, }; diff --git a/components/limoexpress/package.json b/components/limoexpress/package.json index f0ff0060e7882..96a81b64d4428 100644 --- a/components/limoexpress/package.json +++ b/components/limoexpress/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/limoexpress", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream LimoExpress Components", "main": "limoexpress.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } } diff --git a/components/limoexpress/sources/new-booking-instant/new-booking-instant.mjs b/components/limoexpress/sources/new-booking-instant/new-booking-instant.mjs deleted file mode 100644 index 90ca4d4c7e66e..0000000000000 --- a/components/limoexpress/sources/new-booking-instant/new-booking-instant.mjs +++ /dev/null @@ -1,77 +0,0 @@ -import limoexpress from "../../limoexpress.app.mjs"; -import { - axios, DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, -} from "@pipedream/platform"; - -export default { - key: "limoexpress-new-booking-instant", - name: "New Limo Booking Created", - description: "Emit new event when a customer creates a new limo booking. [See the documentation](https://api.limoexpress.me/api/docs/v1)", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - limoexpress, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, - }, - }, - }, - methods: { - _getLastBookingTime() { - return this.db.get("lastBookingTime"); - }, - _setLastBookingTime(time) { - this.db.set("lastBookingTime", time); - }, - async fetchNewBookings(since) { - return await this.limoexpress._makeRequest({ - path: "/bookings", - params: { - created_after: since, - }, - }); - }, - }, - hooks: { - async deploy() { - const { results } = await this.limoexpress._makeRequest({ - path: "/bookings", - }); - - results.slice(0, 50).forEach((booking) => { - this.$emit(booking, { - id: booking.id, - summary: `New Booking: ${booking.id}`, - ts: new Date(booking.created_at).getTime(), - }); - }); - - if (results.length) { - this._setLastBookingTime(results[0].created_at); - } - }, - }, - async run() { - const lastBookingTime = this._getLastBookingTime(); - const { results: newBookings } = await this.fetchNewBookings(lastBookingTime); - - newBookings.forEach((booking) => { - const ts = new Date(booking.created_at).getTime(); - if (ts > new Date(lastBookingTime).getTime()) { - this.$emit(booking, { - id: booking.id, - summary: `New Booking: ${booking.id}`, - ts, - }); - } - }); - - if (newBookings.length) { - this._setLastBookingTime(newBookings[0].created_at); - } - }, -}; diff --git a/components/limoexpress/sources/new-booking/new-booking.mjs b/components/limoexpress/sources/new-booking/new-booking.mjs new file mode 100644 index 0000000000000..c7b241c6f3db7 --- /dev/null +++ b/components/limoexpress/sources/new-booking/new-booking.mjs @@ -0,0 +1,73 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import { checkNumbers } from "../../common/utils.mjs"; +import app from "../../limoexpress.app.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + key: "limoexpress-new-booking", + name: "New Limo Booking Created", + description: "Emit new event when a customer creates a new limo booking. [See the documentation](https://api.limoexpress.me/api/docs/v1#/Bookings/createBooking)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + app, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastNumber() { + return this.db.get("lastNumber") || 0; + }, + _setLastNumber(lastNumber) { + this.db.set("lastNumber", lastNumber); + }, + async emitEvent(maxResults = false) { + const lastNumber = this._getLastNumber(); + + const response = this.app.paginate({ + fn: this.app.listBookings, + params: { + order: "desc", + order_by: "id", + }, + }); + + let responseArray = []; + for await (const item of response) { + const numbers = item.number.split("/"); + if (checkNumbers(numbers, lastNumber)) break; + responseArray.push(item); + } + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastNumber(responseArray[0].number.split("/")); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: `New Booking: ${item.id}`, + ts: Date.now(), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, + sampleEmit, +}; diff --git a/components/limoexpress/sources/new-booking/test-event.mjs b/components/limoexpress/sources/new-booking/test-event.mjs new file mode 100644 index 0000000000000..d1b239032c871 --- /dev/null +++ b/components/limoexpress/sources/new-booking/test-event.mjs @@ -0,0 +1,74 @@ +export default { + "id": "12345678-1234-1234-1234-123456789012", + "number": "7/2025", + "from_location": "{\"formatted_address\":\"Asdas, Yemen\",\"geometry\":{\"location\":{\"lat\":15.76424,\"lng\":45.079288},\"viewport\":{\"northeast\":{\"lat\":15.7702579,\"lng\":45.08634199999999},\"southwest\":{\"lat\":15.7639416,\"lng\":45.07583229999999}}},\"name\":\"Asdas\",\"place_id\":\"ChIJmxgRYIt7_z0Rc81rWkz0sdI\"}", + "to_location": "{\"formatted_address\":\"Asdas, Yemen\",\"geometry\":{\"location\":{\"lat\":15.76424,\"lng\":45.079288},\"viewport\":{\"northeast\":{\"lat\":15.7702579,\"lng\":45.08634199999999},\"southwest\":{\"lat\":15.7639416,\"lng\":45.07583229999999}}},\"name\":\"Asdas\",\"place_id\":\"ChIJmxgRYIt7_z0Rc81rWkz0sdI\"}", + "note": null, + "note_for_driver": null, + "start": "2025-06-11 00:25", + "end": null, + "distance": 0, + "duration": "00:00", + "expected_drop_off_time": null, + "expected_comeback_time": null, + "pickup_time": "2025-06-11 00:25:00", + "num_of_waiting_hours": 0, + "price": 0, + "price_for_waiting": 0, + "price_type": "NET", + "vat_percentage": 0, + "paid": 0, + "confirmed": 1, + "round_trip": 0, + "vehicle": { + "id": "12345678-1234-1234-1234-123456789012", + "brand": "AS", + "plate_number": "Vehicle test 01", + "price_per_km": 0, + "price_per_hour": 0, + "price_per_waiting_hour": 0, + "year_of_manufacture": null, + "image_path": null, + "active": 1, + "vehicle_class": { + "id": "12345678-1234-1234-1234-123456789012", + "name": "Class Test 01" + }, + "fuel_consumption": null, + "number_of_passengers": 1, + }, + "vehicle_class": { + "id": "12345678-1234-1234-1234-123456789012", + "name": "Class Test 01", + }, + "booking_status": { + "id": "12345678-1234-1234-1234-123456789012", + "name": "Pending", + }, + "payment_method": { + "id": "12345678-1234-1234-1234-123456789012", + "name": "Cash", + }, + "currency": { + "id": "12345678-1234-1234-1234-123456789012", + "name": "United States Dollar", + "code": "USD", + "symbol": "$", + "active": 1, + }, + "booking_type": { + "id": "12345678-1234-1234-1234-123456789012", + "name": "Transfer", + }, + "waiting_board_text": null, + "flight_number": null, + "client": { + "id": "12345678-1234-1234-1234-123456789012", + "name": "Client Test 01", + "address": null, + "phone": "123123123123", + "type": "natural_person", + }, + "checkpoints": [], + "passengers": [], +}; \ No newline at end of file diff --git a/components/limoexpress/sources/new-cancellation-instant/new-cancellation-instant.mjs b/components/limoexpress/sources/new-cancellation-instant/new-cancellation-instant.mjs deleted file mode 100644 index bcfdb2c4bf4fd..0000000000000 --- a/components/limoexpress/sources/new-cancellation-instant/new-cancellation-instant.mjs +++ /dev/null @@ -1,74 +0,0 @@ -import { axios } from "@pipedream/platform"; -import limoexpress from "../../limoexpress.app.mjs"; - -export default { - key: "limoexpress-new-cancellation-instant", - name: "New Booking Cancellation", - description: "Emit new event when a booking is canceled. [See the documentation](https://api.limoexpress.me/api/docs/v1)", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - limoexpress, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 60, - }, - }, - }, - methods: { - _getLastTimestamp() { - return this.db.get("lastTimestamp") || 0; - }, - _setLastTimestamp(ts) { - this.db.set("lastTimestamp", ts); - }, - async _makeRequest(opts = {}) { - return this.limoexpress._makeRequest(opts); - }, - async fetchCancellations(since) { - return this._makeRequest({ - path: "/cancellations", - method: "GET", - params: { - since, - }, - }); - }, - }, - hooks: { - async deploy() { - const currentTime = new Date().getTime(); - const cancellations = await this.fetchCancellations(currentTime - 86400000); // fetch cancellations from the last 24 hours - cancellations.slice(0, 50).forEach((cancellation) => { - this.$emit(cancellation, { - id: cancellation.id, - summary: `New Cancellation: ${cancellation.id}`, - ts: new Date(cancellation.cancelled_at).getTime(), - }); - }); - if (cancellations.length > 0) { - const latestTimestamp = Math.max(...cancellations.map((c) => new Date(c.cancelled_at).getTime())); - this._setLastTimestamp(latestTimestamp); - } - }, - }, - async run() { - const lastTimestamp = this._getLastTimestamp(); - const cancellations = await this.fetchCancellations(lastTimestamp); - - cancellations.forEach((cancellation) => { - const cancellationTime = new Date(cancellation.cancelled_at).getTime(); - if (cancellationTime > lastTimestamp) { - this.$emit(cancellation, { - id: cancellation.id, - summary: `New Cancellation: ${cancellation.id}`, - ts: cancellationTime, - }); - this._setLastTimestamp(cancellationTime); - } - }); - }, -}; diff --git a/components/limoexpress/sources/new-driver-assignment-instant/new-driver-assignment-instant.mjs b/components/limoexpress/sources/new-driver-assignment-instant/new-driver-assignment-instant.mjs deleted file mode 100644 index 61eeec14b5916..0000000000000 --- a/components/limoexpress/sources/new-driver-assignment-instant/new-driver-assignment-instant.mjs +++ /dev/null @@ -1,67 +0,0 @@ -import limoexpress from "../../limoexpress.app.mjs"; -import { axios } from "@pipedream/platform"; - -export default { - key: "limoexpress-new-driver-assignment-instant", - name: "New Driver Assignment Instant", - description: "Emit new event when a driver is assigned to a limo ride. Useful for dispatch coordination. [See the documentation](https://api.limoexpress.me/api/docs/v1)", - version: "0.0.{{ts}}", - type: "source", - dedupe: "unique", - props: { - limoexpress, - db: "$.service.db", - timer: { - type: "$.interface.timer", - default: { - intervalSeconds: 15 * 60, // 15 minutes - }, - }, - }, - hooks: { - async deploy() { - this._setLastTimestamp(Date.now()); - }, - async activate() { - // No activation logic needed - }, - async deactivate() { - // No deactivation logic needed - }, - }, - methods: { - _setLastTimestamp(ts) { - this.db.set("lastTimestamp", ts); - }, - _getLastTimestamp() { - return this.db.get("lastTimestamp") || 0; - }, - async fetchDriverAssignments() { - const lastTimestamp = this._getLastTimestamp(); - const now = Date.now(); - - const driverAssignments = await this.limoexpress._makeRequest({ - path: "/driver_assignments", - params: { - since: lastTimestamp, - }, - }); - - this._setLastTimestamp(now); - return driverAssignments; - }, - }, - async run() { - const driverAssignments = await this.fetchDriverAssignments(); - - for (const assignment of driverAssignments) { - this.$emit(assignment, { - id: assignment.id, - summary: `Driver assigned to booking ID: ${assignment.bookingId}`, - ts: assignment.timestamp - ? Date.parse(assignment.timestamp) - : Date.now(), - }); - } - }, -}; From 46804d58db8e9538a107643ee997e2468d3fb063 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 3 Jun 2025 18:19:48 -0300 Subject: [PATCH 3/7] pnpm update --- pnpm-lock.yaml | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fbbcb2964ec4d..023844f501903 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -367,8 +367,7 @@ importers: specifier: ^1.2.1 version: 1.6.6 - components/adtraction: - specifiers: {} + components/adtraction: {} components/adversus: dependencies: @@ -3739,8 +3738,7 @@ importers: components/dokan: {} - components/dolibarr: - specifiers: {} + components/dolibarr: {} components/domain_group: dependencies: @@ -7378,7 +7376,10 @@ importers: version: 3.0.3 components/limoexpress: - specifiers: {} + dependencies: + '@pipedream/platform': + specifier: ^3.0.3 + version: 3.0.3 components/line: dependencies: @@ -9892,8 +9893,7 @@ importers: specifier: ^1.5.1 version: 1.6.6 - components/pinghome: - specifiers: {} + components/pinghome: {} components/pingone: {} @@ -10388,8 +10388,7 @@ importers: components/procore_sandbox: {} - components/prodatakey: - specifiers: {} + components/prodatakey: {} components/prodpad: dependencies: @@ -15062,8 +15061,7 @@ importers: specifier: ^3.0.3 version: 3.0.3 - components/zenedu: - specifiers: {} + components/zenedu: {} components/zenkit: dependencies: From dfcdaf5e1f7cce7b513443c40796c92d597943ea Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Tue, 3 Jun 2025 18:25:17 -0300 Subject: [PATCH 4/7] pnpm update --- pnpm-lock.yaml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 199eb1762ef16..9b066dff0471a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -32648,7 +32648,7 @@ snapshots: '@babel/helper-validator-option': 8.0.0-alpha.13 browserslist: 4.24.2 lru-cache: 7.18.3 - semver: 7.7.1 + semver: 7.7.2 '@babel/helper-create-class-features-plugin@7.25.9(@babel/core@7.26.0)': dependencies: @@ -35988,6 +35988,8 @@ snapshots: '@putout/operator-filesystem': 5.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3)) '@putout/operator-json': 2.2.0 putout: 36.13.1(eslint@8.57.1)(typescript@5.6.3) + transitivePeerDependencies: + - supports-color '@putout/operator-regexp@1.0.0(putout@36.13.1(eslint@8.57.1)(typescript@5.6.3))': dependencies: @@ -43264,7 +43266,7 @@ snapshots: is-bun-module@1.2.1: dependencies: - semver: 7.7.1 + semver: 7.7.2 is-callable@1.2.7: {} From b9cb3b4f0b8145cd787acfbb62148bc37c01ecf0 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 6 Jun 2025 12:34:10 -0300 Subject: [PATCH 5/7] some adjusts --- .../actions/create-booking/create-booking.mjs | 5 +++-- components/limoexpress/limoexpress.app.mjs | 9 +++++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/components/limoexpress/actions/create-booking/create-booking.mjs b/components/limoexpress/actions/create-booking/create-booking.mjs index 7cbb0ab9804c0..b774f3b8c0317 100644 --- a/components/limoexpress/actions/create-booking/create-booking.mjs +++ b/components/limoexpress/actions/create-booking/create-booking.mjs @@ -89,6 +89,7 @@ export default { limoexpress, "vehicleClassId", ], + withLabel: true, optional: true, }, vehicleId: { @@ -96,7 +97,7 @@ export default { limoexpress, "vehicleId", ({ vehicleClassId }) => ({ - vehicleClassId, + vehicleClassId: vehicleClassId.label, }), ], optional: true, @@ -258,7 +259,7 @@ export default { pickup_time: this.pickupTime, expected_drop_off_time: this.expectedDropOffTime, expected_comeback_time: this.expectedComebackTime, - vehicle_class_id: this.vehicleClassId, + vehicle_class_id: this.vehicleClassId.value, vehicle_id: this.vehicleId, price: this.price && parseFloat(this.price), price_type: this.priceType, diff --git a/components/limoexpress/limoexpress.app.mjs b/components/limoexpress/limoexpress.app.mjs index d500ad5be9d4c..598b16e84bdbb 100644 --- a/components/limoexpress/limoexpress.app.mjs +++ b/components/limoexpress/limoexpress.app.mjs @@ -42,13 +42,18 @@ export default { page, }, }); - return data.map(({ name }) => name); + return data.map(({ + id: value, name: label, + }) => ({ + label, + value, + })); }, }, vehicleId: { type: "string", label: "Vehicle ID", - description: "ID of the vehicle to be used for the booking.", + description: "ID of the vehicle to be used for the booking. **Vehicle class ID is required**.", async options({ page, vehicleClassId, }) { From 182d1034d3c11d11e01e6c2fd9fcdd422af85fb2 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 6 Jun 2025 12:38:22 -0300 Subject: [PATCH 6/7] pnpm update --- pnpm-lock.yaml | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9b066dff0471a..ce15b0b5bfbf3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15744,7 +15744,7 @@ importers: version: 3.1.7 ts-jest: specifier: ^29.2.5 - 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) + 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))(esbuild@0.24.2)(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) @@ -15781,7 +15781,7 @@ importers: version: 3.1.0 jest: specifier: ^29.1.2 - version: 29.7.0(@types/node@20.17.6)(babel-plugin-macros@3.1.0) + version: 29.7.0(@types/node@20.17.30)(babel-plugin-macros@3.1.0) type-fest: specifier: ^4.15.0 version: 4.27.0 @@ -49657,7 +49657,7 @@ snapshots: ts-interface-checker@0.1.13: {} - 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): + 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))(esbuild@0.24.2)(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 @@ -49671,10 +49671,11 @@ snapshots: typescript: 5.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@babel/core': 7.26.0 + '@babel/core': 8.0.0-alpha.13 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.26.0) + babel-jest: 29.7.0(@babel/core@8.0.0-alpha.13) + esbuild: 0.24.2 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: From e0feef7d1097125fefcf29c48c213515eaaf6fe1 Mon Sep 17 00:00:00 2001 From: Luan Cazarine Date: Fri, 6 Jun 2025 13:11:26 -0300 Subject: [PATCH 7/7] pnpm update --- pnpm-lock.yaml | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ce15b0b5bfbf3..9b066dff0471a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -15744,7 +15744,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))(esbuild@0.24.2)(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) @@ -15781,7 +15781,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 @@ -49657,7 +49657,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))(esbuild@0.24.2)(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 @@ -49671,11 +49671,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) - esbuild: 0.24.2 + 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: