diff --git a/vic-class11-assignment/.eslintignore b/vic-class11-assignment/.eslintignore new file mode 100644 index 0000000..5b88cef --- /dev/null +++ b/vic-class11-assignment/.eslintignore @@ -0,0 +1,5 @@ +**/node_modules/* +**/vendor/* +**/*.min.js +/*.md +/package.json diff --git a/vic-class11-assignment/.eslintrc b/vic-class11-assignment/.eslintrc new file mode 100644 index 0000000..6d505ad --- /dev/null +++ b/vic-class11-assignment/.eslintrc @@ -0,0 +1,41 @@ +{ +"rules": { +"no-console": 0, +"indent": [ +2, +2 +], +"quotes": [ +2, +"single" +], +"linebreak-style": [ +2, +"unix" +], +"semi": [ +2, +"always" +] +}, +"env": { +"es6": true, +"node": true, +"browser": true, +"mocha": true +}, +"globals": { +"describe": false, +"it": false, +"beforeEach": false, +"afterEach": false, +"before": false, +"after": false +}, +"ecmaFeatures": { +"modules": true, +"experimentalObjectRestSpread": true, +"impliedStrict": true +}, +"extends": "eslint:recommended" +} diff --git a/vic-class11-assignment/.gitignore b/vic-class11-assignment/.gitignore new file mode 100644 index 0000000..703bb84 --- /dev/null +++ b/vic-class11-assignment/.gitignore @@ -0,0 +1,4 @@ +node_modules +db +./db/* +../db/* diff --git a/vic-class11-assignment/gulpfile.js b/vic-class11-assignment/gulpfile.js new file mode 100644 index 0000000..3c0a395 --- /dev/null +++ b/vic-class11-assignment/gulpfile.js @@ -0,0 +1,20 @@ +const gulp = require('gulp'); +const eslint = require('gulp-eslint'); +const mocha = require('gulp-mocha'); + +gulp.task('default', ['lint', 'test'], () => { + console.log('started'); +}); + +gulp.task('lint', () => { + gulp.src('/server.js') + .pipe(eslint()) + .pipe(eslint.format()); +}); + +gulp.task('test', () => { + gulp.src('test/*.js') + .pipe(mocha()); +}); + +gulp.watch('./**/*.js', ['lint', 'test']); diff --git a/vic-class11-assignment/index.js b/vic-class11-assignment/index.js new file mode 100644 index 0000000..d3a98f5 --- /dev/null +++ b/vic-class11-assignment/index.js @@ -0,0 +1 @@ +module.exports = require(__dirname + '/server.js'); diff --git a/vic-class11-assignment/lib/basic_http.js b/vic-class11-assignment/lib/basic_http.js new file mode 100644 index 0000000..7e0aa79 --- /dev/null +++ b/vic-class11-assignment/lib/basic_http.js @@ -0,0 +1,21 @@ +'use strict'; + +module.exports = function(req, res, next) { + let basicAuth = req.headers.authorization; + let authString = basicAuth.split(' ')[1]; + let authBuff = new Buffer(authString, 'base64'); + let asciiAuth = authBuff.toString(); + let authArray = asciiAuth.split(':'); + authBuff.fill(0); + + req.auth = { + username: authArray[0], + password: authArray[1] + }; + + if(!req.auth.username || !req.auth.password) { + return next(new Error('Username or Password missing')); + } + + next(); +}; diff --git a/vic-class11-assignment/lib/error_handling.js b/vic-class11-assignment/lib/error_handling.js new file mode 100644 index 0000000..cde5178 --- /dev/null +++ b/vic-class11-assignment/lib/error_handling.js @@ -0,0 +1,4 @@ +module.exports = function(err, req, res, next) { + res.status(500).json({message: err.message}); + next(err); +}; diff --git a/vic-class11-assignment/lib/jwt_auth.js b/vic-class11-assignment/lib/jwt_auth.js new file mode 100644 index 0000000..df3e186 --- /dev/null +++ b/vic-class11-assignment/lib/jwt_auth.js @@ -0,0 +1,25 @@ +'use strict'; + +const jwt = require('jsonwebtoken'); +const User = require('../model/user.js'); +const secret = process.env.SECRET || 'changeme'; + +module.exports = function(req, res, next) { + let token = req.body.token || req.headers.token; + let tokenErr = new Error('Authorization Failure'); + let decodedToken; + + if(!token) return next(tokenErr); + + try { + decodedToken = jwt.verify(token, secret); + } catch(e) { + return next(tokenErr); + } + + User.findOne({_id: decodedToken._id}, (err, user) => { + if(!user || err) return next(tokenErr); + req.user = user; + next(); + }); +}; diff --git a/vic-class11-assignment/model/icecream.js b/vic-class11-assignment/model/icecream.js new file mode 100644 index 0000000..97efeeb --- /dev/null +++ b/vic-class11-assignment/model/icecream.js @@ -0,0 +1,11 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const IceCream = new mongoose.Schema({ + flavor: String, + scoops: Number, + vessel: String +}); + +module.exports = mongoose.model('icecream', IceCream); diff --git a/vic-class11-assignment/model/milkshake.js b/vic-class11-assignment/model/milkshake.js new file mode 100644 index 0000000..35bc324 --- /dev/null +++ b/vic-class11-assignment/model/milkshake.js @@ -0,0 +1,11 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const MilkShake = new mongoose.Schema({ + flavor: String, + scoops: Number, + milkRichness: String +}); + +module.exports = mongoose.model('milkshake', MilkShake); diff --git a/vic-class11-assignment/model/user.js b/vic-class11-assignment/model/user.js new file mode 100644 index 0000000..5282226 --- /dev/null +++ b/vic-class11-assignment/model/user.js @@ -0,0 +1,25 @@ +'use strict'; + +const mongoose = require('mongoose'); +const bcrypt = require('bcrypt'); +const jwt = require('jsonwebtoken'); +const secret = process.env.SECRET || 'changeme'; + +const User = new mongoose.Schema({ + username: {type: String, required: true}, + password: {type: String, required: true} +}); + +User.methods.hashPassword = function() { + return bcrypt.hashSync(this.password, 8); +}; + +User.methods.comparePassword = function(password) { + return bcrypt.compareSync(password, this.password); +}; + +User.methods.generateToken = function() { + return jwt.sign({_id: this._id}, secret); +}; + +module.exports = mongoose.model('user', User); diff --git a/vic-class11-assignment/package.json b/vic-class11-assignment/package.json new file mode 100644 index 0000000..f5ba2fe --- /dev/null +++ b/vic-class11-assignment/package.json @@ -0,0 +1,38 @@ +{ + "name": "two_resource_api_with_authentication", + "version": "1.0.0", + "description": "##To Submit this Assignment * fork this repository * write all of your code in a folder containing your name * push to your repository * submit a pull request to this repository * submit a link to your PR in canvas", + "main": "index.js", + "scripts": { + "test": "./node_modules/mocha/bin/mocha", + "start": "node index.js", + "lint": "./node_modules/eslint/bin/eslint.js *" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/vbenavente/rest_api.git" + }, + "keywords": [], + "author": "", + "license": "ISC", + "bugs": { + "url": "https://github.com/vbenavente/rest_api/issues" + }, + "homepage": "https://github.com/vbenavente/rest_api#readme", + "devDependencies": { + "chai": "^3.5.0", + "chai-http": "^2.0.1", + "gulp": "^3.9.1", + "gulp-eslint": "^2.0.0", + "gulp-mocha": "^2.2.0", + "gulp-watch": "^4.3.6", + "mocha": "^2.5.2" + }, + "dependencies": { + "bcrypt": "^0.8.6", + "body-parser": "^1.15.1", + "cors": "^2.7.1", + "express": "^4.13.4", + "jsonwebtoken": "^7.0.0" + } +} diff --git a/vic-class11-assignment/routes/auth_routes.js b/vic-class11-assignment/routes/auth_routes.js new file mode 100644 index 0000000..ce90010 --- /dev/null +++ b/vic-class11-assignment/routes/auth_routes.js @@ -0,0 +1,30 @@ +'use strict'; + +const express = require('express'); +const bodyParser = require('body-parser').json(); +const User = require('../model/user'); +const basicHTTP = require('../lib/basic_http'); + +const authRouter = module.exports = exports = express.Router(); + +authRouter.post('/signup', bodyParser, (req, res, next) => { + let newUser = new User(req.body); + let hashedPassword = newUser.hashPassword(); + newUser.password = hashedPassword; + req.body.password = null; + User.findOne({username: req.body.username}, (err, user) => { + if(err || user) return next(new Error('could not create user')); + newUser.save((err, user) => { + if(err) return next(new Error('could not create user')); + res.json({token:user.generateToken()}); + }); + }); +}); + +authRouter.get('/signin', basicHTTP, (req, res, next) => { + User.findOne({username: req.auth.username}, (err, user) => { + if(err || !user) return next(new Error('could not sign in - username not found')); + if(!user.comparePassword(req.auth.password)) return next(new Error('could not sign in - incorrect password')); + res.json({token: user.generateToken()}); + }); +}); diff --git a/vic-class11-assignment/routes/icecream_routes.js b/vic-class11-assignment/routes/icecream_routes.js new file mode 100644 index 0000000..3cd8999 --- /dev/null +++ b/vic-class11-assignment/routes/icecream_routes.js @@ -0,0 +1,43 @@ +'use strict'; + +const express = require('express'); +const bodyParser = require('body-parser'); +const jsonParser = bodyParser.json(); +const IceCream = require('../model/icecream'); +const jwt = require('../lib/jwt_auth'); + +const iceCreamRouter = module.exports = exports = express.Router(); + +iceCreamRouter.get('/', (req, res, next) => { + IceCream.find({}, (err, icecream) => { + console.log('not getting', icecream); + if(err) return next(err); + res.json(icecream); + }); +}); + +iceCreamRouter.post('/', jwt, jsonParser, (req, res, next) => { + let newIceCream = new IceCream(req.body); + newIceCream.save((err, icecream) => { + if(err) return next(err); + res.json(icecream); + }); +}); + +iceCreamRouter.put('/:id', jwt, jsonParser, (req, res, next) => { + let _id = req.params.id; + IceCream.findOneAndUpdate({_id}, req.body, (err) => { + if(err) return next(err); + let message = 'successfully updated'; + res.json({message}); + }); +}); + +iceCreamRouter.delete('/:id', jwt, (req, res, next) => { + let _id = req.params.id; + IceCream.findOneAndRemove({_id}, (err) => { + if(err) return next(err); + let message = 'successfully deleted'; + res.json({message}); + }); +}); diff --git a/vic-class11-assignment/routes/milkshake_routes.js b/vic-class11-assignment/routes/milkshake_routes.js new file mode 100644 index 0000000..aeeb600 --- /dev/null +++ b/vic-class11-assignment/routes/milkshake_routes.js @@ -0,0 +1,43 @@ +'use strict'; + +const express = require('express'); +const bodyParser = require('body-parser'); +const jsonParser = bodyParser.json(); +const MilkShake = require('../model/milkshake'); +const jwt = require('../lib/jwt_auth'); + +const milkShakeRouter = module.exports = exports = express.Router(); + +milkShakeRouter.get('/', (req, res, next) => { + MilkShake.find({}, (err, milkshakes) => { + console.log('not getting', milkshakes); + if(err) return next(err); + res.json(milkshakes); + }); +}); + +milkShakeRouter.post('/', jwt, jsonParser, (req, res, next) => { + let newMilkshake = new MilkShake(req.body); + newMilkshake.save((err, milkshake) => { + if(err) return next(err); + res.json(milkshake); + }); +}); + +milkShakeRouter.put('/:id', jwt, jsonParser, (req, res, next) => { + let _id = req.params.id; + MilkShake.findOneAndUpdate({_id}, req.body, (err) => { + if(err) return next(err); + let message = 'successfully updated'; + res.json({message}); + }); +}); + +milkShakeRouter.delete('/:id', jwt, (req, res, next) => { + let _id = req.params.id; + MilkShake.findOneAndRemove({_id}, (err) => { + if(err) return next(err); + let message = 'successfully deleted'; + res.json({message}); + }); +}); diff --git a/vic-class11-assignment/server.js b/vic-class11-assignment/server.js new file mode 100644 index 0000000..e92c428 --- /dev/null +++ b/vic-class11-assignment/server.js @@ -0,0 +1,34 @@ +'use strict'; + +const express = require('express'); +const app = express(); +const mongoose = require('mongoose'); +const morgan = require('morgan'); +const errorHandler = require('./lib/error_handling'); +const cors = require('cors'); + +const dbPort = process.env.MONGOLAB_URI || 'mongodb://localhost/dev_db'; + +mongoose.connect(dbPort); + +const authRouter = require('./routes/auth_routes'); +const iceCreamRouter = require('./routes/icecream_routes'); +const milkShakeRouter = require('./routes/milkshake_routes'); + +app.use(cors()); + +app.use(morgan('dev')); +app.use('/', authRouter); +app.use('/icecream', iceCreamRouter); +app.use('/milkshake', milkShakeRouter); + +app.use(errorHandler); + +app.use((req, res) => { + res.status(404).json({message: 'not found'}); +}); + + +app.listen(3000, () => { + console.log('up on 3000'); +}); diff --git a/vic-class11-assignment/test/milkshake_test.js b/vic-class11-assignment/test/milkshake_test.js new file mode 100644 index 0000000..932f29f --- /dev/null +++ b/vic-class11-assignment/test/milkshake_test.js @@ -0,0 +1,91 @@ +'use strict'; + +const chai = require('chai'); +const chaiHTTP = require('chai-http'); +const mongoose = require('mongoose'); +const MilkShake = require('../model/milkshake.js'); +chai.use(chaiHTTP); + +const expect = chai.expect; +const request = chai.request; +const dbPort = process.env.MONGOLAB_URI; + +process.env.MONGOLAB_URI = 'mongodb://localhost/milkshake_test_db'; +require('../index'); + +describe('Testing CRUD routes MilkShake', () => { + after((done) => { + process.env.MONGOLAB_URI = dbPort; + mongoose.connection.db.dropDatabase(() => { + done(); + }); + }); + + it('should respond with 404 to a bad path', (done) => { + request('localhost:3000') + .get('/badpath') + .end((err, res) => { + expect(err).to.not.eql(null); + expect(res).to.have.status(404); + expect(res.text).to.eql('{"message":"not found"}'); + done(); + }); + }); + it('should get a list of milkshakes', (done) => { + request('localhost:3000') + .get('/milkshake/') + .end((err, res) => { + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(Array.isArray(res.body)).to.eql(true); + done(); + }); + }); + it('should create a milkshake', (done) => { + request('localhost:3000') + .post('/milkshake') + .send({flavor: 'strawberry', scoops: 3, milkRichness: 'whole milk'}) + .end((err, res) => { + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.flavor).to.eql('strawberry'); + expect(res.body.scoops).to.eql(3); + done(); + }); + }); + + describe('tests that need an existing milkshake', () => { + let testMilkShake; + beforeEach((done) => { + let newMilkShake = new MilkShake({flavor: 'peanut butter', scoops: 4, milkRichness: '2percent milk'}); + newMilkShake.save((err, milkshake) => { + testMilkShake = milkshake; + done(); + }); + }); + + it('should update a message', (done) => { + testMilkShake.scoops = 2; + request('localhost:3000') + .put('/milkshake/') + .send(testMilkShake) + .end((err, res) => { + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.message).to.eql('successfully updated'); + done(); + }); + }); + + it('should get rid of perfectly good milkshakes', (done) => { + request('localhost:3000') + .delete('/milkshake/' + testMilkShake._id) + .end((err, res) => { + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.message).to.eql('successfully deleted'); + done(); + }); + }); + }); +}); diff --git a/vic-class11-assignment/test/test.js b/vic-class11-assignment/test/test.js new file mode 100644 index 0000000..fc6e37f --- /dev/null +++ b/vic-class11-assignment/test/test.js @@ -0,0 +1,91 @@ +'use strict'; + +const chai = require('chai'); +const chaiHTTP = require('chai-http'); +const mongoose = require('mongoose'); +const IceCream = require('../model/icecream'); +chai.use(chaiHTTP); + +const expect = chai.expect; +const request = chai.request; +const dbPort = process.env.MONGOLAB_URI; + +process.env.MONGOLAB_URI = 'mongodb://localhost/test_db'; +require('../index'); + +describe('Testing CRUD routes IceCream', () => { + after((done) => { + process.env.MONGOLAB_URI = dbPort; + mongoose.connection.db.dropDatabase(() => { + done(); + }); + }); + + it('should respond with 404 to bad path', (done) => { + request('localhost:3000') + .get('/wrongway') + .end((err, res) => { + expect(err).to.not.eql(null); + expect(res).to.have.status(404); + expect(res.text).to.eql('{"message":"not found"}'); + done(); + }); + }); + it('should get a list of ice cream', (done) => { + request('localhost:3000') + .get('/icecream/') + .end((err, res) => { + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(Array.isArray(res.body)).to.eql(true); + done(); + }); + }); + it('should create ice cream', (done) => { + request('localhost:3000') + .post('/icecream/') + .send({flavor: 'chocolate', scoops: 1, vessel: 'cup'}) + .end((err, res) => { + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.flavor).to.eql('chocolate'); + expect(res.body.vessel).to.eql('cup'); + done(); + }); + }); + + describe('tests that need ice cream already', () => { + let testIceCream; + beforeEach((done) => { + let newIceCream = new IceCream({flavor: 'vanilla', scoops: 2, vessel: 'waffle cone'}); + newIceCream.save((err, icecream) => { + testIceCream = icecream; + done(); + }); + }); + + it('should update a message', (done) => { + testIceCream.flavor = 'coffee'; + request('localhost:3000') + .put('/icecream/') + .send(testIceCream) + .end((err, res) => { + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.message).to.eql('successfully updated'); + done(); + }); + }); + + it('should get rid of perfectly good ice cream', (done) => { + request('localhost:3000') + .delete('/icecream/' + testIceCream._id) + .end((err, res) => { + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.message).to.eql('successfully deleted'); + done(); + }); + }); + }); +});