From 65d082a1970fafa490c7cb7bad9c02ece5b87647 Mon Sep 17 00:00:00 2001 From: Marlene Date: Wed, 26 Mar 2025 10:35:26 +0200 Subject: [PATCH 1/5] first commit to my integrating with hubspot 1-foundations-practicum-repository --- app.js | 0 index.js | 71 -------------------------------------------- public/css/style.css | 58 ------------------------------------ views/contacts.pug | 15 ---------- 4 files changed, 144 deletions(-) create mode 100644 app.js delete mode 100644 public/css/style.css delete mode 100644 views/contacts.pug diff --git a/app.js b/app.js new file mode 100644 index 00000000..e69de29b diff --git a/index.js b/index.js index f337a32d..e69de29b 100644 --- a/index.js +++ b/index.js @@ -1,71 +0,0 @@ -const express = require('express'); -const axios = require('axios'); -const app = express(); - -app.set('view engine', 'pug'); -app.use(express.static(__dirname + '/public')); -app.use(express.urlencoded({ extended: true })); -app.use(express.json()); - -// * Please DO NOT INCLUDE the private app access token in your repo. Don't do this practicum in your normal account. -const PRIVATE_APP_ACCESS = ''; - -// TODO: ROUTE 1 - Create a new app.get route for the homepage to call your custom object data. Pass this data along to the front-end and create a new pug template in the views folder. - -// * Code for Route 1 goes here - -// TODO: ROUTE 2 - Create a new app.get route for the form to create or update new custom object data. Send this data along in the next route. - -// * Code for Route 2 goes here - -// TODO: ROUTE 3 - Create a new app.post route for the custom objects form to create or update your custom object data. Once executed, redirect the user to the homepage. - -// * Code for Route 3 goes here - -/** -* * This is sample code to give you a reference for how you should structure your calls. - -* * App.get sample -app.get('/contacts', async (req, res) => { - const contacts = 'https://api.hubspot.com/crm/v3/objects/contacts'; - const headers = { - Authorization: `Bearer ${PRIVATE_APP_ACCESS}`, - 'Content-Type': 'application/json' - } - try { - const resp = await axios.get(contacts, { headers }); - const data = resp.data.results; - res.render('contacts', { title: 'Contacts | HubSpot APIs', data }); - } catch (error) { - console.error(error); - } -}); - -* * App.post sample -app.post('/update', async (req, res) => { - const update = { - properties: { - "favorite_book": req.body.newVal - } - } - - const email = req.query.email; - const updateContact = `https://api.hubapi.com/crm/v3/objects/contacts/${email}?idProperty=email`; - const headers = { - Authorization: `Bearer ${PRIVATE_APP_ACCESS}`, - 'Content-Type': 'application/json' - }; - - try { - await axios.patch(updateContact, update, { headers } ); - res.redirect('back'); - } catch(err) { - console.error(err); - } - -}); -*/ - - -// * Localhost -app.listen(3000, () => console.log('Listening on http://localhost:3000')); \ No newline at end of file diff --git a/public/css/style.css b/public/css/style.css deleted file mode 100644 index 85587bb4..00000000 --- a/public/css/style.css +++ /dev/null @@ -1,58 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); - -body, * { - font-family: 'Roboto', sans-serif; - margin: 0; - padding: 0; -} - -h1 { - margin: 1rem; -} - -.cards { - display: flex; - align-items: center; - justify-content: space-evenly; - flex-wrap: wrap; -} - -.card { - flex-basis: 31%; - margin: 1rem 0.4rem; - padding: 1rem 0.4rem 2.5rem 0.4rem; - border: solid 1px lightgrey; - border-radius: 15px; - box-shadow: 3px 2px 6px lightgrey; -} - -.card__email { - font-size: 1rem; -} - -.form-wrapper { - font-size: 18px; - max-width: 768px; - margin: 2rem auto; - padding: 2rem; - border: solid 1px lightgrey; - border-radius: 15px; - box-shadow: 3px 2px 6px lightgrey; -} - -label, input { - margin-top: 5px; - display: block; - font-size: inherit; -} - -input[type="text"] { - padding: .25rem; -} - -input[type="submit"] { - background-color: lightgrey; - border: none; - padding: .375rem 1rem; - margin-top: 10px; -} \ No newline at end of file diff --git a/views/contacts.pug b/views/contacts.pug deleted file mode 100644 index c600ca01..00000000 --- a/views/contacts.pug +++ /dev/null @@ -1,15 +0,0 @@ -//- ** This is a sample of a pug template and how it uses the data passed to it from index.js. - -//- doctype html -//- html -//- head -//- title= `${title}` -//- meta(name="viewport" content="width=device-width, initial-scale=1") -//- link(rel="stylesheet", href="/css/style.css") -//- body -//- h1 Contacts -//- .cards -//- each contact in data -//- .card -//- h2.card__name #{contact.properties.firstname} #{contact.properties.lastname} -//- p.card__email #{contact.properties.email} \ No newline at end of file From 64d7f896f738f40c174e7eb6e59be3a17eecca36 Mon Sep 17 00:00:00 2001 From: Marlene Date: Wed, 26 Mar 2025 10:38:45 +0200 Subject: [PATCH 2/5] add server app.js and index.js --- app.js | 32 ++++++++++++++++++++++++ bin/www.js | 8 ++++++ index.js | 71 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 111 insertions(+) create mode 100644 bin/www.js diff --git a/app.js b/app.js index e69de29b..ddc315f8 100644 --- a/app.js +++ b/app.js @@ -0,0 +1,32 @@ +import express from 'express' +import path from 'path' +import {fileURLToPath} from 'node:url' + +//import of routes +import getCustomObjects from "./routes/getCustomObjects.js" +import updateCustomObjects from "./routes/updateCustomObjects.js" + +//instantiation of the express application +const app = express() + +//setup of __dirname because its not natively available ES6 +const __filename = fileURLToPath(import.meta.url) +const __dirname = path.dirname(__filename) + +//serving of static files such as the style.css file +app.use(express.static(path.join(__dirname, 'public'))) + +//setup of the view engine + +app.set("views", path.join(__dirname, 'views')) +app.set("view engine", "pug") +// Parse application/x-www-form-urlencoded form data +app.use(express.urlencoded({ extended: true })); + +// Enable json parsing +app.use(express.json()); +//registration of routes +app.use("/", getCustomObjects) +app.use("/", updateCustomObjects) + +export default app diff --git a/bin/www.js b/bin/www.js new file mode 100644 index 00000000..29048400 --- /dev/null +++ b/bin/www.js @@ -0,0 +1,8 @@ +//import of the express application instance +import app from "../app.js" + +//id of the port to listen on +const port = process.env.PORT || 3000 + +//local host to listen on +app.listen(port, () => console.log(`Listening on port ${port}`)) \ No newline at end of file diff --git a/index.js b/index.js index e69de29b..d6270c48 100644 --- a/index.js +++ b/index.js @@ -0,0 +1,71 @@ +const express = require('express'); +const axios = require('axios'); +const app = express(); + +app.set('view engine', 'pug'); +app.use(express.static(__dirname + '/public')); +app.use(express.urlencoded({ extended: true })); +app.use(express.json()); + +// * Please DO NOT INCLUDE the private app access token in your repo. Don't do this practicum in your normal account. +const PRIVATE_APP_ACCESS = ''; + +// TODO: ROUTE 1 - Create a new app.get route for the homepage to call your custom object data. Pass this data along to the front-end and create a new pug template in the views folder. + +// * Code for Route 1 goes here + +// TODO: ROUTE 2 - Create a new app.get route for the form to create or update new custom object data. Send this data along in the next route. + +// * Code for Route 2 goes here + +// TODO: ROUTE 3 - Create a new app.post route for the custom objects form to create or update your custom object data. Once executed, redirect the user to the homepage. + +// * Code for Route 3 goes here + +/** + * * This is sample code to give you a reference for how you should structure your calls. + + * * App.get sample + app.get('/contacts', async (req, res) => { + const contacts = 'https://api.hubspot.com/crm/v3/objects/contacts'; + const headers = { + Authorization: `Bearer ${PRIVATE_APP_ACCESS}`, + 'Content-Type': 'application/json' + } + try { + const resp = await axios.get(contacts, { headers }); + const data = resp.data.results; + res.render('contacts', { title: 'Contacts | HubSpot APIs', data }); + } catch (error) { + console.error(error); + } + }); + + * * App.post sample + app.post('/update', async (req, res) => { + const update = { + properties: { + "favorite_book": req.body.newVal + } + } + + const email = req.query.email; + const updateContact = `https://api.hubapi.com/crm/v3/objects/contacts/${email}?idProperty=email`; + const headers = { + Authorization: `Bearer ${PRIVATE_APP_ACCESS}`, + 'Content-Type': 'application/json' + }; + + try { + await axios.patch(updateContact, update, { headers } ); + res.redirect('back'); + } catch(err) { + console.error(err); + } + + }); + */ + + +// * Localhost +app.listen(3000, () => console.log('Listening on http://localhost:3000')); \ No newline at end of file From d88a07ad22b9b06220860b2846f04e9afe965830 Mon Sep 17 00:00:00 2001 From: Marlene Date: Wed, 26 Mar 2025 10:40:42 +0200 Subject: [PATCH 3/5] add routes and controllers --- controllers/getCustomObjects.js | 38 ++++++++++++++++++++ controllers/updateCustomObjects.js | 56 ++++++++++++++++++++++++++++++ routes/getCustomObjects.js | 12 +++++++ routes/updateCustomObjects.js | 15 ++++++++ 4 files changed, 121 insertions(+) create mode 100644 controllers/getCustomObjects.js create mode 100644 controllers/updateCustomObjects.js create mode 100644 routes/getCustomObjects.js create mode 100644 routes/updateCustomObjects.js diff --git a/controllers/getCustomObjects.js b/controllers/getCustomObjects.js new file mode 100644 index 00000000..1fdff522 --- /dev/null +++ b/controllers/getCustomObjects.js @@ -0,0 +1,38 @@ +import axios from "axios"; + +/** + * Controller function to get existing records of a custom object from the HubSpot API and render + * to data via a Pug template to the user + * @param req + * @param res + * @returns {Promise} + */ +export const getCustomObjects = async (req, res) => { + + // Setup of axios request + const objectTypeId = 'p145883771_plants'; + const properties = 'plant_name,species,plant_size'; + const getCustomObjectEndpoint = `https://api.hubapi.com/crm/v3/objects/${objectTypeId}?properties=${properties}`; + const axiosConfig = { + method: 'get', + url: getCustomObjectEndpoint, + headers: { + Authorization: `Bearer ${process.env.PRIVATE_APP_TOKEN}`, + 'Content-Type': 'application/json', + } + } + // Axios get request to the specified API to get and render the information in a Pug template. + // Use of a try/catch block in case the axios call fails + try { + + const response = await axios(axiosConfig); + const data = response.data.results; + res.render('customObjects', { title: 'Custom Object | Plants', data }); + + } catch (error) { + + console.error(error); + + } + +} \ No newline at end of file diff --git a/controllers/updateCustomObjects.js b/controllers/updateCustomObjects.js new file mode 100644 index 00000000..14abfc32 --- /dev/null +++ b/controllers/updateCustomObjects.js @@ -0,0 +1,56 @@ +import axios from "axios"; + +/** + * Controller function to render a custom form when a get call is made to route '/update-cobj' + * @param req + * @param res + * @returns {Promise} + */ +export const getFormData = async (req, res) => { + res.render('updates', {title: 'Update Custom Object Form | Integrating With HubSpot I Practicum'}); +} + + /** + * Controller to make a post request to the HubSpot API to add new records to a custom object + * @param req + * @param res + * @returns {Promise} + */ + export const updateCustomObject = async (req, res) => { + + // The submitted details from the custom form in the updates.pug file received via the req.body as a POST method form + const {plant_name, plant_size, species} = req.body + +// Setup of the axios request + const objectTypeId = 'p145883771_plants' + const updateCustomObjectsEndpoint = `https://api.hubapi.com/crm/v3/objects/${objectTypeId}`; + const newData = { + properties: { + "plant_name": plant_name, + "plant_size": plant_size, + "species": species, + } + } + const axiosConfig = { + method: 'post', + data: newData, + url: updateCustomObjectsEndpoint, + headers: { + Authorization: `Bearer ${process.env.PRIVATE_APP_TOKEN}`, + 'Content-Type': 'application/json', + } + } + // The try/catch block for the axios request in case it fails and a redirect back to the home page if successful + try { + + await axios(axiosConfig); + res.redirect('/'); + + } catch (error) { + + console.error(error); + + } + + +} \ No newline at end of file diff --git a/routes/getCustomObjects.js b/routes/getCustomObjects.js new file mode 100644 index 00000000..83d36b2b --- /dev/null +++ b/routes/getCustomObjects.js @@ -0,0 +1,12 @@ +import express from 'express' + +//import of the controller function for this route +import {getCustomObjects} from "../controllers/getCustomObjects.js" + +//instantiation of the router object +const router = express.Router() + +//get route to obtain custom object data from the HubSpot account +router.get('/', getCustomObjects) + +export default router \ No newline at end of file diff --git a/routes/updateCustomObjects.js b/routes/updateCustomObjects.js new file mode 100644 index 00000000..8719273f --- /dev/null +++ b/routes/updateCustomObjects.js @@ -0,0 +1,15 @@ +import express from 'express' + +//import of controller functions for the two routes +import {getFormData, updateCustomObject} from "../controllers/updateCustomObjects.js" + +//instantiation of the router object +const router = express.Router() + +//the get route to get and display the custom form to add records to the custom object +router.get("/update-cobj", getFormData) + +//the post route to make use of the information submitted through the form and add a record to the custom object via the appropriate hubspot api endpoint +router.post("/update-cobj", updateCustomObject) + +export default router \ No newline at end of file From 3d8886f438090a1e69b82bf5cd84b03b1c3f1d2b Mon Sep 17 00:00:00 2001 From: Marlene Date: Wed, 26 Mar 2025 10:42:29 +0200 Subject: [PATCH 4/5] add views and styling of views --- public/css/style.css | 72 +++++++++++++++++++++++++++++++++++++++++ views/customObjects.pug | 27 ++++++++++++++++ views/updates.pug | 28 ++++++++++++++++ 3 files changed, 127 insertions(+) create mode 100644 public/css/style.css create mode 100644 views/customObjects.pug create mode 100644 views/updates.pug diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 00000000..44873bca --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,72 @@ +@import url('https://fonts.googleapis.com/css2?family=Roboto&display=swap'); + +body, * { + font-family: 'Roboto', sans-serif; + margin: 0; + padding: 0; +} + +h1 { + margin: 1rem; +} + +.cards { + display: flex; + align-items: center; + justify-content: space-evenly; + flex-wrap: wrap; +} + +.card { + flex-basis: 31%; + margin: 1rem 0.4rem; + padding: 1rem 0.4rem 2.5rem 0.4rem; + border: solid 1px lightgrey; + border-radius: 15px; + box-shadow: 3px 2px 6px lightgrey; +} + +.card__email { + font-size: 1rem; +} + +.form-wrapper { + font-size: 18px; + max-width: 768px; + margin: 2rem auto; + padding: 2rem; + border: solid 1px lightgrey; + border-radius: 15px; + box-shadow: 3px 2px 6px lightgrey; +} + +label, input { + margin-top: 5px; + display: block; + font-size: inherit; +} + +input[type="text"] { + padding: .25rem; +} + +input[type="submit"] { + background-color: lightgrey; + border: none; + padding: .375rem 1rem; + margin-top: 10px; +} + +.custom-plants{ + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 100vh; + background-color: green; +} + +.custom-plants__table__heading, +.custom-plants__table__cell{ + padding: 10px 40px; +} \ No newline at end of file diff --git a/views/customObjects.pug b/views/customObjects.pug new file mode 100644 index 00000000..5ed195df --- /dev/null +++ b/views/customObjects.pug @@ -0,0 +1,27 @@ +doctype html +html + head + // Interpolation of title + title= `${title}` + meta(name="viewport" content="width=device-width, initial-scale=1") + link(rel="stylesheet", href="/css/style.css") + body + .custom-plants + h1.custom-plants__heading Custom Object + h2.custom-plants__subheading Plants + // Link to the /object-cobj get route + a.custom-plants__update-link(href='./update-cobj') Add to this table + // Setup of the table needed to render the data + table.custom-plants__table + tr.custom-plants__table__header-row + th.custom-plants__table__heading Name + th.custom-plants__table__heading Species + th.custom-plants__table__heading Size + // Loop through the data received from the get route that contains individual records of the + // custom object + each customPlants in data + tr.custom-plants__table__row + // Interpolation of the specific data points into a table + td.custom-plants__table__cell #{customPlants.properties.plant_name} + td.custom-plants__table__cell #{customPlants.properties.species} + td.custom-plants__table__cell #{customPlants.properties.plant_size} diff --git a/views/updates.pug b/views/updates.pug new file mode 100644 index 00000000..c0be23c1 --- /dev/null +++ b/views/updates.pug @@ -0,0 +1,28 @@ +doctype html +html + head + // Interpolation of the title received from the get route associated with this template + title= `${title}` + meta(name="viewport" content="width=device-width, initial-scale=1") + link(rel="stylesheet", href="/css/style.css") + body + .form-wrapper + // Interpolation of the title + h1.updates__heading #{title} + // Link back to the homepage + a.updates__homepage-link(href="/") Homepage + // Setup of the form with a POST method for submitting data to the HubSpot API for addition to the + // custom object + form.updates__form(action="/update-cobj" method="POST") + .updates__form-control + label(for="plant-name") Plant Name + input(type="text" name="plant_name" id="plant-name") + .updates__form-control + label(for="plant-size") Plant Size + input(type="text" name="plant_size" id="plant-size") + .updates__form-control + label(for="plant-species") Plant Species + input(type="text" name="species" id="plant-species") + // Submit button to trigger submission of data to the /update-cobj post route + .updates__form-control-btn + input(type="submit" value="Submit") \ No newline at end of file From 8d01b6769b3e6e340a14eca302c86605097137ca Mon Sep 17 00:00:00 2001 From: Marlene Date: Wed, 26 Mar 2025 10:52:24 +0200 Subject: [PATCH 5/5] add views and styling of views --- package.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 62db37aa..51ab5fdf 100644 --- a/package.json +++ b/package.json @@ -4,13 +4,16 @@ "description": "", "main": "index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "nodemon -r dotenv/config bin/www.js" }, "author": "HubSpot Academy learner", "license": "ISC", "dependencies": { "axios": "^1.3.5", + "dotenv": "^16.4.7", "express": "^4.18.2", + "nodemon": "^3.1.9", "pug": "^3.0.2" } }