Jonas – Node.js part 2


Section 10 – Auth

custom validator (on model) only works on SAVE, so to update, we need to save again, not findOneandUpdate like tours

password encryption: npm i bcryptjs

JSONWebToken, https://github.com/auth0/node-jsonwebtoken
Decode token, https://jwt.io
npm i jsonwebtoken

default to not display password when output, by on model, setting select: false
if needs to output password, use select(‘+password’)



defining extra custom function in Model, can be named anything



only users can access tour info -> protect tour routes



jwt.verify can receive a callback function as the last argument to run once verification is done. But to be consistent with our style, using async await so far, we can use promisify from require(‘util’) to make jwt.verify return a promise

the verification here works because if there’s any error (due to unsuccessful login), it’ll be caught with catchAsync.





advanced postman
manually set environment variables, use by brackets {{var}}
automatically set env variables in Tests
retrieve in Authorization tab


Delete tour authorization:
– add middlewares to delete route
– add role to model, only admin allowed to modify route
– implement restrictTo in authController, receives multiple roles that are allowed to do certain actions with {…roles}, check if that array roels.includes(req.user.role) (created from protect)



Reset password:
– the user sends a post request with email to a reset route, create the route, find the user, generate token with require(‘crypto’), need to save the token to User model (may add option validateBeforeSave to false, to avoid validation when only change a few fields)
– send email with a token (not jwt) to the user, npm i nodemailer. May use gmail as service, pro tool: sendgrid or mailgun. Mailtrap is a staging & dev testing service. In case this fails, instead of simply response with an error, more need to be done to reverse the token trace, here it simply means to remove token data from database.
– the user sends back a new password along with the token, find user, set new password, update changedPass property, log user in.



Update password: dont use findOneandUpdate, because the validator only works for Create() and Save().. !?
– run protect middleware first, ensure authorization and have user info (token, id, etc.) already ready
– find user, check current password, update new password, login



UpdateMe user info: use findByIdandUpdate here perhaps because we want to take advantage of the validators (for email and other inputs) !?
– protect middleware first, to authorize and get user info ready
– check if trying to update password and deny access, filter for updatable fields only, use findByIdandUpdate

Delete User
– add active field to model
– findByIdandUpdate active to false
– modify user filter to exclude active:false

Refactor response code & Save cookie
– res.cookie()
– secure = https encrupted conn only, httpOnly = browser cannot modify, only store and send


Rate limiting: prevent too many requests
– npm i express-rate-limit
– const rateLimit = require(‘express-rate-limit’);
– config:



Set secured HTTP headers:
– npm i helmet
– require and simply call app.use(helmet()), put it first in all middlewares

Code injection attack, require and use simply like helmet above
– npm i xss-clean, clean html with javascript injection
– npm express-mongo-sanitize, simply remove all $ and . in the req.body,

Params polution, e.g having 2 sorts in params, to only use the last one. Can pass a whitelist object of params that can be duplicated hpp({whitelist: ‘duration’})
Simple use like above
– npm i hpp




Section 11 – Data Modeling

Add geospatial data
– Add locations schema to Tour model
– Delete, import tour data

Embedded sample for guides:
– Add guide to schema in Tour model
– refer to User Id in req.body
– in pre save(), Find detail of user from User model, to return inside guides object, trick to get results from promises: await Promise.all(promises)

Child referencing:
– simply add this field to the model:



Populating data to reference, 2 ways
– 1. simply add .populate(‘field name’) to the end of the query
– 2. in query middleware, this refers to the query itself

Parent ref & Virtual populate:
– add schema in parent tour Model
– virtual middleware with foreignField = localField
– add .populate() like normal

Nested routes, refactor & merge params, so that review routes are only handled in reviewRoute, not tourRoute, merge params help to access params in url from tourRoute
– Require reviewRoutes
– Mount /:tourId/review from tourRoute to reviewRoute: router.use(‘/:tourId/reviews’, reviewRouter)
– merge params: const router = express.Router({ mergeParams:true })



Get all reviews for one tour
– take advantage of nested routes
– simply add a filter to getAllReviews

Refactor to handler Factory
– to standardize delete, create, update, getOne, getAll methods of tour,user,review
– when there are specific codes that deem a method not the same, simply add new middleware for those specific codes before the standard method

Get Me, current user infomation
– use getUser, but the id comes from a middleware just before getUser, instead of from the URL


Review authentication for all router
– we can add a middleware with router.use(authController.protect), so that all routes below are only run after that protect middleware. Similarly, all routes below the restrictTo middleware are only accessible by admin role




Indexing frequently read fields for faster querying
– check for the number of doc scanned with query.explain(), console.log or respond to see.
– in Model, indexing with tourSchema.index({price: 1, ratingsAverage: -1}),
– to delete indexing, do it from compass, only exclude from the code isnt enough

Calculating average & quantity of ratings, to make a middleware function to calculate & update those numbers whenever a reviews is added, updated or deleted.
– static vs instance methods, static linked to the Model itself, while instance is linked to an “extension” of that Model. https://riptutorial.com/mongoose/example/10574/schema-statics
– when creating new review, use the function in post.save() middleware, this.constructor refers to Review!? while this points to the current review, or this is the document and this.constructor points to its model

– for updating and deleting, there is no dedicated middleware, only query middleware is available. Need to use some trick/workaround here… There is a simpler alternative solution as well!




One user should have only one review for a tour
– simply use indexing for combination & set unique object


Round with setter function in mongoose Model

Geospatial query to find nearby tours
– New route, new function to handle
– indexing for fast query: tourSchema.index({ startLocation: ‘2dsphere’ });
– documentation https://docs.mongodb.com/manual/reference/operator/query-geospatial/





Calculating distances
– use aggregate, but $geoNear needs to be the first stage so need to comment out the aggregate middleware.

Documentation on Postman
– simply add descriptions…
Section 12 – Server-side rendering

npm i pug
Create pug engine, in the express app.js file


Route to pub template and respond








Pass data from route to pub template, simply an object in the 2nd argument of render()

Split to separate files and include back to the template

Extends the base template

Refactor into router, controller

Put everything together:
– Get data, res with tours
– Build template
– Render template with data, loop with each t in tours





Mixin in pug, like a sub-template for repetitive parts

Block Extend, 2 types, either remove everything in the block with new content, or add new content to the block with append/prepend.

Get data from
HTML: data-locations=${JSON.stringify(tour.locations)}
to Javascript:

Mapbox https://docs.mapbox.com/mapbox-gl-js/api/
– Include library, create container ‘map’
– Include custom mapbox script at the bottom of the pub template







Login
– get login info from front end with js
– include axios front end js, make resquest, either npm i axios or include script from cdn
– npm i cookie-parser, include and use with app.use()
– access cookie with req.cookie





Displaying correct pug template for login/logout user
– use res.locals.var to pass variable ‘var’ to local browser environment that the pub template can access
– router.use(isLoggedIn) middleware before any view route.



Auto redirect to homepage

Parcel, npm i parcel-bundler –save-dev, dev dependency
– watch for changes and update bundle.js (which is included into the base pub template)
– refactor, export functions and import to index.js
– @babel/polyfill



Alert login status





Logout, due to security reasons, we cannot simply delete the cookie, the workaround is to send back a dummy token + short expiry time
– make a logout controller, add to auth Routes
– make logout function with axios GET request from the front end login.js
– add logout function to eventListener if (logoutBtn) logoutBtn.addEventListener(‘click’, logout)





Error page render
– modify error Controller to branch out to API and Render, then isOperational or not
– make error.pug



User account page render
– make account pug template
– add route and controller

Updating with HTML form
– at Form HTML, add, action = url, method = POST, specify attributes (with name=’attr’) which will be pass as var to req.body
– add body parser in express to handle incoming data from a HTML form, app.use(express.urlencoded())

Updating with API:
– make a front end axios request function to the API, add this via an Event Listener to index.js (similar to login)





Updating password:
– similar to data, but make use of different api and data
– the await make the little touch on save button and clear password field visible. There, it actually waits for the save password to complete, instead of move on immediately to next codes.


Section 13 – Upload files, payments

Upload files with multer https://github.com/expressjs/multer
– npm i multer
– multer without dest will just save file to memory and not store on any path
– ‘photo’ is the form field name
– refactor to userController, cb is callback function, cb(raise error if any, other aug)
– the file upload through from will be accessible at req.file





Update user, link user to uploaded image name

Default image for new user

Edit/resize then save the images with sharp https://sharp.pixelplumbing.com/en/stable/
– npm i sharp, require
– comment out multer diskStorage, simply use multer.memoryStorage()
– access the file with req.file.buffer
– need to create req.file.filename for updateMe middleware because the image is now only in buffer
– need to await the whole editing and uploading process



Enabling from account page
– clicking on label will activate input with ID ‘photo’ (the line right above), which will open up a window to select file from
– 2 options as before, with/without API, for without API will need to specify enctype=’multipart/form-data’
– just like in Postman where we switch to using form (instead of raw as before) to submit files, to upload files we’ll need to modify index.js to a form. And no need to change the other middlewares!

Upload tour images
– upload multiple files with upload.fields
– imageCover is similar to user image
– images, here somehow foreach does not work, need to use map and then await Promise.all so that all img and req.body processing is done before the next step.



Email class
– constructor(what var to take in), and specify internal var
– createTransport()
– send(temp,subject), and other sendWelcome, sendAbc functions that make use of this bare send functions
– npm i html-to-text



Prepare Pub template
– html to pug https://html2pug.now.sh/
– html email template https://github.com/leemunroe/responsive-html-email-template
– split to style, base, block content etc. files





Sendgrid
– web api or smtp (select this for nodemailer)

Mailsac temporary email address without signup

Stripe.com Process payment

Backend
– Make bookingRoutes, Controllers add to app.js. This doesnt really follow the Restful model, because not returning anydata, just need a middleware to process something here.
– npm i stripe, weird require statement, because this require will return a function so we can call it immediately and pass in the key
const stripe = require(‘stripe’)(process.env.STRIPE_SECRET_KEY);
– in controller, create a stripe session:



Frontend
– edit tour pug template to 1. include stripe cdn, 2. display Book tour vs login depending on login status
– scrape stripe.js for bookTour function that calls backend to create session
– add to index.js an event listener to bookTour button to call bookTour function
– trick to get data/content from HTML class, data-xyz-abc will be available as element.dataset.xyzAbc in JS
– stripe redirect to checkout page



Save booking info to the database
– create Booking Model, with parent ref to tour, user, and price
– create middleware to add to database with BookingModel.create(). Add this middleware to the appropriate route.
– in middleware can redirect to another url with res.redirect()
– first unsecured version with query in the url after the ?tour=xyz . Then anyone can create bookings without paying …



err updateUserData controller is missing … in viewRoutes and viewControllers

My booking page
– account pug template, modify my booking link
– add route and controller
– in controller, find bookings, then find tour from bookings with the $in operator of mongoose
– reuse the overview pug template, and simply pass in the found tours

complete CRUD for booking routes
Section 14 – Git & Heroku

Install here: https://git-scm.com/
Create account here: https://github.com/

git , check version
git config –global user.name “Kien Vo”

In the root folder:
git init, to initialize git
add .gitignore file, usually includes node_modules/ and *.env
git status, see not yet commited (new) files, or modified files
git add -A, add to staging before commit, -A for all the files,
git commit -m “Inital commit”, a commit is like a snapshot of all the files at one point in time, -m for message

after some changes:
git status, see modified files
git add (-A or files), add to staging
git commit -m “change sth”, commit new changes

Create new repo on git hub, similar to git init, but on github

Use these code provided from git to push from origin to master branchgit remote add origin https://github.com/kienvo1202/nato.git git push -u origin master






git pull origin master, to pull from master to local computer

Some final touches:
– Add compression for every text response to the client, npm i compression, then simply app.use() in app.js
– Remove all console.log()
– Modify urls with localhost, in html, url starting with / is root relative.



Heroku modifications:
– heroku will actually use npm start command, and nodemon is actually only used in dev so let’s change to node server.js only.
– add to package.json “engines”: “>= 10.0.0”
– heroku needs port = process.env.PORT in server.js to work

Heroku commands at root folder
– heroku login,
– heroku create (after git add & commit)
– git push heroku master, push from heroku branch to master
– heroku logs –tail
– heroku open, open app on browser
– heroku apps:rename nato

Modify postman URL
test gzip compression

Heroku specifics https settings
– add app.enable(‘trust proxy’) to app.js
– modify cookie token:





Heroku sigterm, Heroku restarts every 24h, and this can be abrupt, so we add a listening process.on(SIGTERM) to modify the process.
– heroku ps, see the dyno,
– heroku ps:restart, restart now, instead of every 24h

CORS, cross origin request, allows other different domains (i.e everyone) to access API on our domain (normally this would fail automatically)
– npm i cors, https://github.com/expressjs/cors/blob/master/lib/index.js, this docs you’ll simply see that cors simply modify the headers to *
– only allow access to certain routes: app.use(‘/api/v1/users’, cors(), userRouter)
– only allow access from certain domain: app.use(cors({origin:’https://www.natours.com’}))
– stuff so far will only work for simple requests, get post
– for other requests (with pre flight @#E??? mode), add
app.options(‘*’, cors());
// app.options(‘/api/v1/tours/:id’, cors())



Stripe webhooks, stripe sends back a post request to confirm the transaction status
– add webhook on stripe
– add app.post request to app.js, use express.raw before express.json (required by stripe to have the request in raw form, not json)
– extract info to create Booking










Comments

Popular posts from this blog

Bryan Peterson – Learning to See Creatively

R – Stats, factor count,proportion

R - Supervised Learning