619 lines
21 KiB
JavaScript
619 lines
21 KiB
JavaScript
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;
|