-
Notifications
You must be signed in to change notification settings - Fork 26
J.Sorge w5 Exercise - Complete #26
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| const mongoose = require('mongoose') | ||
|
|
||
| const schema = mongoose.Schema({ | ||
| username: { | ||
| type: String, | ||
| required: true | ||
| }, | ||
| password: { | ||
| type: String, | ||
| required: true | ||
| }, | ||
| admin: { | ||
| type: Boolean, | ||
| default: false | ||
| } | ||
| }, | ||
| {timestamps: { | ||
| createdAt: 'created_at', | ||
| updatedAt: 'updated_at' | ||
| } | ||
| }) | ||
|
|
||
| module.exports = mongoose.model('User', schema) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,120 @@ | ||
| const router = require('express').Router() | ||
| const User = require('../models/user') | ||
| const bcrypt = require('bcrypt'); | ||
| const saltRounds = 10; | ||
| const jwt = require('jsonwebtoken'); | ||
| const { SECRET_KEY } = process.env | ||
|
|
||
| router.post('/signup', async (req, res, next) => { | ||
| const status = 201 | ||
| try { | ||
| const { username, password } = req.body | ||
| // Username is not provided | ||
| if (!username) throw new Error (`Username not provided.`) | ||
| // Username is already taken | ||
| const alreadyUsername = await User.findOne({username}) | ||
| .select('-__v -password') | ||
| if (alreadyUsername) throw new Error (`Username already taken.`) | ||
|
|
||
| // Password is not provided | ||
| if (!password) throw new Error (`Password not provided.`) | ||
| // password is less than 8 characters | ||
| if (password.length < 8) throw new Error (`Password is less than 8 characters.`) | ||
|
|
||
| const hashedPassword = await bcrypt.hash(password, saltRounds) | ||
| const user = await User.create({ | ||
| username, | ||
| password: hashedPassword | ||
| }) | ||
|
|
||
| const payload = { id: user._id } | ||
| const options = { expiresIn: '1 day' } | ||
| const token = jwt.sign(payload, SECRET_KEY, options) | ||
|
|
||
| res.json({ status, token }) | ||
| } catch (e) { | ||
| e.status = 400 | ||
| console.error(e) | ||
| next(e) | ||
| } | ||
| }) | ||
|
|
||
|
|
||
| router.post('/login', async (req, res, next) => { | ||
| const status = 201 | ||
| try { | ||
| const { username, password } = req.body | ||
| const user = await User.findOne({ username}) | ||
| .select('-__v -password') | ||
| // Username is not found | ||
| if (!user) throw new Error (`Incorrect login credentials.`) | ||
|
|
||
| // If username does exist, compare the plain text password the the hashed version | ||
| const isValid = await bcrypt.compare(password, user.password) | ||
| if (!isValid) throw new Error(`Incorrect login credentials.`) | ||
|
|
||
| const payload = { id: user._id } | ||
| const options = { expiresIn: '1 day' } | ||
| const token = jwt.sign(payload, SECRET_KEY, options) | ||
|
|
||
| res.json({ status, token }) | ||
| } catch (e) { | ||
| console.error(e) | ||
| const error = new Error ('Incorrect login credentials') | ||
| error.status = 401 | ||
| next(error) | ||
| } | ||
| }) | ||
|
|
||
| // PATCH /api/users/:id/permissions to change admin permissions of another user | ||
| // Finds admin: <boolean> key in req.body | ||
| // Returns 204 | ||
| router.patch('/api/users/:id/permissions', async (req, res, next) => { | ||
| const status = 204 | ||
| try { | ||
| const { admin } = req.body | ||
| // Request body does not include an admin key with a boolean value 400 | ||
| if (!( admin === true || admin === false)) { | ||
| const error = new Error (`Invalid request.`) | ||
| error.status = 400 | ||
| next(error) | ||
| } | ||
|
|
||
| // Valid JWT is not provided 401 | ||
| const token = req.headers.authorization.split('Bearer ')[1] | ||
| if (!token) { | ||
| const error = new Error (`Invalid token.`) | ||
| error.status = 401 | ||
| next(error) | ||
| } | ||
| const payload = jsonwebtoken.verify(token, SECRET_KEY) | ||
|
|
||
| // JWT token is for a user who is not an admin 401 | ||
| const clientUser = await User.findOne({ _id: payload.id }) | ||
| .select('-__v -password') | ||
| if (!clientUser.admin === true) { | ||
| const error = new Error (`Insufficient privelages.`) | ||
| error.status = 401 | ||
| next(error) | ||
| } | ||
|
|
||
| // User cannot be found 404 | ||
| const userToUpdate = await User.findOne({_id: req.params.id}) | ||
| .select('-__v -password') | ||
| if (!userToUpdate) { | ||
| const error = new Error (`User cannot be found.`) | ||
| error.status = 404 | ||
| next(error) | ||
| } | ||
|
|
||
| Object.assign(userToUpdate, admin) | ||
| await user.save() | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This shouldn't work, as there's no |
||
|
|
||
| res.json({ status, user }) | ||
| } catch (e) { | ||
| console.error(e) | ||
| next(e) | ||
| } | ||
| }) | ||
|
|
||
| module.exports = router | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,8 @@ | ||
| const router = require('express').Router() | ||
| const Book = require('../models/book') | ||
| const User = require('../models/user') | ||
| const jsonwebtoken = require('jsonwebtoken'); | ||
| const { SECRET_KEY } = process.env | ||
|
|
||
| router.get('/', async (req, res, next) => { | ||
| const status = 200 | ||
|
|
@@ -26,53 +29,131 @@ router.get('/:id', async (req, res, next) => { | |
|
|
||
| // You should only be able to create a book if the user is an admin | ||
| router.post('/', async (req, res, next) => { | ||
| const status = 200 | ||
| const status = 201 | ||
| try { | ||
| // Valid JWT is not provided 401 | ||
| const token = req.headers.authorization.split('Bearer ')[1] | ||
| if (!token) { | ||
| const error = new Error (`Invalid token.`) | ||
| error.status = 401 | ||
| next(error) | ||
| } | ||
| const payload = jsonwebtoken.verify(token, SECRET_KEY) | ||
|
|
||
| // JWT token is for a user who is not an admin 401 | ||
| const clientUser = await User.findOne({ _id: payload.id }) | ||
| .select('-__v -password') | ||
| if (!clientUser.admin === true) { | ||
| const error = new Error (`Insufficient privelages.`) | ||
| error.status = 401 | ||
| next(error) | ||
| } | ||
|
|
||
| const book = await Book.create(req.body) | ||
| if (!book) throw new Error(`Request body failed: ${JSON.stringify(req.body)}`) | ||
|
|
||
| const response = await Book.findById(book._id).select('-__v') | ||
| res.json({ status, response }) | ||
| } catch (e) { | ||
| console.error(e) | ||
| const message = 'Failure to create. Please check request body and try again.' | ||
| const error = new Error(message) | ||
| error.status = 400 | ||
| next(error) | ||
| next(e) | ||
| } | ||
| }) | ||
|
|
||
| // You should only be able to reserve a book if a user is logged in | ||
| router.patch('/:id/reserve', async (req, res, next) => { | ||
| const { id } = req.params | ||
| try { | ||
| const token = req.headers.authorization.split('Bearer ')[1] | ||
| // A valid JWT token is not provided (status 401) | ||
| if (!token) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. You don't want to just check whether or not there is a token, you want to check whether or not the token is valid. In this case, you should use |
||
| const error = new Error (`Invalid token.`) | ||
| error.status = 401 | ||
| next(error) | ||
| } | ||
| const payload = jsonwebtoken.verify(token, SECRET_KEY) | ||
|
|
||
| const book = await Book.findById(id) | ||
| // Book cannot be found (status 404) | ||
| if (!book) { | ||
| const error = new Error(`Invalid Book _id: ${id}`) | ||
| error.message = 404 | ||
| return next(error) | ||
| } | ||
|
|
||
| book.reserved.status = true | ||
| // Set the reserved memberId to the current user | ||
| await book.save() | ||
|
|
||
| const response = await Book.findById(book._id).select('-__v') | ||
| const status = 200 | ||
| res.json({ status, response }) | ||
| // The book is already reserved (status 400) | ||
| if (book.reserved.status === true) { | ||
| const error = new Error(`Book is already reserved.`) | ||
| error.message = 400 | ||
| return next(error) | ||
|
|
||
| } else if (book.reserved.status === false){ | ||
| book.reserved.status = true | ||
| // Set the reserved memberId to the current user | ||
| book.reserved.memberId = payload.id | ||
| await book.save() | ||
|
|
||
| const response = book | ||
| const status = 200 | ||
| res.json({ status, response }) | ||
| } | ||
| } catch (e) { | ||
| console.error(e) | ||
| next(e) | ||
| } | ||
| }) | ||
|
|
||
| // You should only be able to return a book if the user is logged in | ||
| // and that user is the one who reserved the book | ||
| router.patch('/:id/return', async (req, res, next) => { | ||
| const status = 200 | ||
| const message = 'You must implement this route!' | ||
|
|
||
| console.log(message) | ||
| res.status(status).json({ status, message }) | ||
| const { id } = req.params | ||
| try { | ||
| const token = req.headers.authorization.split('Bearer ')[1] | ||
| // A valid JWT token is not provided (status 401) | ||
| if (!token) { | ||
| const error = new Error (`Invalid token.`) | ||
| error.status = 401 | ||
| next(error) | ||
| } | ||
| const payload = jsonwebtoken.verify(token, SECRET_KEY) | ||
|
|
||
| const book = await Book.findById(id) | ||
| // Book cannot be found (status 404) | ||
| if (!book) { | ||
| const error = new Error(`Invalid Book _id: ${id}`) | ||
| error.message = 404 | ||
| return next(error) | ||
| } | ||
|
|
||
| // The book is not reserved (status 400) | ||
| if (book.reserved.status === false) { | ||
| const error = new Error(`Book is not reserved.`) | ||
| error.message = 400 | ||
| return next(error) | ||
| } | ||
|
|
||
| // The book is reserved by a different user (status 401) | ||
| if (book.reserved.memberId != payload.id) { | ||
| const error = new Error(`Book is reserved by a different user.`) | ||
| error.message = 401 | ||
| return next(error) | ||
| } | ||
|
|
||
| if (book.reserved.status === true){ | ||
| // Set the reserved memberId to the current user | ||
| book.reserved.status = false | ||
| book.reserved.memberId = null | ||
| await book.save() | ||
|
|
||
| const response = book | ||
| const status = 200 | ||
| res.json({ status, response }) | ||
| } | ||
| } catch (e) { | ||
| console.error(e) | ||
| next(e) | ||
| } | ||
| }) | ||
|
|
||
| module.exports = router | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because you're performing this operation multiple times, this is a great thing to put into a function.