From 1749b7ded08f82f2b93a5c375c584781a9c085e8 Mon Sep 17 00:00:00 2001 From: bbarth86 <46633689+bbarth86@users.noreply.github.com> Date: Tue, 30 Jul 2019 23:43:06 -0700 Subject: [PATCH 1/5] Add files via upload --- app.js | 75 +++++++++++++++++++++++++++++----------------------------- 1 file changed, 38 insertions(+), 37 deletions(-) diff --git a/app.js b/app.js index 075ce75..221944d 100644 --- a/app.js +++ b/app.js @@ -1,37 +1,38 @@ -const { MONGO_DB_CONNECTION, NODE_ENV, PORT } = process.env -const express = require('express') -const mongoose = require('mongoose') -const app = express() - -// Database Connection -if (MONGO_DB_CONNECTION) { - mongoose.connect(MONGO_DB_CONNECTION, { useNewUrlParser: true }) - console.log('Connected to database...') -} else { - console.log('Could not connect to database!') -} - -// Application-level Middleware -if (NODE_ENV === 'development') app.use(require('morgan')('dev')) -app.use(require('body-parser').json()) - -// Routes -app.use('/api/books', require('./api/routes/books')) - -// Not Found Handler -app.use((req, res, next) => { - const error = new Error(`Could not ${req.method} ${req.path}`) - error.status = 404 - next(error) -}) - -// Error Handler -app.use((err, req, res, next) => { - if (NODE_ENV === 'development') console.error(err) - const { message, status } = err - res.status(status).json({ status, message }) -}) - -// Open Connection -const listener = () => console.log(`Listening on Port ${PORT}!`) -app.listen(PORT, listener) +const { MONGO_DB_CONNECTION, NODE_ENV, PORT } = process.env; +const express = require('express'); +const mongoose = require('mongoose'); +const app = express(); + +// Database Connection +if (MONGO_DB_CONNECTION) { + mongoose.connect(MONGO_DB_CONNECTION, { useNewUrlParser: true, useFindAndModify: false}); + console.log('Connected to database...'); +} else { + console.log('Could not connect to database!'); +} + +// Application-level Middleware +if (NODE_ENV === 'development') app.use(require('morgan')('dev')); +app.use(require('body-parser').json()); + +// Routes +app.use('/api/books', require('./api/routes/books')); +app.use('/api/', require('./api/routes/auth')); + +// Not Found Handler +app.use((req, res, next) => { + const error = new Error(`Could not ${req.method} ${req.path}`); + error.status = 404; + next(error); +}) + +// Error Handler +app.use((err, req, res, next) => { + if (NODE_ENV === 'development') console.error(err); + const { message, status } = err; + res.status(status).json({ status, message }); +}) + +// Open Connection +const listener = () => console.log(`Listening on Port ${PORT}!`); +app.listen(PORT, listener); From c03403deb4ce281fa349183b7d4fb40a5d489298 Mon Sep 17 00:00:00 2001 From: bbarth86 <46633689+bbarth86@users.noreply.github.com> Date: Tue, 30 Jul 2019 23:43:29 -0700 Subject: [PATCH 2/5] Add files via upload --- db/seeds.js | 113 +++++++++++++++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 49 deletions(-) diff --git a/db/seeds.js b/db/seeds.js index 3ea4a6a..1192c72 100644 --- a/db/seeds.js +++ b/db/seeds.js @@ -1,49 +1,64 @@ -const mongoose = require('mongoose') -const Book = require('../api/models/book') -const config = require('../nodemon.json') - -const reset = async () => { - mongoose.connect(config.env.MONGO_DB_CONNECTION, { useNewUrlParser: true }) - await Book.deleteMany() // Deletes all records - return await Book.create([ - { - title: 'The Colour of Magic', - published: 1983, - authors: [ - { - name: 'Sir Terry Pratchett', - dob: '04-28-1948' - } - ] - }, - { - title: 'Stardust', - published: 1997, - authors: [ - { - name: 'Neil Gaiman', - dob: '11-10-1960' - } - ] - }, - { - title: 'Good Omens: The Nice and Accurate Prophecies of Agnes Nutter, Witch', - published: 1990, - authors: [ - { - name: 'Neil Gaiman', - dob: '11-10-1960' - }, - { - name: 'Sir Terry Pratchett', - dob: '04-28-1948' - } - ] - } - ]) -} - -reset().catch(console.error).then((response) => { - console.log(`Seeds successful! ${response.length} records created.`) - return mongoose.disconnect() -}) \ No newline at end of file +const mongoose = require('mongoose'); +const User = require('../api/models/user'); +const Book = require('../api/models/book'); +const config = require('../nodemon.json'); +const bcrypt = require('bcrypt'); + +const reset = async () => { + mongoose.connect(config.env.MONGO_DB_CONNECTION, { useNewUrlParser: true }); + await Book.deleteMany(); // Deletes all records + await User.deleteMany(); // Deletes all records + const password = "password" + const saltRounds = 10; + const hashedPassword = await bcrypt.hash(password, saltRounds); + await User.create([{ + username: "basicUser", + password: hashedPassword, + admin: false + }, { + username: "powerUser", + password: hashedPassword, + admin: true + }]); + return await Book.create([ + { + title: 'The Colour of Magic', + published: 1983, + authors: [ + { + name: 'Sir Terry Pratchett', + dob: '04-28-1948' + } + ] + }, + { + title: 'Stardust', + published: 1997, + authors: [ + { + name: 'Neil Gaiman', + dob: '11-10-1960' + } + ] + }, + { + title: 'Good Omens: The Nice and Accurate Prophecies of Agnes Nutter, Witch', + published: 1990, + authors: [ + { + name: 'Neil Gaiman', + dob: '11-10-1960' + }, + { + name: 'Sir Terry Pratchett', + dob: '04-28-1948' + } + ] + } + ]); +} + +reset().catch(console.error).then((response) => { + console.log(`Seeds successful! ${response.length} records created.`); + return mongoose.disconnect(); +}); \ No newline at end of file From d82d2449b16d5360330a1d3d0ef4755241ecf521 Mon Sep 17 00:00:00 2001 From: bbarth86 <46633689+bbarth86@users.noreply.github.com> Date: Tue, 30 Jul 2019 23:43:57 -0700 Subject: [PATCH 3/5] Add files via upload --- api/models/user.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 api/models/user.js diff --git a/api/models/user.js b/api/models/user.js new file mode 100644 index 0000000..27a04d3 --- /dev/null +++ b/api/models/user.js @@ -0,0 +1,12 @@ +const mongoose = require('mongoose'); + +const schema = new mongoose.Schema({ + username: String, + password: String, + admin: { + type: String, + default: false + } +}, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }); + +module.exports = mongoose.model('User', schema); \ No newline at end of file From 9a9fb1af4366940fb789746fb6b068389429a941 Mon Sep 17 00:00:00 2001 From: bbarth86 <46633689+bbarth86@users.noreply.github.com> Date: Tue, 30 Jul 2019 23:44:31 -0700 Subject: [PATCH 4/5] Add files via upload --- api/routes/auth.js | 149 ++++++++++++++++++++++++++++ api/routes/books.js | 229 +++++++++++++++++++++++++++++--------------- 2 files changed, 301 insertions(+), 77 deletions(-) create mode 100644 api/routes/auth.js diff --git a/api/routes/auth.js b/api/routes/auth.js new file mode 100644 index 0000000..f180dfb --- /dev/null +++ b/api/routes/auth.js @@ -0,0 +1,149 @@ +const router = require('express').Router(); +const User = require('../models/user'); +const bcrypt = require('bcrypt'); +const jsonwebtoken = require('jsonwebtoken'); + +const { SECRET_KEY } = process.env; + + /*** + * - [ ] **Create a `POST /api/signup` route:** Create a new route that allows someone to create an account. Securely store the password using the `bcrypt` package. + * On successful creation, return a JWT token. You should return an error in the following cases: + * Username is not provided + * Username is already taken + * Password is not provided + * Password is less than 8 characters + */ + +router.post('/signup', async (req, res, next) => { + const status = 201; + try { + const {username, password} = req.body; + if (!username) { + throw new Error (`User name not provided. Please update your request and retry.`); + } + if (!password) { + throw new Error (`Password not provided. Please update your request and retry.`); + } + if (password.length < 8) { + throw new Error (`Password does not meet minimum length requirements. Please update your request and retry.`) + } + const user = await User.findOne({username}); + if (user) { + throw new Error (`User ${username} already exists.`) + } + const saltRounds = 10; + const hashedPassword = await bcrypt.hash(password, saltRounds); + const newUser = await User.create({ + username, + password: hashedPassword + }); + const payload = {username: newUser.username, password: newUser.password }; + const options = { expiresIn: '1 day' }; + const token = jsonwebtoken.sign(payload, SECRET_KEY, options); + res.status(201).json({ status, token}); + } catch (e) { + console.error(e); + const error = new Error(`An error has occurred. Please retry your request.`); + error.status = 400; + next(error); + } + +}); + +/** + * + * - [ ] **Create a `POST /api/login` route:** Create a new route that allows someone to login. On successful creation, return a JWT token. You should return an error in the following cases: + * Username is not found + * Username and password do not match + * + */ + +router.post('/login', async (req, res, next) => { + const status = 201; + try { + const {username, password} = req.body; + const user = await User.findOne({username}); + if (!user) { + const error = new Error(`Username could not be found.`); + error.message = 404; + return next(error); + } + const isValid = await bcrypt.compare(password, user.password); + if (!isValid) { + const error = new Error(`Password is invalid.`); + error.message = 400; + return next(error); + } + const payload = { id: user._id } + const options = { expiresIn: '1 day' } + const token = jsonwebtoken.sign(payload, SECRET_KEY, options) + res.status(201).json({ status, token}); + } catch (e) { + console.error(e); + const error = new Error(`Please check your username and password and try again.`) + error.status = 400; + next(error); + + } +}); + +/** + * + * - [ ] **Create a `PATCH /api/users/:id/permissions` route:** Create a new route that allows for an admin to change permissions of another user. + * The route should only be looking for the `admin: ` key in the request body and setting the value appropriately. On success, return a status 204. + * You should return an error in the following cases: + * A valid JWT token is not provided (status 401) + * The JWT token is for a user who is not an admin (status 401) + * User cannot be found (status 404) + * The request body does not include an `admin` key with a boolean value (status 400) + + * + */ + +router.patch('/users/:id/permissions', async (req, res, next) => { + const { id } = req.params; + try { + const token = req.headers.authorization.split(`Bearer `)[1]; + const isValid = jsonwebtoken.verify(token, SECRET_KEY); + if (!isValid) { + throw new Error(`Token is not valid. Please check your request and try again.`); + //error.message = 401; I continously ran into failed requests until i commented this out and changed the above from `const error = new Error...` to the current statement. Any suggestion as to why this is? This prevents me from outputing the specific error codes as intended. + //return next(error); + } + const { admin } = req.body; + if (admin == null) { + throw new Error(`Unable to authenticate as Admin. Please check your request and try again. `); + //error.message = 401; + //return next(error); + } + const isAdmin = await User.findOne({_id: isValid.id}); + if (isAdmin.admin == false) { + throw new Error(`Ah ah ah, you didn't say the magic word! Please check your request and try again.`); + // i could not get this error to throw successfully even when the user.admin = false. is something off with my if condition? I can't figure out why this isn't getting caught, and user is able to manage other users. + //error.message = 401; + //return next(error); + } + const user = await User.findById(id); + if (!user) { + throw new Error(`Username could not be found. Please check your request and try again.`); + //error.message = 401; + //return next(error); + } + const response =await User.findOneAndUpdate( + {_id: id}, + {admin: admin}, + { new: true } + ).select('-__v'); + const status = 204; + res.json({ status, response }); + } catch (e) { + console.error(e); + const error = new Error(`Please check your username and password and try again.`) + error.status = 400; + next(error); + + } +}); + +module.exports = router + diff --git a/api/routes/books.js b/api/routes/books.js index 4ad32c8..5952442 100644 --- a/api/routes/books.js +++ b/api/routes/books.js @@ -1,78 +1,153 @@ -const router = require('express').Router() -const Book = require('../models/book') - -router.get('/', async (req, res, next) => { - const status = 200 - const response = await Book.find().select('-__v') - - res.json({ status, response }) -}) - -router.get('/:id', async (req, res, next) => { - const { id } = req.params - const status = 200 - try { - const response = await Book.findById(id).select('-__v') - if (!response) throw new Error(`Invalid Book _id: ${id}`) - - res.json({ status, response }) - } catch (e) { - console.error(e) - const error = new Error(`Cannot find book with id ${id}.`) - error.status = 404 - next(error) - } -}) - -// You should only be able to create a book if the user is an admin -router.post('/', async (req, res, next) => { - const status = 200 - try { - 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) - } -}) - -// 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 book = await Book.findById(id) - 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 }) - } catch (e) { - console.error(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 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; + const response = await Book.find().select('-__v'); + res.json({ status, response }); +}) + +router.get('/:id', async (req, res, next) => { + const { id } = req.params; + const status = 200 + try { + const response = await Book.findById(id).select('-__v'); + if (!response) throw new Error(`Invalid Book _id: ${id}`); + res.json({ status, response }); + } catch (e) { + console.error(e); + const error = new Error(`Cannot find book with id ${id}.`); + error.status = 404; + next(error); + } +}) + +/*** + * - [ ] **Update the `POST /api/books` route:** This route should only be available to users who are admins. + * If the user is an admin, proceed as normal. If they are not an admin, return an error message with a status code of 401. + */ + +router.post('/', async (req, res, next) => { + const status = 200; + try { + const token = req.headers.authorization.split(`Bearer `)[1]; + const isValid = jsonwebtoken.verify(token, SECRET_KEY); + if (!isValid) { + throw new Error(`Token is not valid. Please check your request and try again.`); + //error.message = 401; I continously ran into failed requests until i commented this out and changed the above from `const error = new Error...` to the current statement. Any suggestion as to why this is? + //return next(error); + } + const isAdmin = await User.findOne({_id: isValid.id}); + if (isAdmin.admin = false) { + throw new Error(`Ah ah ah, you didn't say the magic word! Please check your request and try again.`); + // i could not get this error to throw successfully even when the user.admin = false. is something off with my if condition? I can't figure out why this isn't getting caught, and books continue to get created. + //error.message = 401; + //return 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) + } +}) + +/** + * - [ ] **Update the `POST /api/books/:id/reserve` route:** This route allows for someone to reserve a book. If the user is logged in, proceed as normal. You should return an error in the following cases: + * A valid JWT token is not provided (status 401) + * The book is already reserved (status 400) + * Book cannot be found (status 404) + */ + +router.patch('/:id/reserve', async (req, res, next) => { + const { id } = req.params + try { + // validate token, identify user, identify book + const token = req.headers.authorization.split(`Bearer `)[1]; + const isValid = jsonwebtoken.verify(token, SECRET_KEY); + const user = await User.findOne({ _id: isValid.id }); + if (!isValid) { + const error = new Error(`Token is not valid. Please check your request and try again.`); + error.status = 401; + return next(error); + } + const book = await Book.findById(id) // book id derived from req.params + if (!book) { + const error = new Error(`Invalid Book _id: ${id}`); + error.status = 404; + return next(error); + } + if (book.reserved.status === true) { + const error = new Error(`This book has already been reserved.`); + error.status = 400; + return next(error); + } + book.reserved.status = true; // reserve book + book.reserved.memberId = user._id // set reserve member ID as user + await book.save() + + const response = await Book.findById(book._id).select('-__v') + const status = 200 + res.json({ status, response }) + } catch (e) { + console.error(e) + } +}) + +/*** + * - [ ] **Create a `PATCH /api/books/:id/return` route:** This route should return a book if the user has already reserved it. If the appropriate user is returning the book, set the `reserved.status` to `false` and update the `reserved.memberId` to be `null`. You should return an error in the following cases: + * A valid JWT token is not provided (status 401) + * The book is reserved by a different user (status 401) + * The book is not reserved (status 400) + * Book cannot be found (status 404) + */ + +router.patch('/:id/return', async (req, res, next) => { + const { id } = req.params + try { + const token = req.headers.authorization.split(`Bearer `)[1]; + const isValid = jsonwebtoken.verify(token, SECRET_KEY); + const user = await User.findOne({ _id: isValid.id }); + console.log(user); + if (!isValid) { + const error =new Error(`Token is not valid. Please check your request and try again.`); + error.status = 401; + return next(error); + } + const book = await Book.findById(id); + if (!book) { + const error = new Error(`Invalid Book _id: ${id}`); + error.status = 404; + return next(error); + } + if (book.reserved.status === false) { + const error = new Error(`This book has not yet been reserved.`); + error.status = 400; + return next(error); + } + if (book.reserved.memberId !== user._id) { + const error = new Error(`This book is reserved by another user.`); + error.status = 400; + return next(error); + } + book.reserved.status = false; + book.reserved.memberId = ""; + await book.save(); + const response = await Book.findById(book._id).select('-__v'); + const status = 200; + res.json({ status, response }); + } catch (e) { + console.error(e); + } +}) + module.exports = router \ No newline at end of file From daa6844a0db87c87a2bcf03a4ffc34edf2a62bca Mon Sep 17 00:00:00 2001 From: bbarth86 <46633689+bbarth86@users.noreply.github.com> Date: Tue, 30 Jul 2019 23:44:53 -0700 Subject: [PATCH 5/5] Add files via upload --- api/models/book.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/api/models/book.js b/api/models/book.js index d89d93f..3e18d1f 100644 --- a/api/models/book.js +++ b/api/models/book.js @@ -1,22 +1,22 @@ -const mongoose = require('mongoose') - -const schema = new mongoose.Schema({ - authors: [{ - name: String, - dob: Date - }], - published: Number, - reserved: { - status: { - type: Boolean, - default: false - }, - memberId: mongoose.ObjectId - }, - title: { - type: String, - required: true - } -}, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }) - +const mongoose = require('mongoose') + +const schema = new mongoose.Schema({ + authors: [{ + name: String, + dob: Date + }], + published: Number, + reserved: { + status: { + type: Boolean, + default: false + }, + memberId: mongoose.ObjectId + }, + title: { + type: String, + required: true + } +}, { timestamps: { createdAt: 'created_at', updatedAt: 'updated_at' } }) + module.exports = mongoose.model('Book', schema) \ No newline at end of file