diff --git a/app.js b/app.js new file mode 100644 index 00000000..ddc315f8 --- /dev/null +++ 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/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/index.js b/index.js index f337a32d..d6270c48 100644 --- a/index.js +++ b/index.js @@ -22,49 +22,49 @@ const PRIVATE_APP_ACCESS = ''; // * 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); - } - -}); -*/ +/** + * * 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 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" } } diff --git a/public/css/style.css b/public/css/style.css index 85587bb4..44873bca 100644 --- a/public/css/style.css +++ b/public/css/style.css @@ -55,4 +55,18 @@ input[type="submit"] { 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/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 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 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