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 tourspassword 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
Post a Comment