diff --git a/dan-stineback/.eslintignore b/dan-stineback/.eslintignore new file mode 100644 index 0000000..0314ab3 --- /dev/null +++ b/dan-stineback/.eslintignore @@ -0,0 +1,6 @@ +**/node_modules/* +**/vendor/* +**/*.min.js +/*.md +/package.json +/npm-debug.log diff --git a/dan-stineback/.eslintrc b/dan-stineback/.eslintrc new file mode 100644 index 0000000..6d505ad --- /dev/null +++ b/dan-stineback/.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/dan-stineback/.gitignore b/dan-stineback/.gitignore index 3c3629e..4c497d6 100644 --- a/dan-stineback/.gitignore +++ b/dan-stineback/.gitignore @@ -1 +1,2 @@ -node_modules +/node_modules +/db diff --git a/dan-stineback/gulpfile.js b/dan-stineback/gulpfile.js index 8efd445..02f092e 100644 --- a/dan-stineback/gulpfile.js +++ b/dan-stineback/gulpfile.js @@ -2,7 +2,6 @@ const gulp = require('gulp'); const eslint = require('gulp-eslint'); const mocha = require('gulp-mocha'); -const watch = require('gulp-watch'); gulp.task('default', ['lint', 'test'], () => { console.log('started'); diff --git a/dan-stineback/index.js b/dan-stineback/index.js deleted file mode 100644 index 3847531..0000000 --- a/dan-stineback/index.js +++ /dev/null @@ -1,3 +0,0 @@ -'use strict'; - -module.exports = require(__dirname + '/lib/server.js'); diff --git a/dan-stineback/lib/basic-http.js b/dan-stineback/lib/basic-http.js new file mode 100644 index 0000000..2ac4a7f --- /dev/null +++ b/dan-stineback/lib/basic-http.js @@ -0,0 +1,18 @@ +'use strict'; + +module.exports = function(req, res, next) { + let basicAuth = req.headers.authorization; + let authString = basicAuth.split(' ').pop(); + 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/dan-stineback/lib/jwt_auth.js b/dan-stineback/lib/jwt_auth.js new file mode 100644 index 0000000..e3fd8db --- /dev/null +++ b/dan-stineback/lib/jwt_auth.js @@ -0,0 +1,27 @@ +'use strict'; + +const jwt = require('jsonwebtoken'); +const User = require('../model/user'); +const secret = process.env.SECRET || 'changeme'; + +module.exports = function(req, res, next) { + + + let token = req.headers.token || req.body.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/dan-stineback/lib/server.js b/dan-stineback/lib/server.js deleted file mode 100644 index 95d26ea..0000000 --- a/dan-stineback/lib/server.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -const express = require('express'); -const router = require(__dirname + ('/server')); -const app = express(); -const bodyParser = require('body-parser'); -const jsonParser = bodyParser.json(); - - -app.use(jsonParser); - - -app.get('/', (req, res) => { - console.log('you got get'); - res.send('Mission accomplished. Time for a beer. '); -}); - -app.get('/zoots', (req, res) => { - console.log('Zoots fool. '); - res.send('Zoots Zoots. '); -}); - -app.post('/zoots', (req, res) => { - console.log('Hey Mr. Post-man. '); - res.json({message: 'This is some sweet sweet JSON. '}); -}); - -app.put('/zoots', (req, res) => { - console.log('Put your hands up! '); - res.json({message: 'Party Under Trees JSON '}); -}); - -app.patch('/zoots', (req, res) => { - console.log('Patchy patchface. '); - res.json({message: 'Just a small fix JSON'}); -}); - -app.delete('/zoots', (req, res) => { - console.log('Seek and destroy! '); - res.json({message: 'Nothing to see here folks'}); -}); - -app.get('/*', (req, res) => { - res.status(404).json({message: 'not found'}); -}); -app.listen(3000, () => console.log('server is up on 3000')); diff --git a/dan-stineback/model/cats.js b/dan-stineback/model/cats.js new file mode 100644 index 0000000..470b647 --- /dev/null +++ b/dan-stineback/model/cats.js @@ -0,0 +1,10 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const Cat = new mongoose.Schema({ + name: String, + age: Number +}); + +module.exports = mongoose.model('cat', Cat); diff --git a/dan-stineback/model/dogs.js b/dan-stineback/model/dogs.js new file mode 100644 index 0000000..9fdf25f --- /dev/null +++ b/dan-stineback/model/dogs.js @@ -0,0 +1,10 @@ +'use strict'; + +const mongoose = require('mongoose'); + +const Dog = new mongoose.Schema({ + name: String, + age: Number +}); + +module.exports = mongoose.model('dog', Dog); diff --git a/dan-stineback/model/user.js b/dan-stineback/model/user.js new file mode 100644 index 0000000..8f97cef --- /dev/null +++ b/dan-stineback/model/user.js @@ -0,0 +1,25 @@ +'use strict'; + +const mongoose = require('mongoose'); +const bcrypt = require('bcrypt'); +const secret = process.env.SECRET || 'changeme'; +const jwt = require('jsonwebtoken'); + +const User = new mongoose.Schema({ + username: {type: String, required: true}, + password: {type: String, required: true} +}); + +User.methods.hashedPassword = 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/dan-stineback/package.json b/dan-stineback/package.json index 9db7bb7..591142b 100644 --- a/dan-stineback/package.json +++ b/dan-stineback/package.json @@ -2,12 +2,14 @@ "name": "dan-stineback", "version": "1.0.0", "description": "rest api project", - "main": "index.js", + "main": "server.js", "directories": { "test": "test" }, "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "./node_modules/mocha/bin/mocha", + "start": "node server.js", + "lint": "./node_modules/eslint/bin/eslint.js *" }, "repository": { "type": "git", @@ -20,7 +22,12 @@ }, "homepage": "https://github.com/dstineback/rest_api#readme", "dependencies": { - "body-parser": "^1.15.1" + "bcrypt": "^0.8.6", + "body-parser": "^1.15.1", + "cors": "^2.7.1", + "eslint": "^2.11.1", + "jsonwebtoken": "^7.0.0", + "morgan": "^1.7.0" }, "devDependencies": { "chai": "^3.5.0", @@ -28,7 +35,7 @@ "gulp": "^3.9.1", "gulp-eslint": "^2.0.0", "gulp-mocha": "^2.2.0", - "gulp-watch": "^4.3.6", - "mocha": "^2.5.1" + "mocha": "^2.5.1", + "mongoose": "^4.4.19" } } diff --git a/dan-stineback/routes/cat-route.js b/dan-stineback/routes/cat-route.js new file mode 100644 index 0000000..8850da7 --- /dev/null +++ b/dan-stineback/routes/cat-route.js @@ -0,0 +1,40 @@ +'use strict'; +const express = require('express'); +const Cat = require('../model/cats'); +const bodyParser = require('body-parser').json(); +const jwtAuth = require('../lib/jwt_auth'); + + +const catRouter = module.exports = exports = express.Router(); + + +catRouter.get('/', (req, res, next) => { + Cat.find({}, (err, cat) => { + if(err) return next(err); + res.json(cat); + }); +}); + +catRouter.post('/', bodyParser, jwtAuth, (req, res, next) => { + let newCat = new Cat(req.body); + newCat.save((err, cat)=> { + if (err) return next(err); + res.json(cat); + }); +}); + +catRouter.put('/', bodyParser, jwtAuth, (req, res, next) => { + let _id = req.body._id; + Cat.findOneAndUpdate({_id}, req.body, (err, cat) =>{ + if (err) return next(err); + res.json({message:'successfully updated', data: cat}); + }); +}); + +catRouter.delete('/:id', jwtAuth, (req, res, next) => { + let _id = req.params.id; + Cat.findOneAndRemove({_id}, null, (err, cat) => { + if (err) return next(err); + res.json({message: 'successfully deleted', data: cat}); + }); +}); diff --git a/dan-stineback/routes/dog-route.js b/dan-stineback/routes/dog-route.js new file mode 100644 index 0000000..904832f --- /dev/null +++ b/dan-stineback/routes/dog-route.js @@ -0,0 +1,39 @@ +'use strict'; +const express = require('express'); +const Dog = require('../model/dogs'); +const bodyParser = require('body-parser').json(); +const jwtAuth = require('../lib/jwt_auth'); + +const dogRouter = module.exports = exports = express.Router(); + + +dogRouter.get('/', (req, res, next) => { + Dog.find({}, (err, dog) => { + if(err) return next(err); + res.json(dog); + }); +}); + +dogRouter.post('/', bodyParser, jwtAuth, (req, res, next) => { + let newDog = new Dog(req.body); + newDog.save((err, dog)=> { + if (err) return next(err); + res.json(dog); + }); +}); + +dogRouter.put('/', bodyParser, jwtAuth, (req, res, next) => { + let _id = req.body._id; + Dog.findOneAndUpdate({_id}, req.body, (err, dog) =>{ + if (err) return next(err); + res.json({message: 'successfully updated', data: dog}); + }); +}); + +dogRouter.delete('/:id', jwtAuth, (req, res, next) => { + let _id = req.params.id; + Dog.findOneAndRemove({_id}, null, (err, dog) => { + if (err) return next(err); + res.json({message: 'successfully deleted', data: dog}); + }); +}); diff --git a/dan-stineback/routes/router.js b/dan-stineback/routes/router.js new file mode 100644 index 0000000..c6cc9b6 --- /dev/null +++ b/dan-stineback/routes/router.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 router = module.exports = exports = express.Router(); + +router.post('/signup', bodyParser, (req, res, next) => { + let newUser = new User(req.body); + let hashedPassword = newUser.hashedPassword(); + newUser.password = hashedPassword; + req.body.password = null; + User.findOne({username: req.body.username}, (err, user) => { + if (err || user) return next(new Error('User name already used')); + newUser.save((err, user) => { + if (err) return next(new Error('Could not save user')); + res.json({message: req.body.username + ' We mad a token for you', token: user.generateToken()}); + }); + }); +}); + +router.post('/signin', basicHTTP, (req, res, next) => { + User.findOne({username: req.auth.username}, (err, user) => { + if (err || !user) return next(new Error('Could not sign in')); + if (!user.comparePassword(req.auth.password)) return next(new Error('wrong password')); + + res.json({message: req.auth.username + ' Here is your token', token: user.generateToken()}); + }); +}); diff --git a/dan-stineback/server.js b/dan-stineback/server.js new file mode 100644 index 0000000..e915913 --- /dev/null +++ b/dan-stineback/server.js @@ -0,0 +1,31 @@ +'use strict'; + +const express = require('express'); +const app = express(); +const mongoose = require('mongoose'); +const morgan = require('morgan'); +const cors = require('cors'); + + +const dbPort = process.env.MONGODB_URI || 'mongodb://localhost/dev_db'; +mongoose.connect(dbPort); + +const dogRouter = require(__dirname + '/routes/dog-route'); +const catRouter = require(__dirname + '/routes/cat-route'); +const authRouter = require(__dirname + '/routes/router'); + +app.use(cors()); +app.use(morgan('dev')); +app.use('/dogs', dogRouter); +app.use('/cats', catRouter); +app.use('/', authRouter); + +app.use((err, req, res, next) => { + res.status(500).json({message: err.message}); + next(err); +}); + +app.use((req, res)=> { + res.status(404).json({message: 'not found'}); +}); +app.listen(3000, () => console.log('server is up on 3000')); diff --git a/dan-stineback/test/test.js b/dan-stineback/test/test.js index 4f3f681..ae5d9d0 100644 --- a/dan-stineback/test/test.js +++ b/dan-stineback/test/test.js @@ -1,83 +1,189 @@ 'use strict'; -const fs = require('fs'); -const express = require('express'); const chai = require('chai'); const chaiHTTP = require('chai-http'); -const expect = chai.expect; +const Cat = require('../model/cats'); +const Dog = require('../model/dogs'); +const basic = require('../lib/basic-http'); + +const mongoose = require('mongoose'); chai.use(chaiHTTP); + +const expect = chai.expect; const request = chai.request; -const server = require(__dirname + '/../lib/server'); -const app = express(); -const bodyParser = require('body-parser'); -const jsonParser = bodyParser.json(); -const myRouter = express.Router(); +const dbPort = process.env.MONGODB_URI; -app.use(jsonParser); -require('../index'); +process.env.MONGODB_URI = 'mongodb://localhost/test_db'; +require('../server'); -describe('testing path', () => { - it('should return GET message', (done) => { - request('localhost:3000') - .get('/zoots') - .end((err, res) => { - expect(err).to.eql(null); - expect(res).to.have.status(200); - expect(res.text).to.eql('Zoots Zoots. '); + +describe('Cat test', () => { + after((done)=> { + process.env.MONGODB_URI = dbPort; + mongoose.connection.db.dropDatabase(()=>{ done(); }); }); - it('should write POST mesage', (done) => { + it('should get a list of cats', (done) => { request('localhost:3000') - .post('/zoots') - .send({}) - .end((err, res) => { - expect(err).to.eql(null); - expect(res).to.have.status(200); - expect(res.text).to.eql(('{"message":"This is some sweet sweet JSON. "}')); - done(); - }); + .get('/cats/') + .end((err, res)=>{ + expect(err).to.eql(null); + expect(Array.isArray(res.body)).to.eql(true); + done(); + }); }); - it('should write PUT message', (done) => { + it('should post a cats', (done) => { + let vic = {name: 'Vic', size: 'large'}; request('localhost:3000') - .put('/zoots') - .send({}) - .end((err, res) => { - expect(err).to.eql(null); - expect(res).to.have.status(200); - expect(res.text).to.eql('{"message":"Party Under Trees JSON "}'); + .post('/cats/') + .send(vic) + .end((err, res)=>{ + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.name).to.eql('Vic'); + expect(res.body).to.have.property('_id'); + done(); + }); + }); + + + describe('test that need data', ()=> { + let testCat; + beforeEach((done)=> { + let newCat = new Cat({name: 'test', size: 'large'}); + newCat.save((err, Cat)=> { + testCat = Cat; + done(); + }); + }); + it('should return a updated cats', (done) => { + testCat.name = 'updated'; + request('localhost:3000') + .put('/cats/') + .send(testCat) + .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 delete a cat', (done)=>{ + request('localhost:3000') + .delete('/cats/' + testCat._id) + .end((err, res)=>{ + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.message).to.eql('successfully deleted'); + done(); + }); + }); + }); +}); + +describe('Dog test', () => { + after((done)=> { + process.env.MONGODB_URI = dbPort; + mongoose.connection.db.dropDatabase(()=>{ done(); }); }); - it('should write PATCH message', (done) => { + it('should get a list of dogs', (done) => { request('localhost:3000') - .patch('/zoots') - .send({}) - .end((err, res) => { - expect(err).to.eql(null); - expect(res).to.have.status(200); - expect(res.text).to.eql('{"message":"Just a small fix JSON"}'); + .get('/dogs/') + .end((err, res)=>{ + expect(err).to.eql(null); + expect(Array.isArray(res.body)).to.eql(true); + done(); + }); + }); + it('should post a dogs', (done) => { + let vic = {name: 'Vic', size: 'large'}; + request('localhost:3000') + .post('/dogs/') + .send(vic) + .end((err, res)=>{ + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.name).to.eql('Vic'); + expect(res.body).to.have.property('_id'); + done(); + }); + }); + + describe('test that need data', ()=> { + let testDog; + beforeEach((done)=> { + let newDog = new Dog({name: 'test', size: 'large'}); + newDog.save((err, Dog)=> { + testDog = Dog; + done(); + }); + }); + it('should return a updated dogs', (done) => { + testDog.name = 'updated'; + request('localhost:3000') + .put('/dogs/') + .send(testDog) + .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 delete a dog', (done)=>{ + request('localhost:3000') + .delete('/cats/' + testDog._id) + .end((err, res)=>{ + expect(err).to.eql(null); + expect(res).to.have.status(200); + expect(res.body.message).to.eql('successfully deleted'); + done(); + }); + }); + }); +}); + +describe('unit tests for auth', () => { + it('should auth a user', () => { + let baseString = new Buffer('vic:password').toString('base64'); + let authString = 'Basic ' + baseString; + let req = {headers:{authorization: authString}}; + basic(req, {}, () => { + expect(req.auth).to.eql({username: 'vic', password: 'password'}); + }); + }); +}); + +describe('signin tests', () => { + after((done)=> { + process.env.MONGODB_URI = dbPort; + mongoose.connection.db.dropDatabase(() => { done(); }); }); - it('should test DELETE and write new message', (done) => { + it('should have a token', (done) => { request('localhost:3000') - .delete('/zoots') - .send({}) - .end((err, res) => { + .post('/signup') + .send({username:'vic', password:'vic'}) + .end((err,res) => { expect(err).to.eql(null); - expect(res).to.have.status(200); - expect(res.text).to.eql('{"message":"Nothing to see here folks"}') + expect(res.body).to.have.property('token'); done(); }); - }) - it('should respond with 404 not found', (done) => { + }); +}); + +describe('catch error test', () => { + it('should give an error for unsupported routes', (done) => { request('localhost:3000') .get('/*') .end((err, res) => { expect(err).to.not.eql(null); expect(res).to.have.status(404); - expect(res.text).to.eql('{"message":"not found"}'); + expect(res.body).to.eql({message: 'not found'}); done(); }); });