diff --git a/components/cats/actions/add-candidate-pipeline/add-candidate-pipeline.mjs b/components/cats/actions/add-candidate-pipeline/add-candidate-pipeline.mjs new file mode 100644 index 0000000000000..92e7914068faf --- /dev/null +++ b/components/cats/actions/add-candidate-pipeline/add-candidate-pipeline.mjs @@ -0,0 +1,58 @@ +import cats from "../../cats.app.mjs"; + +export default { + key: "cats-add-candidate-pipeline", + name: "Add Candidate to Job Pipeline", + description: "Adds a specific candidate to a job pipeline in CATS. [See the documentation](https://docs.catsone.com/api/v3/#jobs-create-a-job)", + version: "0.0.1", + type: "action", + props: { + cats, + createActivity: { + type: "boolean", + label: "Create Activity", + description: "Whether a corresponding activity should be created automatically. This mimics what happens when a pipeline is created from the CATS UI.", + optional: true, + }, + candidateId: { + propDefinition: [ + cats, + "candidateId", + ], + }, + jobId: { + propDefinition: [ + cats, + "jobId", + ], + }, + rating: { + type: "integer", + label: "Rating", + description: "The record's rating for the job (0-5).", + optional: true, + }, + }, + async run({ $ }) { + const { headers } = await this.cats.addCandidateToJobPipeline({ + $, + returnFullResponse: true, + params: { + create_activity: this.createActivity, + }, + data: { + candidate_id: this.candidateId, + job_id: this.jobId, + rating: this.rating, + }, + }); + + const location = headers.location.split("/"); + const pipelineId = location[location.length - 1]; + + $.export("$summary", `Successfully added candidate ID ${this.candidateId} to job ID ${this.jobId}`); + return { + pipelineId, + }; + }, +}; diff --git a/components/cats/actions/create-candidate/create-candidate.mjs b/components/cats/actions/create-candidate/create-candidate.mjs new file mode 100644 index 0000000000000..6dd807516ebfe --- /dev/null +++ b/components/cats/actions/create-candidate/create-candidate.mjs @@ -0,0 +1,308 @@ +import cats from "../../cats.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "cats-create-candidate", + name: "Create Candidate", + description: "Create a new candidate in your CATS database. [See the documentation](https://docs.catsone.com/api/v3/#candidates-create-a-candidate)", + version: "0.0.1", + type: "action", + props: { + cats, + checkDuplicate: { + propDefinition: [ + cats, + "checkDuplicate", + ], + }, + firstName: { + propDefinition: [ + cats, + "firstName", + ], + }, + middleName: { + propDefinition: [ + cats, + "middleName", + ], + optional: true, + }, + lastName: { + propDefinition: [ + cats, + "lastName", + ], + }, + title: { + propDefinition: [ + cats, + "title", + ], + optional: true, + }, + emails: { + propDefinition: [ + cats, + "emails", + ], + optional: true, + }, + phones: { + propDefinition: [ + cats, + "phones", + ], + optional: true, + }, + addressStreet: { + propDefinition: [ + cats, + "addressStreet", + ], + optional: true, + }, + addressCity: { + propDefinition: [ + cats, + "addressCity", + ], + optional: true, + }, + addressState: { + propDefinition: [ + cats, + "addressState", + ], + optional: true, + }, + addressPostalCode: { + propDefinition: [ + cats, + "addressPostalCode", + ], + optional: true, + }, + countryCode: { + propDefinition: [ + cats, + "countryCode", + ], + optional: true, + }, + socialMediaUrls: { + propDefinition: [ + cats, + "socialMediaUrls", + ], + optional: true, + }, + website: { + type: "string", + label: "Website", + description: "The website of the record.", + optional: true, + }, + bestTimeToCall: { + propDefinition: [ + cats, + "bestTimeToCall", + ], + optional: true, + }, + currentEmployer: { + propDefinition: [ + cats, + "currentEmployer", + ], + optional: true, + }, + dateAvailable: { + type: "string", + label: "Date Available", + description: "The date the record is available for an opening. **Format: YYYY-MM-DD**.", + optional: true, + }, + currentPay: { + type: "string", + label: "Current Pay", + description: "The current pay of the record.", + optional: true, + }, + desiredPay: { + propDefinition: [ + cats, + "desiredPay", + ], + optional: true, + }, + isWillingToRelocate: { + propDefinition: [ + cats, + "isWillingToRelocate", + ], + optional: true, + }, + keySkills: { + propDefinition: [ + cats, + "keySkills", + ], + optional: true, + }, + notes: { + propDefinition: [ + cats, + "notes", + ], + optional: true, + }, + source: { + propDefinition: [ + cats, + "source", + ], + optional: true, + }, + ownerId: { + propDefinition: [ + cats, + "ownerId", + ], + optional: true, + }, + isActive: { + type: "boolean", + label: "Is Active", + description: "A flag indicating if the candidate is active.", + optional: true, + }, + isHot: { + propDefinition: [ + cats, + "isHot", + ], + optional: true, + }, + password: { + type: "string", + label: "password", + description: "The candidate's password if they are \"registering\".", + secret: true, + optional: true, + }, + customFields: { + propDefinition: [ + cats, + "customFields", + ], + withLabel: true, + reloadProps: true, + optional: true, + }, + workHistory: { + propDefinition: [ + cats, + "workHistory", + ], + optional: true, + }, + }, + async additionalProps() { + const props = {}; + (this.customFields || []).map(({ + label, value, + }) => { + props[value] = { + type: "string", + label: `Custom Field: ${label}`, + optional: true, + }; + }, {}); + + return props; + }, + async run({ $ }) { + const { + cats, // eslint-disable-next-line no-unused-vars + customFields, + firstName, + lastName, + ownerId, + middleName, + checkDuplicate, + bestTimeToCall, + currentEmployer, + emails, + phones, + addressStreet, + addressCity, + addressState, + addressPostalCode, + countryCode, + socialMediaUrls, + dateAvailable, + currentPay, + desiredPay, + isWillingToRelocate, + keySkills, + isActive, + isHot, + workHistory, + ...data + } = this; + + const customFieldsObject = customFields + ? customFields.map(({ value }) => { + return { + id: value, + value: data[value], + }; + }) + : {}; + + const { headers } = await cats.createCandidate({ + $, + returnFullResponse: true, + params: { + check_duplicate: checkDuplicate, + }, + data: { + first_name: firstName, + middle_name: middleName, + last_name: lastName, + emails: parseObject(emails), + phones: parseObject(phones), + address: { + street: addressStreet, + city: addressCity, + state: addressState, + postal_code: addressPostalCode, + }, + country_code: countryCode, + social_media_urls: parseObject(socialMediaUrls), + best_time_to_call: bestTimeToCall, + current_employer: currentEmployer, + date_available: dateAvailable, + current_pay: currentPay, + desired_pay: desiredPay, + is_willing_to_relocate: isWillingToRelocate, + key_skills: keySkills, + owner_id: ownerId, + is_active: isActive, + is_hot: isHot, + work_history: parseObject(workHistory), + custom_fields: customFieldsObject, + ...data, + }, + }); + + const location = headers.location.split("/"); + const candidateId = location[location.length - 1]; + + $.export("$summary", `Created candidate with ID ${candidateId}`); + return { + candidateId, + }; + }, +}; diff --git a/components/cats/actions/create-contact/create-contact.mjs b/components/cats/actions/create-contact/create-contact.mjs new file mode 100644 index 0000000000000..e1ffe64da8b38 --- /dev/null +++ b/components/cats/actions/create-contact/create-contact.mjs @@ -0,0 +1,226 @@ +import cats from "../../cats.app.mjs"; +import { parseObject } from "../../common/utils.mjs"; + +export default { + key: "cats-create-contact", + name: "Create Contact", + description: "Adds a new contact to the CATS platform. [See the documentation](https://docs.catsone.com/api/v3/#contacts-create-a-contact)", + version: "0.0.1", + type: "action", + props: { + cats, + firstName: { + propDefinition: [ + cats, + "firstName", + ], + }, + lastName: { + propDefinition: [ + cats, + "lastName", + ], + }, + ownerId: { + propDefinition: [ + cats, + "ownerId", + ], + }, + companyId: { + propDefinition: [ + cats, + "companyId", + ], + }, + checkDuplicate: { + propDefinition: [ + cats, + "checkDuplicate", + ], + optional: true, + }, + title: { + propDefinition: [ + cats, + "title", + ], + optional: true, + }, + reportsToId: { + propDefinition: [ + cats, + "contactId", + ], + label: "Reports To Id", + description: "The ID of the contact that this contact reports to.", + optional: true, + }, + hasLeftCompany: { + type: "boolean", + label: "Has Left Company", + description: "Whether the contact has left the company or not.", + optional: true, + }, + emails: { + propDefinition: [ + cats, + "emails", + ], + optional: true, + }, + phones: { + propDefinition: [ + cats, + "phones", + ], + optional: true, + }, + addressStreet: { + propDefinition: [ + cats, + "addressStreet", + ], + optional: true, + }, + addressCity: { + propDefinition: [ + cats, + "addressCity", + ], + optional: true, + }, + addressState: { + propDefinition: [ + cats, + "addressState", + ], + optional: true, + }, + addressPostalCode: { + propDefinition: [ + cats, + "addressPostalCode", + ], + optional: true, + }, + countryCode: { + propDefinition: [ + cats, + "countryCode", + ], + optional: true, + }, + socialMediaUrls: { + propDefinition: [ + cats, + "socialMediaUrls", + ], + optional: true, + }, + isHot: { + propDefinition: [ + cats, + "isHot", + ], + optional: true, + }, + notes: { + propDefinition: [ + cats, + "notes", + ], + optional: true, + }, + customFields: { + propDefinition: [ + cats, + "customFields", + ], + withLabel: true, + reloadProps: true, + optional: true, + }, + }, + async additionalProps() { + const props = {}; + (this.customFields || []).map(({ + label, value, + }) => { + props[value] = { + type: "string", + label: `Custom Field: ${label}`, + optional: true, + }; + }, {}); + + return props; + }, + async run({ $ }) { + const { + cats, // eslint-disable-next-line no-unused-vars + customFields, + firstName, + lastName, + ownerId, + companyId, + checkDuplicate, + reportsToId, + hasLeftCompany, + emails, + phones, + addressStreet, + addressCity, + addressState, + addressPostalCode, + countryCode, + socialMediaUrls, + ...data + } = this; + + const customFieldsObject = customFields + ? customFields.map(({ value }) => { + return { + id: value, + value: data[value], + }; + }) + : {}; + + const { headers } = await cats.createContact({ + $, + returnFullResponse: true, + params: { + check_duplicate: checkDuplicate, + }, + data: { + first_name: firstName, + last_name: lastName, + owner_id: ownerId, + company_id: companyId, + reports_to_id: reportsToId, + has_left_company: hasLeftCompany, + emails: parseObject(emails), + phones: parseObject(phones), + address: { + street: addressStreet, + city: addressCity, + state: addressState, + postal_code: addressPostalCode, + }, + country_code: countryCode, + social_media_urls: parseObject(socialMediaUrls), + custom_fields: customFieldsObject, + ...data, + }, + }); + + const location = headers.location.split("/"); + const contactId = location[location.length - 1]; + + $.export("$summary", `Contact successfully created with Id: ${contactId}!`); + return { + contactId, + }; + }, +}; diff --git a/components/cats/cats.app.mjs b/components/cats/cats.app.mjs index 35105682c0dee..74694a4ba14ba 100644 --- a/components/cats/cats.app.mjs +++ b/components/cats/cats.app.mjs @@ -1,11 +1,342 @@ +import { axios } from "@pipedream/platform"; + export default { type: "app", app: "cats", - propDefinitions: {}, + propDefinitions: { + candidateId: { + type: "integer", + label: "Candidate ID", + description: "The ID of the candidate.", + async options({ page }) { + const { _embedded } = await this.listCandidates({ + params: { + page: page + 1, + }, + }); + + return _embedded + ? _embedded.candidates.map(({ + id, first_name: fName, last_name: lName, emails: { + primary, secondary, + }, + }) => ({ + label: `${fName} ${lName} ${primary || secondary + ? `(${primary || secondary})` + : ""}`, + value: parseInt(id), + })) + : []; + }, + }, + companyId: { + type: "integer", + label: "Company ID", + description: "The company ID related to the contact.", + async options({ page }) { + const { _embedded } = await this.listCompanies({ + params: { + page: page + 1, + }, + }); + + return _embedded + ? _embedded.companies.map(({ + id: value, name: label, + }) => ({ + label, + value, + })) + : []; + }, + }, + contactId: { + type: "integer", + label: "Contact Id", + description: "The ID of the contact.", + async options({ page }) { + const { _embedded } = await this.listContacts({ + params: { + page: page + 1, + }, + }); + + return _embedded + ? _embedded.contacts.map(({ + id, first_name: fName, last_name: lName, emails: { primary }, + }) => ({ + label: `${fName} ${lName} ${primary + ? `(${primary})` + : ""}`, + value: parseInt(id), + })) + : []; + }, + }, + ownerId: { + type: "integer", + label: "Owner ID", + description: "The user id of the record owner.", + async options({ page }) { + const { _embedded } = await this.listUsers({ + params: { + page: page + 1, + }, + }); + + return _embedded + ? _embedded.users.map(({ + id: value, username: label, + }) => ({ + label, + value, + })) + : []; + }, + }, + customFields: { + type: "string[]", + label: "Custom Fields", + description: "An array of custom field objects. ", + async options({ page }) { + const { _embedded } = await this.listCustomFields({ + params: { + page: page + 1, + }, + }); + + return _embedded + ? _embedded.custom_fields.map(({ + id: value, name: label, + }) => ({ + label, + value, + })) + : []; + }, + }, + jobId: { + type: "integer", + label: "Job ID", + description: "The ID of the job to which the record is added.", + async options({ page }) { + const { _embedded } = await this.listJobs({ + params: { + page: page + 1, + }, + }); + + return _embedded + ? _embedded.jobs.map(({ + id: value, title: label, + }) => ({ + label, + value, + })) + : []; + }, + }, + firstName: { + type: "string", + label: "First Name", + description: "The first name of the record.", + }, + middleName: { + type: "string", + label: "Middle Name", + description: "The middle name of the record.", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The last name of the record.", + }, + checkDuplicate: { + type: "boolean", + label: "Check Duplicate", + description: "When this flag is set to true, if a duplicate record is found to the one being created, an error will be thrown instead of creating a duplicate record.", + default: false, + }, + title: { + type: "string", + label: "Title", + description: "The record's job title.", + }, + emails: { + type: "string[]", + label: "Emails", + description: "An array of email objects. Each email object should contain two keys: `email` and `is_primary`, as described [here](https://docs.catsone.com/api/v3/#contacts-create-a-contact-email). **Format: {\"email\":\"example@email.com\", \"is_primary\":\"true\"}**", + }, + isHot: { + type: "boolean", + label: "Is Hot", + description: "Whether the record is marked as hot.", + }, + phones: { + type: "string[]", + label: "Phones", + description: "An array of phone objects. Each phone object should contain three keys: `number`, `extension`, and `type`, as described [here](https://docs.catsone.com/api/v3/#contacts-create-a-contact-phone). **Format: {\"number\":\"+16124063451\", \"extension\":\"8371\", \"type\":\"mobile\"}**. Type can be `mobile`, `home`, `work`, `fax`, `main` or `other`", + }, + addressStreet: { + type: "string", + label: "Street Address", + description: "The street of the record's address.", + }, + addressCity: { + type: "string", + label: "City Address", + description: "The city of the record's address.", + }, + addressState: { + type: "string", + label: "State Address", + description: "The state of the record's address.", + }, + addressPostalCode: { + type: "string", + label: "Address Postal Code", + description: "The postal code of the record's address.", + }, + countryCode: { + type: "string", + label: "Country Code", + description: "The country code of the record.", + }, + socialMediaUrls: { + type: "string[]", + label: "Social Media URLs", + description: "The social media URLs of the record.", + }, + notes: { + type: "string", + label: "Notes", + description: "Any notes about the record.", + }, + bestTimeToCall: { + type: "string", + label: "Best Time to Call", + description: "The best time to call the record. **Format: HH:MM**.", + }, + currentEmployer: { + type: "string", + label: "Current Employer", + description: "The current employer of the record.", + }, + desiredPay: { + type: "string", + label: "Desired Pay", + description: "The desired pay for the record.", + }, + isWillingToRelocate: { + type: "boolean", + label: "Willing to Relocate", + description: "Whether the record is willing to relocate.", + }, + keySkills: { + type: "string", + label: "Key Skills", + description: "The key skills of the record.", + }, + source: { + type: "string", + label: "Source", + description: "The source of the record.", + }, + workHistory: { + type: "string[]", + label: "Work History", + description: "An array of work history objects. **Example: {\"title\": \"Engineer\", \"employer\": { \"linked\": false, \"name\": \"\", \"location\": { \"city\": \"\", \"state\": \"\" }}, \"supervisor\": { \"linked\": false, \"name\": \"\", \"phone\": \"\" }, \"is_verified\": true, \"is_current\": false, \"start_date\": \"YYYY-MM-DD\", \"end_date\": \"YYYY-MM-DD\", \"reason_for_leaving\": \"foo\"}**", + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://api.catsone.com/v3"; + }, + _headers() { + return { + Authorization: `Token ${this.$auth.api_key}`, + }; + }, + _makeRequest({ + $ = this, path, ...opts + }) { + const config = { + url: this._baseUrl() + path, + headers: this._headers(), + ...opts, + }; + console.log("config: ", config); + return axios($, config); + }, + listCandidates(opts = {}) { + return this._makeRequest({ + path: "/candidates", + ...opts, + }); + }, + listCompanies(opts = {}) { + return this._makeRequest({ + path: "/companies", + ...opts, + }); + }, + listContacts(opts = {}) { + return this._makeRequest({ + path: "/contacts", + ...opts, + }); + }, + listCustomFields(opts = {}) { + return this._makeRequest({ + path: "/contacts/custom_fields", + ...opts, + }); + }, + listJobs(opts = {}) { + return this._makeRequest({ + path: "/jobs", + ...opts, + }); + }, + listUsers(opts = {}) { + return this._makeRequest({ + path: "/users", + ...opts, + }); + }, + createCandidate(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/candidates", + ...opts, + }); + }, + addCandidateToJobPipeline(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/pipelines", + ...opts, + }); + }, + createContact(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/contacts", + ...opts, + }); + }, + createWebhook(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/webhooks", + ...opts, + }); + }, + deleteWebhook(webhookId) { + return this._makeRequest({ + method: "DELETE", + path: `/webhooks/${webhookId}`, + }); }, }, }; diff --git a/components/cats/common/utils.mjs b/components/cats/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/cats/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/cats/package.json b/components/cats/package.json index 08d007c23d152..52701eb2cf6a7 100644 --- a/components/cats/package.json +++ b/components/cats/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/cats", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream CATS Components", "main": "cats.app.mjs", "keywords": [ @@ -11,5 +11,9 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3", + "crypto": "^1.0.1" } -} \ No newline at end of file +} diff --git a/components/cats/sources/common/base.mjs b/components/cats/sources/common/base.mjs new file mode 100644 index 0000000000000..76c33f18ae210 --- /dev/null +++ b/components/cats/sources/common/base.mjs @@ -0,0 +1,80 @@ +import { + createHmac, randomUUID, +} from "crypto"; +import cats from "../../cats.app.mjs"; + +export default { + props: { + cats, + http: { + type: "$.interface.http", + customResponse: true, + }, + db: "$.service.db", + }, + methods: { + _getUUID() { + return this.db.get("UUID"); + }, + _setUUID(UUID) { + this.db.set("UUID", UUID); + }, + _getHookId() { + return this.db.get("hookId"); + }, + _setHookId(hookId) { + this.db.set("hookId", hookId); + }, + getExtraData() { + return {}; + }, + checkSignature({ + bodyRaw, headers, + }) { + const uuid = this._getUUID(); + const hash = createHmac("sha256", uuid).update(`${bodyRaw}${headers["x-request-id"]}`) + .digest() + .toString("hex"); + + console.log("hash: ", hash); + + return headers["x-signature"] === `HMAC-SHA256 ${hash}`; + }, + }, + hooks: { + async activate() { + const uuid = randomUUID(); + const { headers } = await this.cats.createWebhook({ + returnFullResponse: true, + data: { + target_url: this.http.endpoint, + events: this.getEventType(), + secret: uuid, + }, + }); + + const location = headers.location.split("/"); + const webhookId = location[location.length - 1]; + + this._setUUID(uuid); + this._setHookId(webhookId); + }, + async deactivate() { + const webhookId = this._getHookId(); + await this.cats.deleteWebhook(webhookId); + }, + }, + async run({ + body, ...event + }) { + if (!this.checkSignature(event)) { + return this.http.respond({ + status: 400, + }); + } + + console.log("this.generateMeta(body): ", this.generateMeta(body)); + + this.$emit(body, this.generateMeta(body)); + }, +}; diff --git a/components/cats/sources/new-activity-instant/new-activity-instant.mjs b/components/cats/sources/new-activity-instant/new-activity-instant.mjs new file mode 100644 index 0000000000000..0b4d4d010dd5a --- /dev/null +++ b/components/cats/sources/new-activity-instant/new-activity-instant.mjs @@ -0,0 +1,28 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cats-new-activity-instant", + name: "New Activity (Instant)", + description: "Emit new event when an activity related to a cat is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return [ + "activity.created", + ]; + }, + generateMeta(body) { + return { + id: body.activity_id, + summary: `New activity: ${body.activity_id}`, + ts: Date.parse(body.date || new Date()), + }; + }, + }, + sampleEmit, +}; diff --git a/components/cats/sources/new-activity-instant/test-event.mjs b/components/cats/sources/new-activity-instant/test-event.mjs new file mode 100644 index 0000000000000..f1fe0026dd622 --- /dev/null +++ b/components/cats/sources/new-activity-instant/test-event.mjs @@ -0,0 +1,41 @@ +export default { + "event": "activity.created", + "activity_id": 123456789, + "date": "2024-11-19T19:57:54+00:00", + "_links": { + "activity": { + "href": "/activities/123456789" + } + }, + "_embedded": { + "activity": { + "id": 123456789, + "data_item": { + "id": 123456789, + "type": "candidate" + }, + "date": "2024-11-19T19:57:54+00:00", + "regarding_id": 123456789, + "type": "other", + "notes": null, + "annotation": "Added candidate to pipeline: No Contact", + "entered_by_id": 123456789, + "date_created": "2024-11-19T19:57:54+00:00", + "date_modified": "2024-11-19T19:57:54+00:00", + "_links": { + "self": { + "href": "/activities/123456789" + }, + "regarding": { + "href": "/jobs/123456789" + }, + "data_item": { + "href": "/candidates/123456789" + }, + "entered_by": { + "href": "/users/123456789" + } + } + } + } +} \ No newline at end of file diff --git a/components/cats/sources/new-candidate-instant/new-candidate-instant.mjs b/components/cats/sources/new-candidate-instant/new-candidate-instant.mjs new file mode 100644 index 0000000000000..5a2418396bbe0 --- /dev/null +++ b/components/cats/sources/new-candidate-instant/new-candidate-instant.mjs @@ -0,0 +1,29 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cats-new-candidate-instant", + name: "New Candidate (Instant)", + description: "Emit new event when a new candidate is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return [ + "candidate.created", + ]; + }, + generateMeta(body) { + const candidate = body._embedded.candidate; + return { + id: body.candidate_id, + summary: `New candidate created: ${candidate.first_name} ${candidate.last_name} (${candidate.emails.primary || candidate.emails.second})`, + ts: Date.parse(body.date || new Date()), + }; + }, + }, + sampleEmit, +}; diff --git a/components/cats/sources/new-candidate-instant/test-event.mjs b/components/cats/sources/new-candidate-instant/test-event.mjs new file mode 100644 index 0000000000000..34e1779782171 --- /dev/null +++ b/components/cats/sources/new-candidate-instant/test-event.mjs @@ -0,0 +1,112 @@ +export default { + "event": "candidate.created", + "candidate_id": 123456789, + "date": "2024-11-19T20:13:12+00:00", + "_links": { + "candidate": { + "href": "/candidates/123456789" + } + }, + "_embedded": { + "candidate": { + "id": 123456789, + "first_name": "Candidate Name", + "middle_name": "Middle Name", + "last_name": "Last Name", + "title": "Candidate Tittle", + "emails": { + "primary": "candidate@email.com", + "secondary": null + }, + "address": { + "street": "street", + "city": "city", + "state": "CA", + "postal_code": "18234" + }, + "country_code": "US", + "social_media_urls": [], + "website": "https://website.com", + "phones": { + "home": null, + "cell": "1234567890", + "work": null + }, + "best_time_to_call": "14:00", + "current_employer": "CurrentEmployer", + "date_available": "2024-12-12", + "current_pay": "400", + "desired_pay": "8000", + "is_willing_to_relocate": true, + "key_skills": "", + "notes": "", + "is_hot": true, + "is_active": true, + "contact_id": null, + "owner_id": 123456, + "entered_by_id": 123456, + "source": "", + "is_registered": true, + "consent_status": null, + "date_created": "2024-11-19T20:13:12+00:00", + "date_modified": "2024-11-19T20:13:12+00:00", + "_links": { + "self": { + "href": "/candidates/123456789" + }, + "custom_fields": { + "href": "/candidates/123456789/custom_fields" + }, + "attachments": { + "href": "/candidates/123456789/attachments" + }, + "activities": { + "href": "/candidates/123456789/activities" + }, + "work_history": { + "href": "/candidates/123456789/work_history" + }, + "pipelines": { + "href": "/candidates/123456789/pipelines" + }, + "tags": { + "href": "/candidates/123456789/tags" + }, + "thumbnail": { + "href": "/candidates/123456789/thumbnail" + }, + "phones": { + "href": "/candidates/123456789/phones" + }, + "emails": { + "href": "/candidates/123456789/emails" + }, + "owner": { + "href": "/users/123456" + }, + "entered_by": { + "href": "/users/123456" + } + }, + "_embedded": { + "custom_fields": [], + "work_history": [], + "thumbnail": [ + { + "id": 92680, + "source": "gravatar", + "attachment_id": null, + "url": "https://pipedream.catsone.com/candidates/123456789/thumbnail?_s=e1221ae117fecea3c20ec9075dfb36ac05e876d1be10725e27b4eeb42289bd65", + "_links": { + "self": { + "href": "/candidates/123456789/thumbnail" + } + } + } + ], + "phones": [], + "emails": [] + } + } + } +} \ No newline at end of file diff --git a/components/cats/sources/new-contact-instant/new-contact-instant.mjs b/components/cats/sources/new-contact-instant/new-contact-instant.mjs new file mode 100644 index 0000000000000..a3ae27151b3de --- /dev/null +++ b/components/cats/sources/new-contact-instant/new-contact-instant.mjs @@ -0,0 +1,29 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "cats-new-contact-instant", + name: "New Contact Created (Instant)", + description: "Emit new event when a contact related to a cat is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getEventType() { + return [ + "contact.created", + ]; + }, + generateMeta(body) { + const contact = body._embedded.contact; + return { + id: body.contact_id, + summary: `New contact: ${contact.first_name} ${contact.last_name}`, + ts: Date.parse(body.date || new Date()), + }; + }, + }, + sampleEmit, +}; diff --git a/components/cats/sources/new-contact-instant/test-event.mjs b/components/cats/sources/new-contact-instant/test-event.mjs new file mode 100644 index 0000000000000..3fd9ac647f591 --- /dev/null +++ b/components/cats/sources/new-contact-instant/test-event.mjs @@ -0,0 +1,103 @@ +export default { + "event": "contact.created", + "contact_id": 123456789, + "date": "2024-11-19T20:21:10+00:00", + "_links": { + "contact": { + "href": "/contacts/123456789" + } + }, + "_embedded": { + "contact": { + "id": "123456789", + "first_name": "Contact Name", + "last_name": "Last Name", + "title": "Contact title", + "reports_to_id": 1234567, + "owner_id": 123456, + "company_id": 22978541, + "emails": { + "primary": "contact@email.com", + "secondary": null + }, + "phones": { + "work": null, + "cell": "12345678", + "other": null + }, + "address": { + "street": "street", + "city": "city", + "state": "CA", + "postal_code": "92132" + }, + "country_code": "US", + "social_media_urls": [], + "is_hot": false, + "has_left_company": false, + "notes": "", + "entered_by_id": 123456, + "consent_status": null, + "date_created": "2024-11-19T20:21:10+00:00", + "date_modified": "2024-11-19T20:21:10+00:00", + "status_id": 123456, + "_links": { + "self": { + "href": "/contacts/123456789" + }, + "reports_to": { + "href": "/users/1234567" + }, + "custom_fields": { + "href": "/contacts/123456789/custom_fields" + }, + "activities": { + "href": "/contacts/123456789/activities" + }, + "status": { + "href": "/contacts/statuses/123456" + }, + "entered_by": { + "href": "/users/123456" + }, + "owner": { + "href": "/users/123456" + }, + "attachments": { + "href": "/contacts/123456789/attachments" + }, + "tags": { + "href": "/contacts/123456789/tags" + }, + "thumbnail": { + "href": "/contacts/123456789/thumbnail" + }, + "phones": { + "href": "/contacts/123456789/phones" + }, + "emails": { + "href": "/contacts/123456789/emails" + } + }, + "_embedded": { + "custom_fields": [], + "status": { + "id": 123456, + "workflow_id": 5696671, + "title": "No Status", + "mapping": "", + "prerequisites": [], + "triggers": [], + "_links": { + "self": { + "href": "contacts/statuses/123456" + } + } + }, + "thumbnail": [], + "phones": [], + "emails": [] + } + } + } +} \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1bb40f3699cb3..5cb6e8380fbe0 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1545,7 +1545,12 @@ importers: '@pipedream/platform': 1.6.0 components/cats: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + crypto: ^1.0.1 + dependencies: + '@pipedream/platform': 3.0.3 + crypto: 1.0.1 components/cdc_national_environmental_public_health_tracking: specifiers: {} @@ -13414,55 +13419,6 @@ packages: - aws-crt dev: false - /@aws-sdk/client-sso-oidc/3.600.0_tdq3komn4zwyd65w7klbptsu34: - resolution: {integrity: sha512-7+I8RWURGfzvChyNQSyj5/tKrqRbzRl7H+BnTOf/4Vsw1nFOi5ROhlhD4X/Y0QCTacxnaoNcIrqnY7uGGvVRzw==} - engines: {node: '>=16.0.0'} - dependencies: - '@aws-crypto/sha256-browser': 5.2.0 - '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sts': 3.600.0 - '@aws-sdk/core': 3.598.0 - '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 - '@aws-sdk/middleware-host-header': 3.598.0 - '@aws-sdk/middleware-logger': 3.598.0 - '@aws-sdk/middleware-recursion-detection': 3.598.0 - '@aws-sdk/middleware-user-agent': 3.598.0 - '@aws-sdk/region-config-resolver': 3.598.0 - '@aws-sdk/types': 3.598.0 - '@aws-sdk/util-endpoints': 3.598.0 - '@aws-sdk/util-user-agent-browser': 3.598.0 - '@aws-sdk/util-user-agent-node': 3.598.0 - '@smithy/config-resolver': 3.0.3 - '@smithy/core': 2.2.3 - '@smithy/fetch-http-handler': 3.2.1 - '@smithy/hash-node': 3.0.2 - '@smithy/invalid-dependency': 3.0.2 - '@smithy/middleware-content-length': 3.0.2 - '@smithy/middleware-endpoint': 3.0.4 - '@smithy/middleware-retry': 3.0.6 - '@smithy/middleware-serde': 3.0.3 - '@smithy/middleware-stack': 3.0.3 - '@smithy/node-config-provider': 3.1.3 - '@smithy/node-http-handler': 3.1.2 - '@smithy/protocol-http': 4.0.3 - '@smithy/smithy-client': 3.1.6 - '@smithy/types': 3.3.0 - '@smithy/url-parser': 3.0.3 - '@smithy/util-base64': 3.0.0 - '@smithy/util-body-length-browser': 3.0.0 - '@smithy/util-body-length-node': 3.0.0 - '@smithy/util-defaults-mode-browser': 3.0.6 - '@smithy/util-defaults-mode-node': 3.0.6 - '@smithy/util-endpoints': 2.0.3 - '@smithy/util-middleware': 3.0.3 - '@smithy/util-retry': 3.0.2 - '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 - transitivePeerDependencies: - - '@aws-sdk/client-sts' - - aws-crt - dev: false - /@aws-sdk/client-sso/3.423.0: resolution: {integrity: sha512-znIufHkwhCIePgaYciIs3x/+BpzR57CZzbCKHR9+oOvGyufEPPpUT5bFLvbwTgfiVkTjuk6sG/ES3U5Bc+xtrA==} engines: {node: '>=14.0.0'} @@ -13698,7 +13654,55 @@ packages: dependencies: '@aws-crypto/sha256-browser': 5.2.0 '@aws-crypto/sha256-js': 5.2.0 - '@aws-sdk/client-sso-oidc': 3.600.0_tdq3komn4zwyd65w7klbptsu34 + '@aws-sdk/client-sso-oidc': 3.600.0 + '@aws-sdk/core': 3.598.0 + '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 + '@aws-sdk/middleware-host-header': 3.598.0 + '@aws-sdk/middleware-logger': 3.598.0 + '@aws-sdk/middleware-recursion-detection': 3.598.0 + '@aws-sdk/middleware-user-agent': 3.598.0 + '@aws-sdk/region-config-resolver': 3.598.0 + '@aws-sdk/types': 3.598.0 + '@aws-sdk/util-endpoints': 3.598.0 + '@aws-sdk/util-user-agent-browser': 3.598.0 + '@aws-sdk/util-user-agent-node': 3.598.0 + '@smithy/config-resolver': 3.0.3 + '@smithy/core': 2.2.3 + '@smithy/fetch-http-handler': 3.2.1 + '@smithy/hash-node': 3.0.2 + '@smithy/invalid-dependency': 3.0.2 + '@smithy/middleware-content-length': 3.0.2 + '@smithy/middleware-endpoint': 3.0.4 + '@smithy/middleware-retry': 3.0.6 + '@smithy/middleware-serde': 3.0.3 + '@smithy/middleware-stack': 3.0.3 + '@smithy/node-config-provider': 3.1.3 + '@smithy/node-http-handler': 3.1.2 + '@smithy/protocol-http': 4.0.3 + '@smithy/smithy-client': 3.1.6 + '@smithy/types': 3.3.0 + '@smithy/url-parser': 3.0.3 + '@smithy/util-base64': 3.0.0 + '@smithy/util-body-length-browser': 3.0.0 + '@smithy/util-body-length-node': 3.0.0 + '@smithy/util-defaults-mode-browser': 3.0.6 + '@smithy/util-defaults-mode-node': 3.0.6 + '@smithy/util-endpoints': 2.0.3 + '@smithy/util-middleware': 3.0.3 + '@smithy/util-retry': 3.0.2 + '@smithy/util-utf8': 3.0.0 + tslib: 2.6.3 + transitivePeerDependencies: + - aws-crt + dev: false + + /@aws-sdk/client-sts/3.600.0_dseaa2p5u2yk67qiepewcq3hkq: + resolution: {integrity: sha512-KQG97B7LvTtTiGmjlrG1LRAY8wUvCQzrmZVV5bjrJ/1oXAU7DITYwVbSJeX9NWg6hDuSk0VE3MFwIXS2SvfLIA==} + engines: {node: '>=16.0.0'} + dependencies: + '@aws-crypto/sha256-browser': 5.2.0 + '@aws-crypto/sha256-js': 5.2.0 + '@aws-sdk/client-sso-oidc': 3.600.0 '@aws-sdk/core': 3.598.0 '@aws-sdk/credential-provider-node': 3.600.0_f7n47caigsrjd2lr2szmwfuee4 '@aws-sdk/middleware-host-header': 3.598.0 @@ -13737,6 +13741,7 @@ packages: '@smithy/util-utf8': 3.0.0 tslib: 2.6.3 transitivePeerDependencies: + - '@aws-sdk/client-sso-oidc' - aws-crt dev: false @@ -18083,7 +18088,7 @@ packages: '@aws-sdk/client-sns': 3.423.0 '@aws-sdk/client-sqs': 3.423.0 '@aws-sdk/client-ssm': 3.423.0 - '@aws-sdk/client-sts': 3.600.0 + '@aws-sdk/client-sts': 3.600.0_dseaa2p5u2yk67qiepewcq3hkq '@aws-sdk/s3-request-presigner': 3.609.0 '@pipedream/helper_functions': 0.3.12 '@pipedream/platform': 1.6.6