@lautmaler/crm-connectors
Version:
Provides connectors to various CRM systems and calendar services.
281 lines • 11.1 kB
JavaScript
import axios from "axios";
import winston from "winston";
import { GoogleCalendarBackend } from "./GoogleCalendarBackend.js";
import { CalendlyBackend } from "./CalendlyBackend.js";
const HUBSPOT_API_BASE_URL = "https://api.hubspot.com";
export class HubSpotBackend {
logger;
hubspotApiKey;
calenderBackend;
hubspotOwners = [];
employee;
constructor(backend, hubspotOwners, employee) {
this.hubspotOwners = hubspotOwners || [];
this.employee = employee || "";
this.logger = winston.createLogger({
level: "info",
format: winston.format.json(),
defaultMeta: { service: "crm-service" },
transports: [
new winston.transports.Console({
format: winston.format.simple(),
}),
],
});
this.hubspotApiKey = process.env.HUBSPOT_API_KEY || "";
switch (backend) {
case "google":
this.calenderBackend = new GoogleCalendarBackend();
break;
case "calendly":
this.calenderBackend = new CalendlyBackend();
break;
default:
throw new Error("Invalid backend specified");
}
}
getHeaders() {
return {
headers: {
Authorization: `Bearer ${this.hubspotApiKey}`,
"Content-Type": "application/json",
},
};
}
async fetchAvailableSlots(timestamp, attendees) {
return this.calenderBackend.fetchAvailableSlots(timestamp, attendees);
}
async fetchAppointmentTypes() {
// TODO: Implement fetching appointment types from HubSpot
return ["Consultation", "Meeting", "Demo"];
}
async getContactIdByName(contactName) {
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/contacts/search`;
const [firstName, lastName] = contactName.split(" "); // Split the name into first and last name
const data = {
filterGroups: [
{
filters: [
{
propertyName: "firstname",
operator: "EQ",
value: firstName,
},
{
propertyName: "lastname",
operator: "EQ",
value: lastName,
},
],
},
],
};
try {
const response = await axios.post(url, data, {
headers: {
Authorization: `Bearer ${this.hubspotApiKey}`,
"Content-Type": "application/json",
},
});
// Check if the contact exists and return the ID
if (response.data.results.length > 0) {
const contactId = response.data.results[0].id; // Get the first matching contact's ID
this.logger.info(`Contact found. ID: ${contactId}`);
return contactId; // Return the contact ID
}
else {
this.logger.info("Contact does not exist");
return null; // Contact not found, return null
}
}
catch (error) {
this.logger.error("Error fetching contact:", error.response?.data || error.message);
throw new Error("Failed to check if contact exists");
}
}
async createContact(contactName) {
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/contacts`;
const data = {
properties: {
firstname: contactName.split(" ")[0],
lastname: contactName.split(" ")[1], // Assumes name is in "First Last" format
},
};
try {
const response = (await axios.post(url, data, this.getHeaders()));
this.logger.info(response.data);
if (response.data?.id) {
return response.data.id;
}
else {
throw new Error("Failed to create contact");
}
}
catch (error) {
throw new Error("Failed to create contact: " + error.message);
}
}
async appointmentCreationCall(appointment, contactId, timestampMs, employee) {
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/meetings`;
const data = {
associations: [
{
types: [
{
associationCategory: "HUBSPOT_DEFINED",
associationTypeId: 200,
},
],
to: {
id: contactId,
},
},
],
properties: {
hs_timestamp: appointment.timestamp,
hs_meeting_start_time: timestampMs, // Start time in milliseconds
hs_meeting_end_time: timestampMs + 3600000, // End time (default: 1 hour later)
hubspot_owner_id: employee?.id,
hs_meeting_title: appointment.meetingTitle ?? `Meeting w/ ${appointment.contactName}`,
hs_internal_meeting_notes: appointment.meetingNotes,
hs_meeting_location: "Remote",
hs_meeting_outcome: "SCHEDULED",
},
};
try {
const response = await axios.post(url, data, this.getHeaders());
return response.data;
}
catch (error) {
this.logger.error("Error booking appointment:", error.response?.data || error.message);
throw new Error("Failed to book appointment.");
}
}
async bookAppointment(appointment) {
const employee = this.hubspotOwners.find((owner) => owner.lastName === appointment.employeeName);
const timestampMs = new Date(appointment.timestamp).getTime(); // Convert to milliseconds
const contactId = (await this.getContactIdByName(appointment.contactName)) ||
(await this.createContact(appointment.contactName));
const apiReponse = await this.appointmentCreationCall(appointment, contactId, timestampMs, employee);
const meetingUrl = `https://app.hubspot.com/meetings/${apiReponse.id}`;
return meetingUrl;
}
async modifyAppointment(id, updatedInfo) {
const url = `${HUBSPOT_API_BASE_URL}/engagements/v1/engagements/${id}`;
// Prepare the data for the API request
const data = {
engagement: {
type: "MEETING",
},
metadata: {},
};
// Update timestamp if provided
if (updatedInfo.timestamp) {
data.engagement.timestamp = new Date(updatedInfo.timestamp).getTime();
data.metadata.startTime = new Date(updatedInfo.timestamp).getTime();
// Assuming a default 1-hour duration
data.metadata.endTime =
new Date(updatedInfo.timestamp).getTime() + 3600000;
}
// Update notes if provided
if (updatedInfo.meetingNotes) {
data.metadata.body = updatedInfo.meetingNotes;
}
// Update type if provided
if (updatedInfo.type) {
data.metadata.meetingType = updatedInfo.type;
}
// Update status if provided
if (updatedInfo.status) {
data.metadata.status = updatedInfo.status;
}
try {
// Make the API call to update the engagement
await axios.patch(url, data, this.getHeaders());
this.logger.info(`Modified appointment with ID: ${id}`);
// Fetch the updated appointment details
const updatedAppointment = await this.findAppointmentById(id);
if (!updatedAppointment) {
throw new Error("Failed to retrieve updated appointment");
}
return updatedAppointment;
}
catch (error) {
this.logger.error("Error modifying appointment:", error);
throw new Error("Failed to modify appointment");
}
}
async findAppointmentByContactName(name) {
const url = `${HUBSPOT_API_BASE_URL}/crm/v3/objects/meetings/search`; // Correct endpoint
const data = {
filterGroups: [
{
filters: [
{
propertyName: "hs_meeting_title",
operator: "CONTAINS_TOKEN",
value: name,
},
],
},
],
};
try {
// Ensure this is a POST request
const response = await axios.post(url, data, this.getHeaders());
const responseData = response.data;
return responseData.results.map((meeting) => ({
id: meeting.id,
timestamp: meeting.properties.hs_meeting_start_time,
meetingNotes: meeting.properties.hs_meeting_body || "",
type: "Meeting",
status: "Scheduled",
contactName: name,
employeeName: this.employee,
}))[0];
}
catch (error) {
this.logger.error("Error searching for appointments:", error.response?.data || error.message);
throw new Error("Failed to search for appointments by name");
}
}
async findAppointmentByTimestamp(timestamp) {
const url = `${HUBSPOT_API_BASE_URL}/engagements/v1/engagements`;
const response = await axios.get(url, this.getHeaders());
const appointment = response.data.results.find((meeting) => new Date(meeting.metadata.startTime).toISOString() === timestamp);
return {
id: appointment.engagement.id,
timestamp,
meetingNotes: appointment.metadata.body,
type: "Meeting",
status: "Scheduled",
contactName: "",
employeeName: this.employee,
};
}
async findAppointmentById(id) {
const url = `${HUBSPOT_API_BASE_URL}/engagements/v1/engagements/${id}`;
try {
const response = await axios.get(url, this.getHeaders());
const meeting = response.data;
return {
id,
timestamp: new Date(meeting.metadata.startTime).toISOString(),
meetingNotes: meeting.metadata.body,
type: "Meeting",
status: "Scheduled",
contactName: "",
employeeName: this.employee,
};
}
catch (error) {
if (error.response?.status === 404)
return null;
throw error;
}
}
async createEventToSms(appointment) {
throw new Error("Method not implemented.");
}
}
//# sourceMappingURL=HubspotBackend.js.map