diff --git a/components/highlevel_oauth/actions/add-contact-to-campaign/add-contact-to-campaign.mjs b/components/highlevel_oauth/actions/add-contact-to-campaign/add-contact-to-campaign.mjs index 8cdb162326c22..98e3358eb3034 100644 --- a/components/highlevel_oauth/actions/add-contact-to-campaign/add-contact-to-campaign.mjs +++ b/components/highlevel_oauth/actions/add-contact-to-campaign/add-contact-to-campaign.mjs @@ -5,7 +5,7 @@ export default { key: "highlevel_oauth-add-contact-to-campaign", name: "Add Contact to Campaign", description: "Adds an existing contact to a campaign. [See the documentation](https://highlevel.stoplight.io/docs/integrations/ecf9b5b45deaf-add-contact-to-campaign)", - version: "0.0.1", + version: "0.0.2", type: "action", props: { ...common.props, diff --git a/components/highlevel_oauth/actions/create-contact/create-contact.mjs b/components/highlevel_oauth/actions/create-contact/create-contact.mjs index 09ddab4f6d8b6..fe3f84b523e62 100644 --- a/components/highlevel_oauth/actions/create-contact/create-contact.mjs +++ b/components/highlevel_oauth/actions/create-contact/create-contact.mjs @@ -11,7 +11,7 @@ export default { key: "highlevel_oauth-create-contact", name: "Create Contact", description: "Creates a new contact on HighLevel. [See the documentation](https://highlevel.stoplight.io/docs/integrations/4c8362223c17b-create-contact)", - version: "0.0.2", + version: "0.0.3", type: "action", props: { app, diff --git a/components/highlevel_oauth/actions/create-record/create-record.mjs b/components/highlevel_oauth/actions/create-record/create-record.mjs new file mode 100644 index 0000000000000..c5e6d1b7c9319 --- /dev/null +++ b/components/highlevel_oauth/actions/create-record/create-record.mjs @@ -0,0 +1,61 @@ +import common from "../../common/base.mjs"; +import { parseObjectEntries } from "../../common/utils.mjs"; + +export default { + ...common, + key: "highlevel_oauth-create-record", + name: "Create Record", + description: "Creates a custom object record. [See the documentation](https://highlevel.stoplight.io/docs/integrations/47e05e18c5d41-create-record)", + version: "0.0.1", + type: "action", + props: { + ...common.props, + schemaKey: { + propDefinition: [ + common.props.app, + "schemaKey", + ], + }, + ownerId: { + propDefinition: [ + common.props.app, + "userId", + ], + label: "Owner ID", + description: "The ID of a user representing the owner of the record. Only supported for custom objects.", + optional: true, + }, + followerIds: { + propDefinition: [ + common.props.app, + "userId", + ], + type: "string[]", + label: "Follower IDs", + description: "An array of user IDs representing followers of the record. Only supported for custom objects", + optional: true, + }, + properties: { + propDefinition: [ + common.props.app, + "properties", + ], + }, + }, + async run({ $ }) { + const response = await this.app.createRecord({ + $, + schemaKey: this.schemaKey, + data: { + locationId: this.app.getLocationId(), + owners: [ + this.ownerId, + ], + followers: this.followerIds, + properties: parseObjectEntries(this.properties), + }, + }); + $.export("$summary", `Successfully created record with ID: ${response.record.id}`); + return response; + }, +}; diff --git a/components/highlevel_oauth/actions/update-contact/update-contact.mjs b/components/highlevel_oauth/actions/update-contact/update-contact.mjs index 477b6097a5d50..8a35a106e0cdf 100644 --- a/components/highlevel_oauth/actions/update-contact/update-contact.mjs +++ b/components/highlevel_oauth/actions/update-contact/update-contact.mjs @@ -11,7 +11,7 @@ export default { key: "highlevel_oauth-update-contact", name: "Update Contact", description: "Updates a selected contact on HighLevel. [See the documentation](https://highlevel.stoplight.io/docs/integrations/9ce5a739d4fb9-update-contact)", - version: "0.0.2", + version: "0.0.3", type: "action", props: { app, diff --git a/components/highlevel_oauth/actions/update-note/update-note.mjs b/components/highlevel_oauth/actions/update-note/update-note.mjs new file mode 100644 index 0000000000000..2f1e618e3040a --- /dev/null +++ b/components/highlevel_oauth/actions/update-note/update-note.mjs @@ -0,0 +1,46 @@ +import common from "../../common/base.mjs"; + +export default { + ...common, + key: "highlevel_oauth-update-note", + name: "Update Note", + description: "Updates a note associated with a contact. [See the documentation](https://highlevel.stoplight.io/docs/integrations/71814e115658f-update-note)", + version: "0.0.1", + type: "action", + props: { + ...common.props, + contactId: { + propDefinition: [ + common.props.app, + "contactId", + ], + description: "The contact that the note is associated with", + }, + noteId: { + propDefinition: [ + common.props.app, + "noteId", + (c) => ({ + contactId: c.contactId, + }), + ], + }, + body: { + type: "string", + label: "Body", + description: "The body of the note", + }, + }, + async run({ $ }) { + const response = await this.app.updateNote({ + $, + contactId: this.contactId, + noteId: this.noteId, + data: { + body: this.body, + }, + }); + $.export("$summary", `Successfully updated note (ID: ${this.noteId})`); + return response; + }, +}; diff --git a/components/highlevel_oauth/actions/update-record/update-record.mjs b/components/highlevel_oauth/actions/update-record/update-record.mjs new file mode 100644 index 0000000000000..b34e8f093b58f --- /dev/null +++ b/components/highlevel_oauth/actions/update-record/update-record.mjs @@ -0,0 +1,71 @@ +import common from "../../common/base.mjs"; +import { parseObjectEntries } from "../../common/utils.mjs"; + +export default { + ...common, + key: "highlevel_oauth-update-record", + name: "Update Record", + description: "Updates a custom object record. [See the documentation](https://highlevel.stoplight.io/docs/integrations/b4c5fdbd3ec85-update-record)", + version: "0.0.1", + type: "action", + props: { + ...common.props, + schemaKey: { + propDefinition: [ + common.props.app, + "schemaKey", + ], + }, + recordId: { + propDefinition: [ + common.props.app, + "recordId", + (c) => ({ + schemaKey: c.schemaKey, + }), + ], + }, + ownerId: { + propDefinition: [ + common.props.app, + "userId", + ], + label: "Owner ID", + description: "The ID of a user representing the owner of the record. Only supported for custom objects.", + optional: true, + }, + followerIds: { + propDefinition: [ + common.props.app, + "userId", + ], + type: "string[]", + label: "Follower IDs", + description: "An array of user IDs representing followers of the record. Only supported for custom objects", + optional: true, + }, + properties: { + propDefinition: [ + common.props.app, + "properties", + ], + }, + }, + async run({ $ }) { + const response = await this.app.updateRecord({ + $, + schemaKey: this.schemaKey, + recordId: this.recordId, + data: { + locationId: this.app.getLocationId(), + owners: [ + this.ownerId, + ], + followers: this.followerIds, + properties: parseObjectEntries(this.properties), + }, + }); + $.export("$summary", `Successfully updated record with ID: ${response.record.id}`); + return response; + }, +}; diff --git a/components/highlevel_oauth/actions/upsert-contact/upsert-contact.mjs b/components/highlevel_oauth/actions/upsert-contact/upsert-contact.mjs index c553f9d9be9e4..8cba8e0717412 100644 --- a/components/highlevel_oauth/actions/upsert-contact/upsert-contact.mjs +++ b/components/highlevel_oauth/actions/upsert-contact/upsert-contact.mjs @@ -11,7 +11,7 @@ export default { key: "highlevel_oauth-upsert-contact", name: "Upsert Contact", description: "Creates or updates a contact on HighLevel. [See the documentation](https://highlevel.stoplight.io/docs/integrations/f71bbdd88f028-upsert-contact)", - version: "0.0.2", + version: "0.0.3", type: "action", props: { app, diff --git a/components/highlevel_oauth/common/utils.mjs b/components/highlevel_oauth/common/utils.mjs index 9c3cdbf569744..37df3ea8be185 100644 --- a/components/highlevel_oauth/common/utils.mjs +++ b/components/highlevel_oauth/common/utils.mjs @@ -7,6 +7,9 @@ function optionalParseAsJSON(value) { } export function parseObjectEntries(value) { + if (!value) { + return undefined; + } const obj = typeof value === "string" ? JSON.parse(value) : value; diff --git a/components/highlevel_oauth/highlevel_oauth.app.mjs b/components/highlevel_oauth/highlevel_oauth.app.mjs index 36530d866e838..171064e4da42f 100644 --- a/components/highlevel_oauth/highlevel_oauth.app.mjs +++ b/components/highlevel_oauth/highlevel_oauth.app.mjs @@ -49,6 +49,81 @@ export default { })) || []; }, }, + noteId: { + type: "string", + label: "Note ID", + description: "The ID of the note to update", + async options({ contactId }) { + const { notes } = await this.listNotes({ + contactId, + }); + return notes?.map(({ + id: value, body, + }) => ({ + label: body.slice(0, 50), + value, + })) || []; + }, + }, + userId: { + type: "string", + label: "User ID", + description: "The ID of a user", + async options() { + const { users } = await this.listUsers({ + params: { + locationId: this.getLocationId(), + }, + }); + return users?.map(({ + id, name, email, + }) => ({ + label: name ?? email ?? id, + value: id, + })); + }, + }, + schemaKey: { + type: "string", + label: "Object Schema Key", + description: "Key used to refer the custom / standard Object internally. For custom objects, the key must include the “custom_objects.” prefix, while standard objects use their respective object keys. This information is available on the Custom Objects Details page under Settings.", + async options() { + const { objects } = await this.listObjects({ + params: { + locationId: this.getLocationId(), + }, + }); + return objects?.map(({ + key: value, labels, + }) => ({ + label: labels.plural, + value, + })) || []; + }, + }, + recordId: { + type: "string", + label: "Record ID", + description: "The ID of the record to be updated. Available on the Record details page under the 3 dots or in the url", + async options({ + schemaKey, page, + }) { + const { customObjectRecords } = await this.searchRecords({ + schemaKey, + params: { + page: page + 1, + pageLimit: 20, + }, + }); + return customObjectRecords?.map(({ id }) => id) || []; + }, + }, + properties: { + type: "object", + label: "Properties", + description: "Properties of the record as key/value pairs. Example: `{\"customer_number\":1424,\"ticket_name\":\"Customer not able login\",\"phone_number\":\"+917000000000\"}`", + optional: true, + }, }, methods: { getLocationId() { @@ -101,6 +176,15 @@ export default { ...args, }); }, + searchRecords({ + schemaKey, ...args + }) { + return this._makeRequest({ + method: "POST", + url: `/objects/${schemaKey}/records/search`, + ...args, + }); + }, listCampaigns(args = {}) { return this._makeRequest({ url: "/campaigns/", @@ -113,6 +197,61 @@ export default { ...args, }); }, + listNotes({ + contactId, ...args + }) { + return this._makeRequest({ + url: `/contacts/${contactId}/notes`, + ...args, + }); + }, + getObject({ + schemaKey, ...args + }) { + return this._makeRequest({ + url: `/objects/${schemaKey}`, + ...args, + }); + }, + listObjects(args = {}) { + return this._makeRequest({ + url: "/objects/", + ...args, + }); + }, + listUsers(args = {}) { + return this._makeRequest({ + url: "/users/", + ...args, + }); + }, + updateNote({ + contactId, noteId, ...args + }) { + return this._makeRequest({ + method: "PUT", + url: `/contacts/${contactId}/notes/${noteId}`, + ...args, + }); + }, + createRecord({ + schemaKey, ...args + }) { + return this._makeRequest({ + method: "POST", + url: `/objects/${schemaKey}/records`, + ...args, + }); + }, + updateRecord({ + schemaKey, recordId, ...args + }) { + return this._makeRequest({ + method: "PUT", + url: `/objects/${schemaKey}/records/${recordId}`, + ...args, + }); + }, addContactToCampaign({ contactId, campaignId, ...args }) { diff --git a/components/highlevel_oauth/package.json b/components/highlevel_oauth/package.json index 259966e71e7ab..2e65e8937c5b1 100644 --- a/components/highlevel_oauth/package.json +++ b/components/highlevel_oauth/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/highlevel_oauth", - "version": "0.2.0", + "version": "0.3.0", "description": "Pipedream HighLevel (OAuth) Components", "main": "highlevel_oauth.app.mjs", "keywords": [ @@ -13,6 +13,7 @@ "access": "public" }, "dependencies": { - "@pipedream/platform": "^3.0.3" + "@pipedream/platform": "^3.0.3", + "md5": "^2.3.0" } } diff --git a/components/highlevel_oauth/sources/contact-updated/contact-updated.mjs b/components/highlevel_oauth/sources/contact-updated/contact-updated.mjs new file mode 100644 index 0000000000000..b9f1508f581b9 --- /dev/null +++ b/components/highlevel_oauth/sources/contact-updated/contact-updated.mjs @@ -0,0 +1,65 @@ +import common from "../common/base-polling.mjs"; +import md5 from "md5"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "highlevel_oauth-contact-updated", + name: "Contact Updated", + description: "Emit new event when an existing contact is updated. [See the documentation](https://highlevel.stoplight.io/docs/integrations/ab55933a57f6f-get-contacts)", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + _getContactValues() { + return this.db.get("contactValues") || {}; + }, + _setContactValues(contactValues) { + this.db.set("contactValues", contactValues); + }, + generateMeta(contact) { + const ts = Date.now(); + return { + id: `${contact.id}${ts}`, + summary: `Contact Updated w/ ID: ${contact.id}`, + ts, + }; + }, + }, + async run() { + const results = []; + const params = { + limit: 100, + locationId: this.app.getLocationId(), + }; + let total; + const contactValues = this._getContactValues(); + const newContactValues = {}; + + do { + const { + contacts, meta, + } = await this.app.searchContacts({ + params, + }); + for (const contact of contacts) { + const hash = md5(JSON.stringify(contact)); + if (contactValues[contact.id] && contactValues[contact.id] !== hash) { + results.push(contact); + } + newContactValues[contact.id] = hash; + } + params.startAfter = meta?.startAfter; + total = meta?.total; + } while (results.length < total); + + results.forEach((contact) => { + const meta = this.generateMeta(contact); + this.$emit(contact, meta); + }); + + this._setContactValues(newContactValues); + }, + sampleEmit, +}; diff --git a/components/highlevel_oauth/sources/contact-updated/test-event.mjs b/components/highlevel_oauth/sources/contact-updated/test-event.mjs new file mode 100644 index 0000000000000..15a6f87bc6a2f --- /dev/null +++ b/components/highlevel_oauth/sources/contact-updated/test-event.mjs @@ -0,0 +1,41 @@ +export default { + "id": "2N41CyHhuw4dUROfablK", + "locationId": "t8lsEHvfdlZugCobd4t7", + "contactName": "hello world", + "firstName": "hello", + "lastName": "world", + "firstNameRaw": "Hello", + "lastNameRaw": "World", + "companyName": null, + "email": "email@email.com", + "phone": "+18314654820", + "dnd": false, + "dndSettings": {}, + "type": "lead", + "source": "form 0", + "assignedTo": null, + "city": null, + "state": null, + "postalCode": null, + "address1": null, + "dateAdded": "2025-04-04T18:31:11.343Z", + "dateUpdated": "2025-04-23T17:02:23.305Z", + "dateOfBirth": null, + "businessId": null, + "tags": [], + "followers": [], + "country": "US", + "website": null, + "timezone": "America/Detroit", + "additionalEmails": [], + "attributions": [ + { + "utmSessionSource": "Direct traffic", + "isLast": true, + "medium": "form", + "mediumId": "AhRYrvMdMFRmZ7GxDlDS", + "url": "https://link.apisystem.tech/widget/form/AhRYrvMdMFRmZ7GxDlDS" + } + ], + "customFields": [] +} \ No newline at end of file diff --git a/components/highlevel_oauth/sources/new-contact-created/new-contact-created.mjs b/components/highlevel_oauth/sources/new-contact-created/new-contact-created.mjs index 6da0999f6b6c4..ea69a6c6c696d 100644 --- a/components/highlevel_oauth/sources/new-contact-created/new-contact-created.mjs +++ b/components/highlevel_oauth/sources/new-contact-created/new-contact-created.mjs @@ -6,7 +6,7 @@ export default { key: "highlevel_oauth-new-contact-created", name: "New Contact Created", description: "Emit new event when a new contact is created. [See the documentation](https://highlevel.stoplight.io/docs/integrations/ab55933a57f6f-get-contacts)", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/highlevel_oauth/sources/new-form-submission/new-form-submission.mjs b/components/highlevel_oauth/sources/new-form-submission/new-form-submission.mjs index 854924310b91a..cbde39d2c81a8 100644 --- a/components/highlevel_oauth/sources/new-form-submission/new-form-submission.mjs +++ b/components/highlevel_oauth/sources/new-form-submission/new-form-submission.mjs @@ -6,7 +6,7 @@ export default { key: "highlevel_oauth-new-form-submission", name: "New Form Submission", description: "Emit new event when a new form submission is created. [See the documentation](https://highlevel.stoplight.io/docs/integrations/a6114bd7685d1-get-forms-submissions)", - version: "0.0.1", + version: "0.0.2", type: "source", dedupe: "unique", methods: { diff --git a/components/highlevel_oauth/sources/note-updated/note-updated.mjs b/components/highlevel_oauth/sources/note-updated/note-updated.mjs new file mode 100644 index 0000000000000..a1855d286b73d --- /dev/null +++ b/components/highlevel_oauth/sources/note-updated/note-updated.mjs @@ -0,0 +1,63 @@ +import common from "../common/base-polling.mjs"; +import md5 from "md5"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "highlevel_oauth-note-updated", + name: "Note Updated", + description: "Emit new event when an existing note is updated. [See the documentation](https://highlevel.stoplight.io/docs/integrations/73decb4b6d0c2-get-all-notes)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + contactId: { + propDefinition: [ + common.props.app, + "contactId", + ], + }, + }, + methods: { + ...common.methods, + _getNoteValues() { + return this.db.get("noteValues") || {}; + }, + _setNoteValues(noteValues) { + this.db.set("noteValues", noteValues); + }, + generateMeta(note) { + const ts = Date.now(); + return { + id: `${note.id}${ts}`, + summary: `Note Updated w/ ID: ${note.id}`, + ts, + }; + }, + }, + async run() { + const results = []; + const noteValues = this._getNoteValues(); + const newNoteValues = {}; + + const { notes } = await this.app.listNotes({ + contactId: this.contactId, + }); + for (const note of notes) { + const hash = md5(JSON.stringify(note)); + if (noteValues[note.id] && noteValues[note.id] !== hash) { + results.push(note); + } + newNoteValues[note.id] = hash; + } + + results.forEach((note) => { + const meta = this.generateMeta(note); + this.$emit(note, meta); + }); + + this._setNoteValues(newNoteValues); + }, + sampleEmit, +}; diff --git a/components/highlevel_oauth/sources/note-updated/test-event.mjs b/components/highlevel_oauth/sources/note-updated/test-event.mjs new file mode 100644 index 0000000000000..8f900fbc54d7e --- /dev/null +++ b/components/highlevel_oauth/sources/note-updated/test-event.mjs @@ -0,0 +1,9 @@ +export default { + "id": "DqaZSho8TS7PF8QMBv8J", + "body": "hello!", + "userId": "t8lsEHvfdlZugCobd4t7", + "dateAdded": "2025-04-04T16:00:06.997Z", + "contactId": "vJkzc8Nbho6FACjlsTUY", + "businessId": "67f001fe67c9c5b961bd6d3e", + "relations": [] +} \ No newline at end of file diff --git a/components/highlevel_oauth/sources/record-updated/record-updated.mjs b/components/highlevel_oauth/sources/record-updated/record-updated.mjs new file mode 100644 index 0000000000000..5b3ea75a555df --- /dev/null +++ b/components/highlevel_oauth/sources/record-updated/record-updated.mjs @@ -0,0 +1,70 @@ +import common from "../common/base-polling.mjs"; +import md5 from "md5"; + +export default { + ...common, + key: "highlevel_oauth-record-updated", + name: "Record Updated", + description: "Emit new event when a record is created or updated. [See the documentation](https://highlevel.stoplight.io/docs/integrations/0d0d041fb90fb-search-object-records)", + version: "0.0.1", + type: "source", + dedupe: "unique", + props: { + ...common.props, + schemaKey: { + propDefinition: [ + common.props.app, + "schemaKey", + ], + }, + }, + methods: { + ...common.methods, + _getRecordValues() { + return this.db.get("recordValues") || {}; + }, + _setRecordValues(recordValues) { + this.db.set("recordValues", recordValues); + }, + generateMeta(record, createdOrUpdated) { + const ts = Date.now(); + return { + id: `${record.id}${ts}`, + summary: `Record ${createdOrUpdated} w/ ID: ${record.id}`, + ts, + }; + }, + }, + async run() { + const params = { + page: 1, + pageLimit: 100, + locationId: this.app.getLocationId(), + schemaKey: this.schemaKey, + }; + let total; + const recordValues = this._getRecordValues(); + const newRecordValues = {}; + + do { + const { customObjectRecords } = await this.app.searchRecords({ + params, + }); + for (const record of customObjectRecords) { + const hash = md5(JSON.stringify(record)); + if (recordValues[record.id] !== hash) { + const createOrUpdate = recordValues[record.id] + ? "Updated" + : "Created"; + const meta = this.generateMeta(record, createOrUpdate); + this.$emit(record, meta); + } + newRecordValues[record.id] = hash; + } + params.page++; + total = customObjectRecords?.length; + } while (total === params.pageLimit); + + this._setRecordValues(newRecordValues); + }, +}; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3691b3a7fe97c..b5a178dc86bf9 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1486,8 +1486,7 @@ importers: components/bigpicture_io: {} - components/bika_ai: - specifiers: {} + components/bika_ai: {} components/bilflo: dependencies: @@ -6021,6 +6020,9 @@ importers: '@pipedream/platform': specifier: ^3.0.3 version: 3.0.3 + md5: + specifier: ^2.3.0 + version: 2.3.0 components/highrise: dependencies: