initial commit

This commit is contained in:
Denise_Datinguinoo committed 2026-08-19 18:23:51 +02:00
commit a60c8ee04d
7 files changed
+2181

No files matched your search

+44
View File
@@ -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
View File
@@ -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;
+98
View File
@@ -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);
}
}
+3
View File
@@ -0,0 +1,3 @@
{
"lastRun": "2026-08-10T10:00:00.000Z"
}