initial commit
This commit is contained in:
commit
a60c8ee04d
7 files changed
+2181
No files matched your search
@@ -0,0 +1,3 @@
|
|||||||
|
.env
|
||||||
|
node_modules
|
||||||
|
cfg
|
||||||
Generated
+1392
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "hubspot-project",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"start": "node src/hubspotConnector.js",
|
||||||
|
"dev": "nodemon src/hubspotConnector.js",
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"type": "commonjs",
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.19.0",
|
||||||
|
"dotenv": "^17.4.2",
|
||||||
|
"express": "^5.2.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"nodemon": "^3.1.14"
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
require('dotenv').config({ path: path.resolve(__dirname, '../.env') });
|
||||||
|
|
||||||
|
// this function retrieve the users' data from Keystone
|
||||||
|
async function getLeads() {
|
||||||
|
try {
|
||||||
|
const response = await fetch('https://smarthub.keystoneacademic.com/api/rest/getLead', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'x-api-key': process.env.KEYSTONE_API_KEY,
|
||||||
|
'Accept': 'application/json',
|
||||||
|
'Content-type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
unsent: 1, // If this variable is set to 0, then it will show leads that have been marked as seen (old leads)
|
||||||
|
test: 1, // If this is removed or set to 0, then all leads that are returned would be marked as seen. This allows you to only fetch new leads
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (!Array.isArray(result?.data)) {
|
||||||
|
console.log('API response empty or invalid: ', result);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const lead of result.data) {
|
||||||
|
console.log('ID: ', lead.id);
|
||||||
|
console.log('First Name: ', lead.firstname);
|
||||||
|
console.log('Last Name: ', lead.lastname);
|
||||||
|
console.log('Email: ', lead.contact?.email ?? 'N/D');
|
||||||
|
console.log('Phone: ', lead.contact?.phone ?? 'N/D');
|
||||||
|
console.log('Program: ', lead.program?.name ?? 'N/D');
|
||||||
|
console.log('--------------------------');
|
||||||
|
}
|
||||||
|
return result.data || [];
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log("Error fetching the data: ", error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { getLeads };
|
||||||
+618
@@ -0,0 +1,618 @@
|
|||||||
|
var fs = require('fs');
|
||||||
|
var axios = require('axios');
|
||||||
|
var express = require('express');
|
||||||
|
const auth = require('../cfg/auth.js');
|
||||||
|
const FILE_PATH = require('./last-modified-date.json');
|
||||||
|
|
||||||
|
const app = express();
|
||||||
|
const port = 3000;
|
||||||
|
app.use(express.json());
|
||||||
|
|
||||||
|
var server = app.listen(3000, () => { console.log('Please your hubspot instance and next click on http://localhost:3000/install or http://localhost:3000/renew') })
|
||||||
|
|
||||||
|
// used to initiate the authorization flow to connect the app with Hubspot
|
||||||
|
app.get('/install', async (req, res) => {
|
||||||
|
// Build the auth URL
|
||||||
|
const authUrl =
|
||||||
|
'https://app.hubspot.com/oauth/authorize' +
|
||||||
|
`?client_id=${encodeURIComponent(auth.hClientId)}` +
|
||||||
|
`&scope=${encodeURIComponent(auth.hScopes.join(' '))}` +
|
||||||
|
`&redirect_uri=${encodeURIComponent(auth.hCallback)}` +
|
||||||
|
`&state=${encodeURIComponent('happy')}`
|
||||||
|
|
||||||
|
// Redirect the user
|
||||||
|
return res.redirect(authUrl);
|
||||||
|
});
|
||||||
|
|
||||||
|
app.get('/renew', async (req, res) => {
|
||||||
|
const refreshTokenProof = {
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
client_id: auth.hClientId,
|
||||||
|
client_secret: auth.hClientSecret,
|
||||||
|
redirect_uri: auth.hCallback,
|
||||||
|
refresh_token: auth.refresh_token // TODO: da capire
|
||||||
|
};
|
||||||
|
|
||||||
|
var token=await exchangeForTokens(refreshTokenProof);
|
||||||
|
if (token.message) {
|
||||||
|
return res.send(`error: ${token.message}`);
|
||||||
|
}
|
||||||
|
token.issued_at=Date.now();
|
||||||
|
fs.writeFileSync( configPath, JSON.stringify(token));
|
||||||
|
|
||||||
|
res.send(`ok`);
|
||||||
|
server.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// converts the temporary authorization code into working API credentials
|
||||||
|
app.get('/oauth-callback', async (req, res) => {
|
||||||
|
console.log(res);
|
||||||
|
|
||||||
|
if (req.query.code) {
|
||||||
|
console.log(' > Received an authorization token');
|
||||||
|
console.log(req.query);
|
||||||
|
|
||||||
|
const authCodeProof = {
|
||||||
|
grant_type: 'authorization_code',
|
||||||
|
client_id: auth.hClientId,
|
||||||
|
client_secret: auth.hClientSecret,
|
||||||
|
redirect_uri: auth.hCallback,
|
||||||
|
code: req.query.code
|
||||||
|
};
|
||||||
|
|
||||||
|
// Step 4
|
||||||
|
// Exchange the authorization code for an access token and refresh token
|
||||||
|
console.log('===> Step 4: Exchanging authorization code for an access token and refresh token');
|
||||||
|
const token = await exchangeForTokens( authCodeProof);
|
||||||
|
if (token.message) {
|
||||||
|
return res.redirect(`/error?msg=${token.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
token.issued_at=Date.now();
|
||||||
|
fs.writeFileSync( FILE_PATH, JSON.stringify({ lastRun: token }, null, 2));
|
||||||
|
|
||||||
|
console.log(token);
|
||||||
|
res.send(`ok`);
|
||||||
|
server.close();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
async function exchangeForTokens(exchangeProof) {
|
||||||
|
try {
|
||||||
|
var body=`grant_type=${exchangeProof.grant_type}&`;
|
||||||
|
body+=`client_id=${exchangeProof.client_id}&`;
|
||||||
|
body+=`client_secret=${exchangeProof.client_secret}&`;
|
||||||
|
body+=`redirect_uri=${exchangeProof.redirect_uri}&`;
|
||||||
|
if (exchangeProof.code) body+=`code=${exchangeProof.code}`;
|
||||||
|
if (exchangeProof.refresh_token) body+=`refresh_token=${exchangeProof.refresh_token}`;
|
||||||
|
|
||||||
|
console.log(body);
|
||||||
|
var responseBody = await axios.post('https://api.hubapi.com/oauth/v1/token', body,
|
||||||
|
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded'} });
|
||||||
|
//console.log(responseBody);
|
||||||
|
const tokens = responseBody.data;
|
||||||
|
|
||||||
|
console.log(' > Received an access token and refresh token');
|
||||||
|
console.log(tokens);
|
||||||
|
|
||||||
|
return tokens;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(` > Error exchanging ${exchangeProof.grant_type} for access token`);
|
||||||
|
console.log(e.response.data);
|
||||||
|
return e.response.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var hubspot = {
|
||||||
|
baseurl: 'https://api.hubapi.com',
|
||||||
|
credentials: {},
|
||||||
|
tokenFile: '',
|
||||||
|
cachedToken: null,
|
||||||
|
tokenExpiry: null,
|
||||||
|
|
||||||
|
logresult: function (resp) {
|
||||||
|
console.log('internal--->');
|
||||||
|
console.log(JSON.stringify(resp, null, 2));
|
||||||
|
},
|
||||||
|
|
||||||
|
setTokenFile: function (filename) {
|
||||||
|
this.tokenFile = filename;
|
||||||
|
},
|
||||||
|
|
||||||
|
setClientId: function (clientId) {
|
||||||
|
this.credentials.clientId = clientId;
|
||||||
|
},
|
||||||
|
|
||||||
|
setClientSecret: function (clientSecret) {
|
||||||
|
this.credentials.clientSecret = clientSecret;
|
||||||
|
},
|
||||||
|
|
||||||
|
setRedirectUri: function (redirectUri) {
|
||||||
|
this.credentials.redirectUri = redirectUri;
|
||||||
|
},
|
||||||
|
|
||||||
|
setRefreshToken: function (refreshToken) {
|
||||||
|
this.credentials.refreshToken = refreshToken;
|
||||||
|
},
|
||||||
|
|
||||||
|
loadConfig: function (configPath) {
|
||||||
|
try {
|
||||||
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
||||||
|
this.credentials.clientId = config.CLIENT_ID;
|
||||||
|
this.credentials.clientSecret = config.CLIENT_SECRET;
|
||||||
|
this.credentials.redirectUri = config.REDIRECT_URI;
|
||||||
|
this.credentials.refreshToken = config.REFRESH_TOKEN;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error loading config:', e.message);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
getAccessToken: async function () {
|
||||||
|
console.log('getAccessToken');
|
||||||
|
const body = new URLSearchParams({
|
||||||
|
grant_type: 'refresh_token',
|
||||||
|
client_id: this.credentials.clientId,
|
||||||
|
client_secret: this.credentials.clientSecret,
|
||||||
|
refresh_token: this.credentials.refreshToken
|
||||||
|
}).toString();
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`${this.baseurl}/oauth/v1/token`, body, {
|
||||||
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.log('getAccessToken error:', error.response?.data);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
checkToken: async function () {
|
||||||
|
if (!this.isTokenValid()) {
|
||||||
|
try {
|
||||||
|
const tokenData = await this.getAccessToken();
|
||||||
|
if (this.tokenFile) {
|
||||||
|
fs.writeFileSync(this.tokenFile, JSON.stringify(tokenData, null, 2));
|
||||||
|
}
|
||||||
|
this.cachedToken = tokenData.access_token;
|
||||||
|
this.tokenExpiry = tokenData.expires_in + Date.now();
|
||||||
|
console.log('New access token generated');
|
||||||
|
return tokenData;
|
||||||
|
} catch (err) {
|
||||||
|
console.log('checkToken: error on getting a new token!');
|
||||||
|
console.log('err', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return { access_token: this.cachedToken };
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getTokenFromFile: async function () {
|
||||||
|
let tokenData=JSON.parse( fs.readFileSync(this.tokenFile) );
|
||||||
|
|
||||||
|
this.credentials.refreshToken = tokenData.refresh_token;
|
||||||
|
this.cachedToken = tokenData.access_token;
|
||||||
|
this.tokenExpiry = tokenData.expires_in;
|
||||||
|
},
|
||||||
|
|
||||||
|
isTokenValid: function () {
|
||||||
|
return this.cachedToken && this.tokenExpiry > Date.now();
|
||||||
|
},
|
||||||
|
|
||||||
|
createContact: async function (contactData) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`${this.baseurl}/crm/v3/objects/contacts`, contactData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error creating contact:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateContact: async function (contactId, contactData) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.patch(`${this.baseurl}/crm/v3/objects/contacts/${contactId}`, contactData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error updating contact:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getContact: async function (contactId) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${this.baseurl}/crm/v3/objects/contacts/${contactId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching contact:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteContact: async function (contactId) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(`${this.baseurl}/crm/v3/objects/contacts/${contactId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error deleting contact:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createCompany: async function (companyData) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`${this.baseurl}/crm/v3/objects/companies`, companyData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error creating company:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateCompany: async function (companyId, companyData) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.patch(`${this.baseurl}/crm/v3/objects/companies/${companyId}`, companyData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error updating company:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getCompany: async function (companyId) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${this.baseurl}/crm/v3/objects/companies/${companyId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching company:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
associateContactWithCompany: async function (contactId, companyId) {
|
||||||
|
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.put(
|
||||||
|
`${this.baseurl}/crm/v3/associations/companies/${companyId}/contacts/${contactId}`,
|
||||||
|
{},
|
||||||
|
{ headers: { Authorization: `Bearer ${this.cachedToken}` } }
|
||||||
|
);
|
||||||
|
console.log('Contact associated with company:', response.data);
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error associating contact with company:', e.response?.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
deleteCompany: async function (companyId) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(`${this.baseurl}/crm/v3/objects/companies/${companyId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error deleting company:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
createDeal: async function (dealData) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.post(`${this.baseurl}/crm/v3/objects/deals`, dealData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error creating deal:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
updateDeal: async function (dealId, dealData) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.patch(`${this.baseurl}/crm/v3/objects/deals/${dealId}`, dealData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error updating deal:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteDeal: async function (dealId) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.delete(`${this.baseurl}/crm/v3/objects/deals/${dealId}`, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error deleting deal:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getObjectProperties: async function (object) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
const response = await axios.get(`${this.baseurl}/crm/v3/properties/${object}/?archived=false`, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
this.logresult(response.data);
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching properties:', e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getFile: async function (fileId) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
|
||||||
|
const response = await axios.get(`${this.baseurl}/files/v3/files/${fileId}/signed-url`, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching file:', e.response?.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
},
|
||||||
|
getCRMRecord: async function (object, recordId, properties, associations, after, limit) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/objects/${object}`;
|
||||||
|
if (recordId) url+=`/${recordId}`;
|
||||||
|
|
||||||
|
var other=[]
|
||||||
|
if (properties) {
|
||||||
|
let props=properties.join(',');
|
||||||
|
other.push('properties='+props);
|
||||||
|
}
|
||||||
|
// console.log(url);
|
||||||
|
if (associations) {
|
||||||
|
let assoc=associations.join(',');
|
||||||
|
other.push('associations='+assoc);
|
||||||
|
}
|
||||||
|
if (limit) other.push(`limit=${limit}`);
|
||||||
|
if (after) other.push(`after=${after}`);
|
||||||
|
|
||||||
|
url+='?'+other.join('&');
|
||||||
|
console.log("getCRMRecord",url);
|
||||||
|
|
||||||
|
|
||||||
|
const response = await axios.get(url, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching file:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createCRMRecord: async function (object, recordData) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/objects/${object}`;
|
||||||
|
console.log("createCRMRecord",url);
|
||||||
|
|
||||||
|
const response = await axios.post(url, recordData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching file:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
updateCRMRecord: async function (object, recordId, recordData) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/objects/${object}/${recordId}`;
|
||||||
|
console.log("updateCRMRecord",url);
|
||||||
|
|
||||||
|
const response = await axios.patch(url, recordData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching file:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
deleteCRMRecord: async function (object, recordId) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/objects/${object}/${recordId}`;
|
||||||
|
console.log("deleteCRMRecord",url);
|
||||||
|
|
||||||
|
const response = await axios.delete(url, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error fetching file:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getBatchCRMRecord: async function (object, recordIds, properties) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/objects/${object}/batch/read`;
|
||||||
|
let inputs=[]
|
||||||
|
if (recordIds) {
|
||||||
|
recordIds.forEach( id => {
|
||||||
|
inputs.push( { "id":id } );
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let body={
|
||||||
|
properties: properties,
|
||||||
|
inputs: inputs
|
||||||
|
}
|
||||||
|
// console.log(JSON.stringify(body, null,2));
|
||||||
|
|
||||||
|
const response = await axios.post(url, body, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error batch reading:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createBatchCRMRecord: async function (object, inputs) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/objects/${object}/batch/create`;
|
||||||
|
let body={
|
||||||
|
inputs: inputs
|
||||||
|
}
|
||||||
|
// console.log(JSON.stringify(body, null,2));
|
||||||
|
|
||||||
|
const response = await axios.post(url, body, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error batch reading:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createBatchCRMAssociations: async function (fromObjectType, toObjectType, inputs) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/associations/${fromObjectType}/${toObjectType}/batch/create`;
|
||||||
|
let body={
|
||||||
|
inputs: inputs
|
||||||
|
}
|
||||||
|
console.log("url",url);
|
||||||
|
|
||||||
|
// console.log(JSON.stringify(body, null,2));
|
||||||
|
|
||||||
|
const response = await axios.post(url, body, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error batch reading:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
upsertBatchCRMRecord: async function (object, inputs) {
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/objects/${object}/batch/upsert`;
|
||||||
|
let body={
|
||||||
|
inputs: inputs
|
||||||
|
}
|
||||||
|
// console.log(JSON.stringify(body, null,2));
|
||||||
|
|
||||||
|
const response = await axios.post(url, body, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error batch reading:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
createCustomEvent: async function (timestamp, eventTemplateId, objectId, tokens) {
|
||||||
|
// Timestamps can be in milliseconds epoch time or in ISO 8601 format.
|
||||||
|
// other possible associations:
|
||||||
|
// "email": "a.test.contact@email.com",
|
||||||
|
// "utk": "89b5afb740d41f4cd6651ac5237edf09"
|
||||||
|
await this.checkToken();
|
||||||
|
try {
|
||||||
|
let url=`${this.baseurl}/crm/v3/timeline/events`;
|
||||||
|
let body={
|
||||||
|
timestamp: timestamp,
|
||||||
|
eventTemplateId: eventTemplateId,
|
||||||
|
objectId: objectId,
|
||||||
|
tokens: tokens,
|
||||||
|
}
|
||||||
|
const response = await axios.post(url, body, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error batch reading:', e.response?.data);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
search: async function (objectType, filters = [], properties = [], pagination = { limit: 10, after: null }, query = '') {
|
||||||
|
await this.checkToken();
|
||||||
|
|
||||||
|
const url = `${this.baseurl}/crm/v3/objects/${objectType}/search`;
|
||||||
|
const { limit, after } = pagination;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestData = {
|
||||||
|
properties: properties,
|
||||||
|
limit: limit,
|
||||||
|
sorts: [ { "propertyName": "createdate", "direction": "DESCENDING" } ]
|
||||||
|
};
|
||||||
|
if (filters) requestData.filterGroups=[{ filters }];
|
||||||
|
|
||||||
|
// Include the 'after' parameter if it's provided (for paging through results)
|
||||||
|
if (after) {
|
||||||
|
requestData.after = after;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If a query is provided, include it in the request body
|
||||||
|
if (query) {
|
||||||
|
requestData.query = query;
|
||||||
|
}
|
||||||
|
console.log("body", objectType, requestData);
|
||||||
|
|
||||||
|
const response = await axios.post(url, requestData, {
|
||||||
|
headers: { Authorization: `Bearer ${this.cachedToken}` }
|
||||||
|
});
|
||||||
|
|
||||||
|
this.logresult(response.data);
|
||||||
|
|
||||||
|
// Return the response along with the paging data (if any)
|
||||||
|
return {
|
||||||
|
total: response.data.total,
|
||||||
|
results: response.data.results,
|
||||||
|
paging: response.data.paging || null // `paging.next.after` can be used for the next request
|
||||||
|
};
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error searching ${objectType}:`, e.response?.data);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = hubspot;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
const fs = require('fs');
|
||||||
|
const { getLeads } = require('./fetchLeads.js');
|
||||||
|
const FILE_PATH = './last-modified-date.json';
|
||||||
|
const auth = require('../cfg/auth.js');
|
||||||
|
const hubspotApi = require('./hublib.js');
|
||||||
|
|
||||||
|
// this initializes the Hubspot connection
|
||||||
|
async function initializeHubspotConnection() {
|
||||||
|
hubspotApi.setTokenFile(auth.hToken);
|
||||||
|
hubspotApi.getTokenFromFile();
|
||||||
|
|
||||||
|
hubspotApi.setClientId(auth.hClientId);
|
||||||
|
hubspotApi.setClientSecret(auth.hClientSecret);
|
||||||
|
hubspotApi.setRedirectUri(auth.hCallback);
|
||||||
|
|
||||||
|
if (!await hubspotApi.getAccessToken()) {
|
||||||
|
console.log('No token found.')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('Token loaded successfully');
|
||||||
|
}
|
||||||
|
|
||||||
|
await hubspotApi.checkToken();
|
||||||
|
console.log('Hubspot connection is ready!');
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the function where we create the Contacts in Hubspot
|
||||||
|
async function createHubspotContacts(leadContact) {
|
||||||
|
console.log('Connection successful!');
|
||||||
|
|
||||||
|
/*const payload = {
|
||||||
|
properties: {
|
||||||
|
firstname: leadContact.firstname,
|
||||||
|
lastname: leadContact.lastname,
|
||||||
|
email: leadContact.contact.email,
|
||||||
|
phone: leadContact.contact.phone,
|
||||||
|
lead_source: 'Keystone'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
const creationContact = await hubspotApi.createCRMRecord('contacts', payload);
|
||||||
|
console.log(creationContact);
|
||||||
|
return creationContact;
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log(`Error in creating the contact with id ${ leadContact.id }`);
|
||||||
|
}*/
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is the function that runs the connector
|
||||||
|
async function runConnector() {
|
||||||
|
try {
|
||||||
|
console.log('Retrieving data from Keystone...');
|
||||||
|
const leads = await getLeads();
|
||||||
|
|
||||||
|
if (!leads.length) {
|
||||||
|
console.log('No data retrieved.');
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Data retrieved successfully! Connecting to Hubspot...');
|
||||||
|
// connection to Hubspot
|
||||||
|
await initializeHubspotConnection();
|
||||||
|
/*for (const lead of leads) {
|
||||||
|
try {
|
||||||
|
const hubspotContact = await createHubspotContacts(lead);
|
||||||
|
if (hubspotContact) {
|
||||||
|
console.log(`Contact '${ hubspotContact.firstname } ${ hubspotContact.lastname }' with ID '${ hubspotContact.id }' craeted successfully!`);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log(`Contact with ID '${ lead.id }' not created.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log('Something went wrong: ', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('All contacts have been created!');*/
|
||||||
|
|
||||||
|
// here we track the last time we ran the connector by passing it to a file json
|
||||||
|
if (fs.existsSync(FILE_PATH)) {
|
||||||
|
let lastRunTime = '';
|
||||||
|
const file = fs.readFileSync(FILE_PATH);
|
||||||
|
lastRunTime = JSON.parse(file).lastRun; // we will use this to simplify the management of the accounts
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
console.log('File not found.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const currentTimeStamp = new Date().toISOString();
|
||||||
|
fs.writeFileSync(FILE_PATH, JSON.stringify({ lastRun: currentTimeStamp }, null, 2));
|
||||||
|
console.log('DateTime saved in file last-modified-date.json');
|
||||||
|
}
|
||||||
|
catch (error) {
|
||||||
|
console.log('Error in processing data: ', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"lastRun": "2026-08-10T10:00:00.000Z"
|
||||||
|
}
|
||||||
Reference in new issue
Block a user