From c9dc0e4b36c47c4b5f4ef510fe929c2c988ed127 Mon Sep 17 00:00:00 2001 From: Zachary Crumbo Date: Mon, 6 Mar 2017 22:04:42 -0800 Subject: [PATCH 1/8] all files --- lab-zachary/.eslintrc | 21 ++++ lab-zachary/.gitignore | 129 +++++++++++++++++++++++ lab-zachary/README.md | 64 +++++++++++ lab-zachary/gulpfile.js | 23 ++++ lab-zachary/lib/basic-auth-middleware.js | 29 +++++ lab-zachary/lib/error-middleware.js | 31 ++++++ lab-zachary/model/user.js | 74 +++++++++++++ lab-zachary/package.json | 34 ++++++ lab-zachary/route/auth-router.js | 46 ++++++++ lab-zachary/server.js | 25 +++++ lab-zachary/test/user-auth-test.js | 122 +++++++++++++++++++++ 11 files changed, 598 insertions(+) create mode 100644 lab-zachary/.eslintrc create mode 100644 lab-zachary/.gitignore create mode 100644 lab-zachary/README.md create mode 100644 lab-zachary/gulpfile.js create mode 100644 lab-zachary/lib/basic-auth-middleware.js create mode 100644 lab-zachary/lib/error-middleware.js create mode 100644 lab-zachary/model/user.js create mode 100644 lab-zachary/package.json create mode 100644 lab-zachary/route/auth-router.js create mode 100644 lab-zachary/server.js create mode 100644 lab-zachary/test/user-auth-test.js diff --git a/lab-zachary/.eslintrc b/lab-zachary/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/lab-zachary/.eslintrc @@ -0,0 +1,21 @@ +{ + "rules": { + "no-console": "off", + "indent": [ "error", 2 ], + "quotes": [ "error", "single" ], + "semi": ["error", "always"], + "linebreak-style": [ "error", "unix" ] + }, + "env": { + "es6": true, + "node": true, + "mocha": true, + "jasmine": true + }, + "ecmaFeatures": { + "modules": true, + "experimentalObjectRestSpread": true, + "impliedStrict": true + }, + "extends": "eslint:recommended" +} diff --git a/lab-zachary/.gitignore b/lab-zachary/.gitignore new file mode 100644 index 0000000..89ab13a --- /dev/null +++ b/lab-zachary/.gitignore @@ -0,0 +1,129 @@ + +# Created by https://www.gitignore.io/api/node,osx,windows,linux + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### Node ### +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# Runtime data +pids +*.pid +*.seed +*.pid.lock + +# Directory for instrumented libs generated by jscoverage/JSCover +lib-cov + +# Coverage directory used by tools like istanbul +coverage + +# nyc test coverage +.nyc_output + +# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) +.grunt + +# Bower dependency directory (https://bower.io/) +bower_components + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules/ +jspm_packages/ + +# Typescript v1 declaration files +typings/ + +# Optional npm cache directory +.npm + +# Optional eslint cache +.eslintcache + +# Optional REPL history +.node_repl_history + +# Output of 'npm pack' +*.tgz + +# Yarn Integrity file +.yarn-integrity + +# dotenv environment variables file +.env + + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + +# End of https://www.gitignore.io/api/node,osx,windows,linux \ No newline at end of file diff --git a/lab-zachary/README.md b/lab-zachary/README.md new file mode 100644 index 0000000..7519e2a --- /dev/null +++ b/lab-zachary/README.md @@ -0,0 +1,64 @@ +# Basic Auth with an Express API + +This app implements Basic Auth with API endpoints to allow for user signup and sign in. + +# System Requirements + + - Terminal.app on macOS or equivalent + - node.js and npm package manager installed + + +### Installation + +Clone the repository to your local server +```sh +https://github.com/zcrumbo/16-basic_auth/tree/day-one +``` + +Install the dependencies - + +```sh +$ npm i +``` + +[HTTPie](https://httpie.org/) will be required to run the HTTP requests from your terminal window. You will need to install this with [Homebrew][1] on macOS. It is also easier to see the results of all operations by running mocha tests with the command +```sh +$ mocha +``` +or use the npm script to run all tests with debug +```sh +$ npm test +``` +Start the server with debug + +```sh +$ npm start +``` +If you want to use the debug and nodemon modules, run the npm script: +``` +npm start +``` + +### Connecting + +If you are using HTTPie, in your terminal window, type the following commands, where '3000' would be replaced with your local environment PORT variable, if configured. Commands can be sent to the api/signin and the api/signup endpoints. + + +```sh +$ http POST localhost:3000/api/signup/ username='testname' password='password' email='email@email.com' #signs up for the api and returns a unqique token that must be used in future api calls + +$ http GET -a username:password localhost:3000/api/signin #signs into the API + +``` + +Sending the following requests to the server will have the results below: + + * `GET, POST`: 404 response with 'not found' for unregistered endpoints +* `GET`: 401 response with a bad credentials + * `GET`: 200 response with a proper signin + * `POST`: 400 response with 'bad request' if no request body was provided or the body was invalid + * `POST`: 200 response with the body content for a post request with a valid body + + +[1]:https://brew.sh/ + diff --git a/lab-zachary/gulpfile.js b/lab-zachary/gulpfile.js new file mode 100644 index 0000000..4c40ff6 --- /dev/null +++ b/lab-zachary/gulpfile.js @@ -0,0 +1,23 @@ +'use strict'; + +const gulp = require('gulp'); +const eslint = require('gulp-eslint'); +const mocha = require ('gulp-mocha'); + +gulp.task('test', function(){ + gulp.src('./test/*-test.js', { read: false}) + .pipe(mocha({ reporter: 'spec'})); +}); + +gulp.task('lint', function() { + return gulp.src(['**/*.js', '!node_modules']) + .pipe(eslint()) + .pipe(eslint.format()) + .pipe(eslint.failAfterError()); +}); + +gulp.task('dev', function(){ + gulp.watch(['**/*.js', '!node_modules/**'], ['lint']); +}); + +gulp.task('default', ['dev', 'lint', 'test']); diff --git a/lab-zachary/lib/basic-auth-middleware.js b/lab-zachary/lib/basic-auth-middleware.js new file mode 100644 index 0000000..2949fe1 --- /dev/null +++ b/lab-zachary/lib/basic-auth-middleware.js @@ -0,0 +1,29 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('cf-gram:basic-auth-middleware'); + +module.exports = function(req, res, next) { + debug('basic auth'); + + var authHeader = req.headers.authorization; + if(!authHeader) return next(createError(401, 'auth header required')); + + var base64str = authHeader.split('Basic ')[1]; + if (!base64str) return next(createError(401, 'username and password required')); + + var utf8str = new Buffer(base64str, 'base64').toString(); //one method to decode auth header + //var utf8str = base64str.toString(); + + var authArr = utf8str.split(':'); + + req.auth = { + username: authArr[0], + password: authArr[1] + }; + + if(!req.auth.username) return next(createError(401, 'username required')); + if(!req.auth.password) return next(createError(401, 'password required')); + + next(); +}; diff --git a/lab-zachary/lib/error-middleware.js b/lab-zachary/lib/error-middleware.js new file mode 100644 index 0000000..70d35b1 --- /dev/null +++ b/lab-zachary/lib/error-middleware.js @@ -0,0 +1,31 @@ +'use strict'; + +const createError = require('http-errors'); +const debug = require('debug')('cf-gram:error-middleware'); + +module.exports = function(err, req, res, next){ + debug('error-middleware'); + + if(err.status) { + debug('user error'); + + console.error('message:', err.message); + console.error('name:', err.name); + + res.status(err.status).send(err.message); + next(); + return; + } + + if (err.message === 'ValidationError'){ + err = createError(400, err.message); + res.status(err.status).send(err.message); + next(); + return; + } + + err = createError(500, 'Internal Server Error'); + res.status(err.status).send(err.message); + next(); + return; +}; diff --git a/lab-zachary/model/user.js b/lab-zachary/model/user.js new file mode 100644 index 0000000..8dabbaa --- /dev/null +++ b/lab-zachary/model/user.js @@ -0,0 +1,74 @@ +'use strict'; + +const mongoose = require('mongoose'); +const crypto = require('crypto'); +const debug = require('debug')('cf-gram:user'); +const bcrypt = require('bcrypt'); +const createError = require('http-errors'); +const jwt = require('jsonwebtoken'); + +const Schema = mongoose.Schema; + +const userSchema = Schema ({ + username: {type: String, required: true, unique: true}, + email: {type: String, required: true, unique: true}, + password: {type: String, required: true}, + findHash: {type: String, unique: true} +}); + +userSchema.methods.createPasswordHash = function(password){ + debug('createPasswordHash'); + + return new Promise((resolve, reject) => { + bcrypt.hash(password, 10, (err, hash) => { + if (err) return reject(err); + this.password = hash; + resolve(this); + }); + }); +}; + + +userSchema.methods.comparePasswordHash = function(password){ + debug('comparePasswordHash'); + + return new Promise((resolve, reject) => { + bcrypt.compare(password, this.password, (err, valid) => { + if (err) return reject(err); + if (!valid) return reject(createError(401, 'invalid password!')); + resolve(this); + }); + }); +};// + + +userSchema.methods.generateFindHash = function(){ + return new Promise((resolve, reject) => { + let tries = 0; + + _generateFindHash.call(this); + + function _generateFindHash() { + this.findHash = crypto.randomBytes(32).toString('hex'); + this.save() + .then( () => resolve(this.findHash)) + .catch( err => { + if (tries>3) return reject(err); + tries++; + _generateFindHash.call(this); + }); + } + }); +}; + +userSchema.methods.generateToken = function(){ + debug('generateToken'); + return new Promise((resolve, reject) => { + this.generateFindHash() + .then( findHash => resolve(jwt.sign({token: findHash}, process.env.APP_SECRET))) + .catch( err => reject(err)); + }); +}; + + +module.exports = mongoose.model('users', userSchema); diff --git a/lab-zachary/package.json b/lab-zachary/package.json new file mode 100644 index 0000000..b3bad6f --- /dev/null +++ b/lab-zachary/package.json @@ -0,0 +1,34 @@ +{ + "name": "lab-zachary", + "version": "1.0.0", + "description": "", + "main": "gulpfile.js", + "scripts": { + "test": "DEBUG='cf-gram*' mocha", + "start": "DEBUG='cf-gram*' nodemon server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcrypt": "^1.0.2", + "bluebird": "^3.5.0", + "body-parser": "^1.17.1", + "cors": "^2.8.1", + "debug": "^2.6.1", + "dotenv": "^4.0.0", + "express": "^4.15.2", + "http-errors": "^1.6.1", + "jsonwebtoken": "^7.3.0", + "mongoose": "^4.8.6", + "morgan": "^1.8.1" + }, + "devDependencies": { + "chai": "^3.5.0", + "gulp": "^3.9.1", + "gulp-eslint": "^3.0.1", + "gulp-mocha": "^4.0.1", + "mocha": "^3.2.0", + "superagent": "^3.5.0" + } +} diff --git a/lab-zachary/route/auth-router.js b/lab-zachary/route/auth-router.js new file mode 100644 index 0000000..e17348b --- /dev/null +++ b/lab-zachary/route/auth-router.js @@ -0,0 +1,46 @@ +'use strict'; + +const Router = require('express').Router; +const jsonParser = require('body-parser').json(); +const User = require('../model/user.js'); +const createError = require('http-errors'); +const debug = require('debug')('cf-gram:auth-router'); + +const basicAuth = require('../lib/basic-auth-middleware'); + +const authRouter = module.exports = Router(); + +authRouter.get('/', (req, res) => { + res.write('you made a request'); + res.end(); +}); + +authRouter.post('/api/signup', jsonParser, (req, res, next) => { + debug('POST: /api/signup'); + + if(!req.body.username) return next(createError(400, 'username required')); + if(!req.body.email) return next(createError(400, 'email address required')); + if(!req.body.password) return next(createError(400, 'password required')); + + let password = req.body.password; + delete req.body.password; + + let user = new User(req.body); + + user.createPasswordHash(password) + .then(user => user.save()) + .then(user => user.generateToken()) + .then(token => res.send(token)) + .catch(() => next(createError(500, 'user not saved'))); + +}); + +authRouter.get('/api/signin', basicAuth, (req, res, next) => { + debug('GET: /api/signin'); + + User.findOne({username: req.auth.username}) + .then( user => user ? user.comparePasswordHash(req.auth.password) : next(createError(401, 'user not found!'))) + .then( user => user.generateToken()) + .then( token => res.send(token)) + .catch(next); +}); \ No newline at end of file diff --git a/lab-zachary/server.js b/lab-zachary/server.js new file mode 100644 index 0000000..e99361a --- /dev/null +++ b/lab-zachary/server.js @@ -0,0 +1,25 @@ +'use strict'; + +const express = require('express'); +const morgan = require('morgan'); +const cors = require('cors'); +const debug = require('debug')('cf-gram:server'); +const dotenv = require('dotenv'); +const mongoose = require('mongoose'); + +const authRouter = require('./route/auth-router.js'); +const errors = require('./lib/error-middleware.js'); + +const PORT = process.env.PORT || 3000; +const app = express(); +dotenv.load(); + +mongoose.connect(process.env.MONGODB_URI); + +app.use(cors()); +app.use(morgan('dev')); +app.use(authRouter); +app.use(errors); + + +app.listen(PORT, () => debug('server up:', PORT)); \ No newline at end of file diff --git a/lab-zachary/test/user-auth-test.js b/lab-zachary/test/user-auth-test.js new file mode 100644 index 0000000..e1b512d --- /dev/null +++ b/lab-zachary/test/user-auth-test.js @@ -0,0 +1,122 @@ +'use strict'; + +const request = require('superagent'); +const expect = require('chai').expect; +const User = require('../model/user.js'); + +const url = `localhost:${process.env.PORT}`; + +require('../server.js'); + +const sampleUser = { + username: 'testUser', + password: '1234', + email: 'fake@email.com', +}; + +describe('User Auth tests', function() { + describe('POST: /api/signup', function() { + describe('with a valid body', function() { + after( done => { + User.remove({}) + .then( () => done()) + .catch(done); + }); + + it('should return a token', done => { + request.post(`${url}/api/signup`) + .send(sampleUser) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.text).to.be.a('string'); + done(); + }); + }); + }); + describe('with an invalid body', function() { + it('should return a 400 error', done => { + request.post(`${url}/api/signup`) + .send('bad data') + .end((err, res) => { + expect(err.message).to.equal('Bad Request'); + expect(res.status).to.equal(400); + done(); + }); + }); + }); + }); + + describe('GET: /api/signup', function() { + describe('with valid credentials', function() { + before( done => { + this.tempUser = new User(sampleUser); + this.tempUser.createPasswordHash(this.tempUser.password) + .then( user => user.save()) + .then( () => done()) + .catch(done); + }); + after( done => { + User.remove({}) + .then( () => done()) + .catch(done); + }); + it('should return a token', done => { + request.get(`${url}/api/signin`) + .auth('testUser', '1234') + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + done(); + }); + }); + }); + describe('with invalid credentials', function () { + before( done => { + this.tempUser = new User(sampleUser); + this.tempUser.createPasswordHash(this.tempUser.password) + .then( user => user.save()) + .then( () => done()) + .catch(done); + }); + after( done => { + User.remove({}) + .then( () => done()) + .catch(done); + }); + describe('with an invalid password', () => { + it('should respond with a 401' , done => { + request.get(`${url}/api/signin`) + .auth('testUser', 'wrongPass') + .end((err, res) => { + expect(err.status).to.equal(401); + expect(res.text).to.equal('invalid password!'); + done(); + }); + }); + }); + describe('with an invalid username', () => { + it('should respond with a 401' , done => { + request.get(`${url}/api/signin`) + .auth('wrongUser', '1234') + .end((err, res) => { + expect(err.status).to.equal(401); + expect(res.text).to.equal('user not found!'); + done(); + }); + }); + }); + }); + }); + describe('for a nonexistent endpoint', function () { + it('should return a 404', done => { + request(`${url}/wrong/`) + .end((err, res) => { + expect(err.message).to.equal('Not Found'); + expect(res.status).to.equal(404); + done(); + }); + }); + }); +}); + From fab85fbb0fa298ff70388938b1e0ab596ece1b3d Mon Sep 17 00:00:00 2001 From: Zachary Crumbo Date: Mon, 6 Mar 2017 22:07:08 -0800 Subject: [PATCH 2/8] added .env file for this one assignment - in .gitignor for future --- lab-zachary/.env | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 lab-zachary/.env diff --git a/lab-zachary/.env b/lab-zachary/.env new file mode 100644 index 0000000..d1c8741 --- /dev/null +++ b/lab-zachary/.env @@ -0,0 +1,2 @@ +MONGODB_URI='mongodb://localhost/cfgram' +APP_SECRET='whateveryouwant' \ No newline at end of file From 1d6e3510a60f78609ae92b8e39312dfac80ce46b Mon Sep 17 00:00:00 2001 From: Zachary Crumbo Date: Tue, 7 Mar 2017 21:23:55 -0800 Subject: [PATCH 3/8] day 2 - all files with bonus delete tests --- lab-zachary/.gitignore | 1 + lab-zachary/README.md | 29 +- lab-zachary/lib/basic-auth-middleware.js | 2 +- lab-zachary/lib/bearer-auth-middleware.js | 32 +++ lab-zachary/lib/error-middleware.js | 2 +- lab-zachary/model/gallery.js | 13 + lab-zachary/model/user.js | 2 +- lab-zachary/package.json | 4 +- lab-zachary/route/auth-router.js | 7 +- lab-zachary/route/gallery-router.js | 68 +++++ lab-zachary/server.js | 2 + lab-zachary/test/gallery-test.js | 308 ++++++++++++++++++++++ lab-zachary/test/user-auth-test.js | 3 +- 13 files changed, 454 insertions(+), 19 deletions(-) create mode 100644 lab-zachary/lib/bearer-auth-middleware.js create mode 100644 lab-zachary/model/gallery.js create mode 100644 lab-zachary/route/gallery-router.js create mode 100644 lab-zachary/test/gallery-test.js diff --git a/lab-zachary/.gitignore b/lab-zachary/.gitignore index 89ab13a..a7dd290 100644 --- a/lab-zachary/.gitignore +++ b/lab-zachary/.gitignore @@ -104,6 +104,7 @@ Icon Network Trash Folder Temporary Items .apdisk +.DS_Store ### Windows ### # Windows thumbnail cache files diff --git a/lab-zachary/README.md b/lab-zachary/README.md index 7519e2a..767005d 100644 --- a/lab-zachary/README.md +++ b/lab-zachary/README.md @@ -1,6 +1,6 @@ -# Basic Auth with an Express API +# Basic and Bearer Auth with an Express API -This app implements Basic Auth with API endpoints to allow for user signup and sign in. +This app implements Basic and Bearer Auth with API endpoints to allow for user signup and sign in, and to create, read, update and delete galleries associated with that user. # System Requirements @@ -41,23 +41,32 @@ npm start ### Connecting -If you are using HTTPie, in your terminal window, type the following commands, where '3000' would be replaced with your local environment PORT variable, if configured. Commands can be sent to the api/signin and the api/signup endpoints. +Commands can be sent to the api/signin and the api/signup endpoints. Once signed up, commands can be sent with the token generated during signup to api/gallery and api/gallery/:id endpoints for full CRUD functionality ```sh -$ http POST localhost:3000/api/signup/ username='testname' password='password' email='email@email.com' #signs up for the api and returns a unqique token that must be used in future api calls +$ POST /api/signup/ with JSON body {username='testname' password='password' email='email@email.com'} #signs up for the api and returns a unqique token that must be used in future api calls -$ http GET -a username:password localhost:3000/api/signin #signs into the API +$ GET /api/signin with basic auth header username:password #signs into the API + +$ POST /api/gallery with token #creates a new gallery + +$ GET /api/gallery/:galleryID with token #retrieve your gallery + +$ PUT /api/gallery/:galleryID with token and JSON body {name:'galleryName', desc: 'description'} #updates specified gallery + +$ DELETE /api/gallery/:galleryID with token #deletes specified gallery ``` Sending the following requests to the server will have the results below: - * `GET, POST`: 404 response with 'not found' for unregistered endpoints -* `GET`: 401 response with a bad credentials - * `GET`: 200 response with a proper signin - * `POST`: 400 response with 'bad request' if no request body was provided or the body was invalid - * `POST`: 200 response with the body content for a post request with a valid body + * `404` response with 'not found' for unregistered endpoints and nonexistent gallery IDs + * `401` response with 'unauthorized' for bad credentials + * `200` response with a proper signin and gallery creation + * `400` response with 'bad request' if no request body was provided or the body was invalid + * `200` response with the body content for requests with valid bodies, endpoints and ids + * `204` response for successful deletions [1]:https://brew.sh/ diff --git a/lab-zachary/lib/basic-auth-middleware.js b/lab-zachary/lib/basic-auth-middleware.js index 2949fe1..a9481bd 100644 --- a/lab-zachary/lib/basic-auth-middleware.js +++ b/lab-zachary/lib/basic-auth-middleware.js @@ -1,7 +1,7 @@ 'use strict'; const createError = require('http-errors'); -const debug = require('debug')('cf-gram:basic-auth-middleware'); +const debug = require('debug')('cfgram:basic-auth-middleware'); module.exports = function(req, res, next) { debug('basic auth'); diff --git a/lab-zachary/lib/bearer-auth-middleware.js b/lab-zachary/lib/bearer-auth-middleware.js new file mode 100644 index 0000000..3058f18 --- /dev/null +++ b/lab-zachary/lib/bearer-auth-middleware.js @@ -0,0 +1,32 @@ +'use strict'; + +const createError = require('http-errors'); +const jwt = require('jsonwebtoken'); +const debug = require('debug')('cfgram:bearer-auth-middleware'); +const User = require('../model/user.js'); + +//get req, res, next from other middleware and decipher the auth token in the auth header, then attach it to the request body + +module.exports = function( req, res, next) { + debug('bearer auth'); + + var authHeader = req.headers.authorization; + if (!authHeader) return next(createError(401, 'Authorization headers required')); + + var token = authHeader.split('Bearer ')[1]; + if (!token) return next(createError(401, 'token required')); + + //validate token and use jwt verify user + + jwt.verify(token, process.env.APP_SECRET, function(err, decoded) { + if (err) return next(createError(401, 'token validation error')); + if (!decoded) return next(createError(401, 'invalid token')); + User.findOne({findHash: decoded.token}) + .then( user => { + req.user = user; + next(); + }) + .catch( err => next(createError(401, err.message))); + }); +}; + diff --git a/lab-zachary/lib/error-middleware.js b/lab-zachary/lib/error-middleware.js index 70d35b1..e774853 100644 --- a/lab-zachary/lib/error-middleware.js +++ b/lab-zachary/lib/error-middleware.js @@ -1,7 +1,7 @@ 'use strict'; const createError = require('http-errors'); -const debug = require('debug')('cf-gram:error-middleware'); +const debug = require('debug')('cfgram:error-middleware'); module.exports = function(err, req, res, next){ debug('error-middleware'); diff --git a/lab-zachary/model/gallery.js b/lab-zachary/model/gallery.js new file mode 100644 index 0000000..878fdab --- /dev/null +++ b/lab-zachary/model/gallery.js @@ -0,0 +1,13 @@ +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const gallerySchema = Schema({ + name: {type: String, required: true}, + desc: {type: String, required: true}, + created: {type: Date, required: true, default: new Date}, + userID: { type: Schema.Types.ObjectId, required: true }, +}); + +module.exports = mongoose.model('gallery', gallerySchema); \ No newline at end of file diff --git a/lab-zachary/model/user.js b/lab-zachary/model/user.js index 8dabbaa..bc4eace 100644 --- a/lab-zachary/model/user.js +++ b/lab-zachary/model/user.js @@ -2,7 +2,7 @@ const mongoose = require('mongoose'); const crypto = require('crypto'); -const debug = require('debug')('cf-gram:user'); +const debug = require('debug')('cfgram:user'); const bcrypt = require('bcrypt'); const createError = require('http-errors'); const jwt = require('jsonwebtoken'); diff --git a/lab-zachary/package.json b/lab-zachary/package.json index b3bad6f..20ba4a0 100644 --- a/lab-zachary/package.json +++ b/lab-zachary/package.json @@ -4,8 +4,8 @@ "description": "", "main": "gulpfile.js", "scripts": { - "test": "DEBUG='cf-gram*' mocha", - "start": "DEBUG='cf-gram*' nodemon server.js" + "test": "DEBUG='cfgram*' mocha", + "start": "DEBUG='cfgram*' nodemon server.js" }, "keywords": [], "author": "", diff --git a/lab-zachary/route/auth-router.js b/lab-zachary/route/auth-router.js index e17348b..060ebf2 100644 --- a/lab-zachary/route/auth-router.js +++ b/lab-zachary/route/auth-router.js @@ -4,7 +4,7 @@ const Router = require('express').Router; const jsonParser = require('body-parser').json(); const User = require('../model/user.js'); const createError = require('http-errors'); -const debug = require('debug')('cf-gram:auth-router'); +const debug = require('debug')('cfgram:auth-router'); const basicAuth = require('../lib/basic-auth-middleware'); @@ -39,7 +39,10 @@ authRouter.get('/api/signin', basicAuth, (req, res, next) => { debug('GET: /api/signin'); User.findOne({username: req.auth.username}) - .then( user => user ? user.comparePasswordHash(req.auth.password) : next(createError(401, 'user not found!'))) + .then( user => { + if (!user) return next(createError(401, 'user not found!')); + return user.comparePasswordHash(req.auth.password); + }) .then( user => user.generateToken()) .then( token => res.send(token)) .catch(next); diff --git a/lab-zachary/route/gallery-router.js b/lab-zachary/route/gallery-router.js new file mode 100644 index 0000000..0517969 --- /dev/null +++ b/lab-zachary/route/gallery-router.js @@ -0,0 +1,68 @@ +'use strict'; + +const Router = require('express').Router; +const jsonParser = require('body-parser').json(); +const createError = require('http-errors'); +const debug = require('debug')('cfgram:gallery-router'); + +const bearerAuth = require('../lib/bearer-auth-middleware.js'); +const Gallery = require('../model/gallery.js'); + +const galleryRouter = module.exports = Router(); + + +galleryRouter.post('/api/gallery', bearerAuth, jsonParser, (req, res, next) => { + debug('POST: /api/gallery'); + //check auth token(in middleware), get user on req body, create new gallery associated with user's id + //req has body and user properties we need to get + if(!req.body.name) return next(createError(400, 'name required')); + if(!req.body.desc) return next(createError(400, 'description required')); + req.body.userID = req.user._id; + new Gallery(req.body).save() + .then( gallery => { + if (!gallery) return next(createError(400, 'gallery not created')); + res.json(gallery); + }) + .catch(next); +}); + +galleryRouter.get('/api/gallery/:id', bearerAuth, (req, res, next) => { + debug('GET: /api/gallery/:id'); + + Gallery.findById(req.params.id, function(err) { + if(err) { + if(err.name === 'CastError') next(createError(404, 'gallery not found')); + } + + }) + .then(gallery => { + if (!gallery) return next(createError(404, 'gallery not found')); + res.json(gallery); + }) + .catch(next); +}); + +galleryRouter.put('/api/gallery/:id', bearerAuth, jsonParser, (req, res, next) => { + debug('POST: /api/gallery/:id'); + + if(!req.body.name) return next(createError(400, 'name required')); + if(!req.body.desc) return next(createError(400, 'description required')); + + Gallery.findByIdAndUpdate(req.params.id, req.body, {new:true}) + .then( gallery => { + if(!gallery) return next(createError(404, 'gallery not found')); + res.json(gallery); + }) + .catch(next); +}); + +galleryRouter.delete('/api/gallery/:id', bearerAuth, (req, res, next) => { + debug('DELETE: /api/gallery:id'); + + Gallery.findByIdAndRemove(req.params.id) + .then(gallery => { + if(!gallery) return next(createError(404, 'gallery not found')); + res.sendStatus(204); + }) + .catch(next); +}); diff --git a/lab-zachary/server.js b/lab-zachary/server.js index e99361a..9bd4b00 100644 --- a/lab-zachary/server.js +++ b/lab-zachary/server.js @@ -8,6 +8,7 @@ const dotenv = require('dotenv'); const mongoose = require('mongoose'); const authRouter = require('./route/auth-router.js'); +const galleryRouter = require('./route/gallery-router.js'); const errors = require('./lib/error-middleware.js'); const PORT = process.env.PORT || 3000; @@ -19,6 +20,7 @@ mongoose.connect(process.env.MONGODB_URI); app.use(cors()); app.use(morgan('dev')); app.use(authRouter); +app.use(galleryRouter); app.use(errors); diff --git a/lab-zachary/test/gallery-test.js b/lab-zachary/test/gallery-test.js new file mode 100644 index 0000000..086f3bc --- /dev/null +++ b/lab-zachary/test/gallery-test.js @@ -0,0 +1,308 @@ +'use strict'; + +const request = require('superagent'); +const expect = require('chai').expect; +const Promise = require('bluebird'); + +const User = require('../model/user.js'); +const Gallery = require('../model/gallery.js'); + +const url = `http://localhost:${process.env.PORT}`; + +require('../server.js'); + + +const sampleUser = { + username: 'sampleUser', + email: 'email@test.com', + password: '1234' +}; +const sampleGallery = { + name: 'sampleName', + desc: 'sample description' +}; + + +describe('Gallery Routes', function() { + afterEach( done => { + Promise.all([ + User.remove({}), + Gallery.remove({}) + ]) + .then( () => done()) + .catch(done); + }); + describe('POST: /api/gallery', function () { + before( done => { + //create a user + new User(sampleUser) + .createPasswordHash(sampleUser.password) + .then( user => { + this.tempUser = user; + return user.generateToken(); + }) + .then( token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + + describe('With a valid token and body', () => { + it('should return a new gallery', done => { + request.post(`${url}/api/gallery`) + .send(sampleGallery) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.desc).to.equal(sampleGallery.desc); + expect(res.body.name).to.equal(sampleGallery.name); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + done(); + }); + }); + }); + describe('Without a valid token', () => { + it('should return a 401', done => { + request.post(`${url}/api/gallery`) + .send(sampleGallery) + .set({ + Authorization: 'Bearer ofbadnews' + }) + .end((err, res) => { + expect(res.status).to.equal(401); + expect(err.message).to.equal('Unauthorized'); + done(); + }); + }); + }); + describe('Without a valid body', () => { + it('should return a 400', done => { + request.post(`${url}/api/gallery`) + .send({bad: 'data'}) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(err.message).to.equal('Bad Request'); + done(); + }); + }); + }); + }); + describe('GET: /api/gallery/:id', function() { + before( done => { + //create a user + new User(sampleUser) + .createPasswordHash(sampleUser.password) + .then( user => { + this.tempUser = user; + return user.generateToken(); + }) + .then( token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + before( done => { + //create a gallery + this.tempGallery = new Gallery(sampleGallery); + this.tempGallery.userID = this.tempUser._id; + this.tempGallery.save() + .then( gallery => { + this.tempGallery = gallery; + done(); + }) + .catch(done); + }); + describe('with a valid gallery ID', () => { + it('should return a gallery', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal(sampleGallery.name); + expect(res.body.desc).to.equal(sampleGallery.desc); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + done(); + }); + }); + }); + describe('with an invalid gallery ID', () => { + it('should return a 404', done => { + request.get(`${url}/api/gallery/badID`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(err.message).to.equal('Not Found'); + expect(res.status).to.equal(404); + done(); + }); + }); + }); + describe('with an invalid token', () => { + it('should return a 400', done => { + request.get(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: 'Bearer ofBadTokens' + }) + .end((err, res) => { + expect(err.message).to.equal('Unauthorized'); + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + describe('PUT /api/gallery/:id', function() { + before( done => { + //create a user + new User(sampleUser) + .createPasswordHash(sampleUser.password) + .then( user => { + this.tempUser = user; + return user.generateToken(); + }) + .then( token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + before( done => { + //create a gallery + this.tempGallery = new Gallery(sampleGallery); + this.tempGallery.userID = this.tempUser._id; + this.tempGallery.save() + .then( gallery => { + this.tempGallery = gallery; + done(); + }) + .catch(done); + }); + describe('with a valid token', () => { + describe('with a valid body', () => { + it('should return an updated gallery', done => { + request.put(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .send({name: 'updatedName', desc: 'updatedDesc'}) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal('updatedName'); + expect(res.body.desc).to.equal('updatedDesc'); + expect(res.body.userID).to.equal(this.tempUser._id.toString()); + done(); + }); + }); + }); + describe('with an invalid body', () => { + it('should return a 400 error', done => { + request.put(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .send({badName:'thiswontupdate', badDesc:'neitherwillthis'}) + .end((err, res) => { + expect(err.message).to.equal('Bad Request'); + expect(res.status).to.equal(400); + done(); + }); + }); + }); + describe('with an invalid gallery ID', () => { + it('should return a 404', done => { + request.put(`${url}/api/gallery/badGalleryID`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .send({name: 'thiswontupdate', desc: 'neitherwillthis'}) + .end((err, res) => { + expect(err.message).to.equal('Not Found'); + expect(res.status).to.equal(404); + done(); + }); + }); + }); + describe('with an invalid token', () => { + it('should return a 401 error', done => { + request.put(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: 'Bearer ofBadPuns' + }) + .send({name: 'didnthaveachance', desc:'neverwillgetupdated'}) + .end((err, res) => { + expect(err.message).to.equal('Unauthorized'); + expect(res.status).to.equal(401); + done(); + }); + }); + }); + }); + }); + describe('DELETE: /api/gallery/:id', function () { + before( done => { + //create a user + new User(sampleUser) + .createPasswordHash(sampleUser.password) + .then( user => { + this.tempUser = user; + return user.generateToken(); + }) + .then( token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + before( done => { + //create a gallery + this.tempGallery = new Gallery(sampleGallery); + this.tempGallery.userID = this.tempUser._id; + this.tempGallery.save() + .then( gallery => { + this.tempGallery = gallery; + done(); + }) + .catch(done); + }); + describe('with a valid id', () => { + it('should return a 204', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + describe('with an invalid id', () => { + it('should return a 404', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}`) //gallery was already removed above + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(res.status).to.equal(404); + expect(err.message).to.equal('Not Found'); + done(); + }); + }); + }); + }); +}); + diff --git a/lab-zachary/test/user-auth-test.js b/lab-zachary/test/user-auth-test.js index e1b512d..726166e 100644 --- a/lab-zachary/test/user-auth-test.js +++ b/lab-zachary/test/user-auth-test.js @@ -118,5 +118,4 @@ describe('User Auth tests', function() { }); }); }); -}); - +}); \ No newline at end of file From 3dc34fcd5f01d94c5c4d3c7cac81eefeeaee6dee Mon Sep 17 00:00:00 2001 From: Zachary Crumbo Date: Wed, 8 Mar 2017 21:52:05 -0800 Subject: [PATCH 4/8] all files --- lab-zachary/.env | 2 - .../data/1943fb9c89c3e9b4822ccc0defb97a6d | Bin 0 -> 4653 bytes lab-zachary/lib/error-middleware.js | 1 + lab-zachary/lib/s3-methods.js | 28 +++ lab-zachary/model/pic.js | 17 ++ lab-zachary/package.json | 5 +- lab-zachary/route/gallery-router.js | 2 +- lab-zachary/route/pic-router.js | 79 +++++++ lab-zachary/server.js | 8 +- lab-zachary/test/data/sample.jpg | Bin 0 -> 4653 bytes lab-zachary/test/gallery-test.js | 5 +- lab-zachary/test/lib/server-toggle.js | 35 +++ lab-zachary/test/pic-test.js | 204 ++++++++++++++++++ lab-zachary/test/user-auth-test.js | 5 +- 14 files changed, 384 insertions(+), 7 deletions(-) delete mode 100644 lab-zachary/.env create mode 100644 lab-zachary/data/1943fb9c89c3e9b4822ccc0defb97a6d create mode 100644 lab-zachary/lib/s3-methods.js create mode 100644 lab-zachary/model/pic.js create mode 100644 lab-zachary/route/pic-router.js create mode 100644 lab-zachary/test/data/sample.jpg create mode 100644 lab-zachary/test/lib/server-toggle.js create mode 100644 lab-zachary/test/pic-test.js diff --git a/lab-zachary/.env b/lab-zachary/.env deleted file mode 100644 index d1c8741..0000000 --- a/lab-zachary/.env +++ /dev/null @@ -1,2 +0,0 @@ -MONGODB_URI='mongodb://localhost/cfgram' -APP_SECRET='whateveryouwant' \ No newline at end of file diff --git a/lab-zachary/data/1943fb9c89c3e9b4822ccc0defb97a6d b/lab-zachary/data/1943fb9c89c3e9b4822ccc0defb97a6d new file mode 100644 index 0000000000000000000000000000000000000000..98c5b2467fcf1f58eb8a4d0b9f8f94786d9135e3 GIT binary patch literal 4653 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 TT%@MM?K)X!8QupK*cko~qX&Ar literal 0 HcmV?d00001 diff --git a/lab-zachary/lib/error-middleware.js b/lab-zachary/lib/error-middleware.js index e774853..7b23bc3 100644 --- a/lab-zachary/lib/error-middleware.js +++ b/lab-zachary/lib/error-middleware.js @@ -18,6 +18,7 @@ module.exports = function(err, req, res, next){ } if (err.message === 'ValidationError'){ + debug('Mongoose Error'); err = createError(400, err.message); res.status(err.status).send(err.message); next(); diff --git a/lab-zachary/lib/s3-methods.js b/lab-zachary/lib/s3-methods.js new file mode 100644 index 0000000..d6d2ff0 --- /dev/null +++ b/lab-zachary/lib/s3-methods.js @@ -0,0 +1,28 @@ +'use strict'; + +const AWS = require('aws-sdk'); +const debug = require('debug')('cfgram:s3-methods'); +const s3 = new AWS.S3(); + +AWS.config.setPromisesDependency(require('bluebird')); + +module.exports = exports = {}; + +exports.uploadObjectProm = function(params) { + return new Promise((resolve, reject) => { + s3.upload(params, (err, data) => { + if (err) console.error(reject); + resolve(data); + }); + }); +}; + +exports.deleteObjectProm = function(params) { + debug('deleteS3Prom') ; + return new Promise((resolve, reject) => { + s3.deleteObject(params, function(err, data) { + if (reject) console.error(err); + resolve(data); + }); + }); +}; \ No newline at end of file diff --git a/lab-zachary/model/pic.js b/lab-zachary/model/pic.js new file mode 100644 index 0000000..e63609f --- /dev/null +++ b/lab-zachary/model/pic.js @@ -0,0 +1,17 @@ +'use strict'; + +const mongoose = require('mongoose'); +const Schema = mongoose.Schema; + +const picSchema = Schema({ + name: {type:String, required:true}, + desc: {type:String, required:true}, + userID: {type: Schema.Types.ObjectId, required: true}, + galleryID: {type: Schema.Types.ObjectId, required: true}, + created: {type: Date, required: true, default: new Date}, + imageURI: {type: String, required: true, unique: true}, + objectKey: {type: String, required: true, unique: true} +}); + +module.exports = mongoose.model('pic', picSchema); + diff --git a/lab-zachary/package.json b/lab-zachary/package.json index 20ba4a0..626b186 100644 --- a/lab-zachary/package.json +++ b/lab-zachary/package.json @@ -11,17 +11,20 @@ "author": "", "license": "ISC", "dependencies": { + "aws-sdk": "^2.24.0", "bcrypt": "^1.0.2", "bluebird": "^3.5.0", "body-parser": "^1.17.1", "cors": "^2.8.1", "debug": "^2.6.1", + "del": "^2.2.2", "dotenv": "^4.0.0", "express": "^4.15.2", "http-errors": "^1.6.1", "jsonwebtoken": "^7.3.0", "mongoose": "^4.8.6", - "morgan": "^1.8.1" + "morgan": "^1.8.1", + "multer": "^1.3.0" }, "devDependencies": { "chai": "^3.5.0", diff --git a/lab-zachary/route/gallery-router.js b/lab-zachary/route/gallery-router.js index 0517969..5d5954e 100644 --- a/lab-zachary/route/gallery-router.js +++ b/lab-zachary/route/gallery-router.js @@ -65,4 +65,4 @@ galleryRouter.delete('/api/gallery/:id', bearerAuth, (req, res, next) => { res.sendStatus(204); }) .catch(next); -}); +}); \ No newline at end of file diff --git a/lab-zachary/route/pic-router.js b/lab-zachary/route/pic-router.js new file mode 100644 index 0000000..1a6140c --- /dev/null +++ b/lab-zachary/route/pic-router.js @@ -0,0 +1,79 @@ +'use strict'; + +const Router = require('express').Router; +const multer = require('multer'); +const fs = require('fs'); +const del = require('del'); +const path = require('path'); +const createError = require('http-errors'); +const debug = require('debug')('cfgram:pic-router'); + +const bearerAuth = require('../lib/bearer-auth-middleware.js'); +const Pic = require('../model/pic.js'); +const Gallery = require('../model/gallery.js'); +const s3Methods = require('../lib/s3-methods.js'); + +const dataDir = `${__dirname}/../data`; +const upload = multer({dest: dataDir }); + +const picRouter = module.exports = Router(); + +picRouter.post('/api/gallery/:galleryID/pic', bearerAuth, upload.single('image'), (req, res, next) => { //req.file is object passed by multer + debug('POST: /api/gallery/:galleryID/pic'); + if(!req.file) return next(createError(400, 'no file provided')); + if(!req.file.path) return next(createError(500, 'file not saved')); + + let ext = path.extname(req.file.originalname); + let params = { + ACL: 'public-read', + Bucket: process.env.AWS_BUCKET, + Key: `${req.file.filename}${ext}`, + Body: fs.createReadStream(req.file.path), + }; +//upload + Gallery.findById(req.params.galleryID) + .then( gallery => { + if(!gallery) return next(createError(404, 'gallery not found')); + this.tempGallery = gallery; + s3Methods.uploadObjectProm(params) + .then( s3Data => { + del([`${dataDir}/*`]); + new Pic({ + name: req.body.name, + desc: req.body.desc, + userID: this.tempGallery.userID, + galleryID: req.params.galleryID, + imageURI: s3Data.Location, + objectKey: s3Data.Key + }) + .save() + .then( pic => { + res.json(pic); + }) + .catch(next); + }); + }); +}); + +picRouter.delete('/api/gallery/:galleryID/pic/:picID', bearerAuth, (req, res, next) => { + debug('DELETE: /api/gallery/:galleryID/pic/:picID'); + + Pic.findById(req.params.picID) + .then( pic => { + if (!pic) return next(createError(404, 'pic not found')); + let params = { + Bucket: process.env.AWS_BUCKET, + Key: pic.objectKey + }; + s3Methods.deleteObjectProm(params) + .then( s3Data => { + debug('delete data:',s3Data); + Pic.findByIdAndRemove(req.params.picID) + .then( () => { + res.sendStatus(204); + }); + }); + }); +}); + + diff --git a/lab-zachary/server.js b/lab-zachary/server.js index 9bd4b00..5170c53 100644 --- a/lab-zachary/server.js +++ b/lab-zachary/server.js @@ -9,10 +9,12 @@ const mongoose = require('mongoose'); const authRouter = require('./route/auth-router.js'); const galleryRouter = require('./route/gallery-router.js'); +const picRouter = require('./route/pic-router.js'); const errors = require('./lib/error-middleware.js'); const PORT = process.env.PORT || 3000; const app = express(); + dotenv.load(); mongoose.connect(process.env.MONGODB_URI); @@ -21,7 +23,11 @@ app.use(cors()); app.use(morgan('dev')); app.use(authRouter); app.use(galleryRouter); +app.use(picRouter); app.use(errors); -app.listen(PORT, () => debug('server up:', PORT)); \ No newline at end of file + +const server = module.exports = app.listen(PORT, () => debug('server up:', PORT)); + +server.isRunning = true; \ No newline at end of file diff --git a/lab-zachary/test/data/sample.jpg b/lab-zachary/test/data/sample.jpg new file mode 100644 index 0000000000000000000000000000000000000000..98c5b2467fcf1f58eb8a4d0b9f8f94786d9135e3 GIT binary patch literal 4653 zcmbVOc{tST+y9Q4u`gMZtznF2WQ=8u8D$x}kzEpwWyWA)jIqR^GIm9VvZtmKM=C8s zL{U+6Dw3U2c6CyrQp9g`PMzQTUgtf3y!Z8ep6hw;=lG2#l__ z4hGn58W#~j38hBE$kbq3xUt&1=2kTrEznqPpMispLxcr2gk~EbNp*{Nbf?6JQV4-+ zrY5l6aYk|U2s$-702W6N3y(62GgkYq+=#c{WMkA|-$kNBjn#;oR$-nF&M*r`Bo$_$ zjYCs(_312<<9YgL2=oXCho6f7FU{ z@E>4a?*84-rXbH7Bd16j?@|K7EE$v-IyF4n&eB+occL9g3p6sc!0B7*Sre@-aOS$Y zRycwMUf)VzN6*k2i^J+;^?vC1N30c|h$G;!))sh+O)TEh%G?sGt3%MS#OfOu=={Li zg-1mPgj1+L?9w*v{)yH9SFDjmBsCzK5$Vogg#D-h=MY9TBPxUu0kg2!ym3Pq(jkCC z3*Tg-HZSSVm@TQ1w1d<@>qrJ2_C3o+w14nIH$WeUBLq>=hFBdc8XG_+qscfj9?c7i zK)~u!1NF)JYJcGa|3BHo@EpQymdSsW%?}o@7B`nan}E0Z*+$fGUhhQmnq*@E5dU8K zKwulM89^JPKq>$MZxU~Y@I!cm9}0m$`33klNkCXYkVgW-!Xl!gq9Ve)iAR!~B>j^> z5C}w2Ku}y*SX@S2TwF$G^W^*VQ{>+l8*PB3AV38;V2~uhCkX;ef;QR#4$nU@6!iD6 z1OUMNf_xCDfDi}-0>HfSU&IsTgKXg;zz_fe1@nQ0HV;93ysacuRf=Cgmnbdh62MlI zVd|MD%jTVRB}c+3De71`c}Wn!$HReyek$a_Bvm20c~V4|0Ol(_va}38bYlb%1@j~& z!IFR}aHbCmGIN&szoyXBPZv%j`ks(WrW_vT!L_Q4H3G01GNW3feXPfC%tj0WpvlCX zy#a{(B&On*CNOX?Rg|{dDrNpm9V}2nm%S-@W_P71hZnjrLK@548@k zazmR#d$cN!^ljyq{;y<3&9;IH7QErmdrhv3i3aJ?Gm0)$b=s@XK2OC{T-1`djuxmq z=ZRO+JaM5R8u%51R@QYxPO^CF1Cho)i& z1DN7|BA4TQiH{)CIl((PT~;9Jipv&H8Vbu|%nC=%4oo`?lU(ZCAY1yM)84t&t%Sa9 zpV4%}w)sGXyy1bRPHCejdvRALEIM6G6rT~b@U1P{8B@%jsmo@UP0{9WGD@@mpu(r$ z!0>n0iyZh5bw`y$hK2l0I(+8+U-vUUH&l$iA&S+q@IFh=;e=CKg|p;ZrR$>SBa1zm zRi2rvf+FT=cc5rl=jh~RXoo-@RKO5#ZLd6FGF|cHv8}bKSlSdJo!Z4BHk9P-SX6uG zVj`sS>)x*}LZKVL;OQ5}d#`T*O&W9RvKs*XVSLc|yx$}pJ|ia87U`65Xj^*bjfkb- zMFM%=(&w=8C*r=s*{H8++c`dD|cH%k+_=A(6gvN8mrX@8>P?D-HOSQ_4MXm_j^ zyGSH|l|4hh$YC}NXR%LKD#cR-`SgbQyS%l#?O4m(QP75-*#)|yu=}sFj%5oJfo-g+ zt}+$DkK?K7%oD~QPcPgp&Kz}>Lth?H`e@o)aHigDK4n1!g8#}APXLvr9pB^kD0U}U zX+2nc(vIj=PEpOZ&vc7dMD;bCUF%JW5HiCscc(TQw*3UL2okdlP=x z$Sa~6T}{~wKa%yWPl0m@$(P1=qGoP4ERPsICzf2#IhjK`o&+N9`j!*jU?bIjd}8Y9 zKFP5PipbISLkRK1;0vVZr4>8fzjmPP4X+`ge)Tl0v~>~}L!6#dIJE&>ZG7kLe>|3R z^XkaJwk%nW%vw?A?fbp93ZNJy2l(n7)!o1}-aqt(wSOI&R&cM$=YdGCr^O%5nZ@TK zColRV5JeI>!@JyLTw|p!T+#NLymE3menm;?)TL|3R8G5H+24yBzvxll@6&6UKADB@ zLv+yBGAh$Ytu}!CC%tB0_hM(sb@NvamHOn)u!b&9<3Hv$ke`hh1~oYmqf}(?1ul)v z_z9T`Y#V@AX5*Ze)J>Mt=p?s!EMeVmq85&Qs}$VZc5Pt&@L_}AD%+b9m)@_nRyUq@$`6kGGA6;DJ=alj zYo$2-)B)1HoX%s)=n|Mw0ZlcOs3IiPn{1OL#pepx0_tJ_xD7xWlRk2veLK^?c0fXD zdaYR`|KW%IBJBLmpprWeT>}CoCecnM^0ToIT%QB)n1U8ZHCEr%2<#+a)PC=B7j?_` zspIc~L$J>rbGjgi1GYDhnh7;=kHmDlUmIHUV!%U-$oRgogjXdx*R_i$NFF&|2Qj}E z87kkg^9}&hugoBkp%)O^37mpQI&bST6zEa|CAUx9zHpD~Fq~g={`PbMjQO zPqkv)exFtv%&s3Q5oJcYwAT#o&ukA_pcD8hg?%L-K0-qWpEiC}P|!?f#vBRqU$r?d zI-@L{y(F^6TKPYfoOjZV01}4S|jED!LIRYZ|D&F~MaI^;dNq1n`bPkS>a@Z{=a5T)0+HRXa`PrL(W-o4ML+0MK5X0!)s6)EjG8a{zknCNP zv!lM1X~w2*5!(J~U7=@(^w^+dH}n3h>6w;Ehnne1`90Mh?cOm-xlxlZkp>xItMxBg z+K5hkt7L{gS|7phYux2zxT1CKi)eMFa_*7)np?kB*5#H%z=8FqL1t$a3*f!xN)2y2 zg!h%FvlV(w8uzS`c+5Q1`JPcBBw3Z{eFf#Ape`YkW2b_ZGR^tugqRWx@bMEX);N zr^d>Jq%2nA&@Jbkixr87L~*rCgKk8-t1+taeCW!9+^3(Y4@+`~e9q-GY4p_Uj^b-x zs^4yW?N%MO!Z)$swZH0UOv6d(uz2pzcjiu_h`yagmGJ+yaPeaK6ME3TfWD+ zb-t7`f@p(9ZL?#LFH>_H%8t|Ec(yLzLtt6&q#;o&B5{;6;YGVqIQtt1Aa$9B^oSjH zR}>K&-qTuVziaPHr!=trRU^f7t1JooF7nf(QxeZ@3^RtMNQE(Dulej9&y;2EcxeS8 z&7SUFmA+CzI5GLO+hp?Lb@Vxz@g4W}{2Rqs;NM zfmQEE{pC2LNy$UjcNmIDuN1MI*W(cw2YRFP7a8+j`}6to1<=QHFhpH*lbmL2Y0^qh z*m3)}0=4Rtb5j|}l=5!5-k>|>uWJ__E_+)}Htr_&Y?Ufcd~4P*;oHBuy|`O9YN-Bw z;_j{DUrJ=_QC+BrZyo9TT{I(P)~Asg5AG-b^0CSyrpW7TiF}KH*KAqeOzl}I^|UY5 z6_WGy--KG9q);7Lj>#hFvyYTR3~VTrsJT=sr2@YkdC{nAar>3F9!u+>sjsV7Urv8G zpw%i&@;#bwYlrf?)aeO{8xFfTMJGPHyt7Mpt@$|l3l8PYrNVC@&PI?7bDM;3ESg^~ zNbKMzFUvi-(dcZm0mQJfJRR$FhnX(^(~+KQ2g&h@ZojE-eU5k_EL{z$M48s`r+s?d zHr_p5waEI=U`e0WmseD7^Q^Y{-C1(o$l0#tfdaMt{8#@(u@>Yuef(78L)Re)`#rZI zd%;8VuJ=w-Mq!u+z~cb zX!kr5mJ&d8Y%?>#%EBbgu7_1xzkbj4bu=hpDh1d(rMYn?}s4>K{ zj+s|-K8VZZCL*QXV8ee6He)v&%}>iZe2krUIWgyXnQpHaq6*4&^;#p;#!?$eF)Sro7cl0?YkO;yn$q TT%@MM?K)X!8QupK*cko~qX&Ar literal 0 HcmV?d00001 diff --git a/lab-zachary/test/gallery-test.js b/lab-zachary/test/gallery-test.js index 086f3bc..a34d071 100644 --- a/lab-zachary/test/gallery-test.js +++ b/lab-zachary/test/gallery-test.js @@ -9,7 +9,8 @@ const Gallery = require('../model/gallery.js'); const url = `http://localhost:${process.env.PORT}`; -require('../server.js'); +const serverToggle = require('./lib/server-toggle.js'); +const server = require('../server.js'); const sampleUser = { @@ -24,6 +25,8 @@ const sampleGallery = { describe('Gallery Routes', function() { + before( done => serverToggle.serverOn(server, done)); + after(done => serverToggle.serverOff(server, done)); afterEach( done => { Promise.all([ User.remove({}), diff --git a/lab-zachary/test/lib/server-toggle.js b/lab-zachary/test/lib/server-toggle.js new file mode 100644 index 0000000..12798f1 --- /dev/null +++ b/lab-zachary/test/lib/server-toggle.js @@ -0,0 +1,35 @@ +'use strict'; + +const debug = require('debug')('cfgram:server-toggle'); +const PORT = process.env.PORT || 3000; + +module.exports = exports = {}; + +exports.serverOn = function(server, done){ + debug('serverOn'); + + if(!server.isRunning) { + server.listen(PORT, () => { + debug('server up:',PORT); + server.isRunning = true; + done(); + }); + return; + } + done(); +}; + +exports.serverOff = function(server, done) { + debug('serverOff'); + + if(server.isRunning) { + server.close( err => { + if (err) console.error(err); + server.isRunning = false; + debug('server down'); + done(); + }); + return; + } + done(); +}; \ No newline at end of file diff --git a/lab-zachary/test/pic-test.js b/lab-zachary/test/pic-test.js new file mode 100644 index 0000000..1e37da1 --- /dev/null +++ b/lab-zachary/test/pic-test.js @@ -0,0 +1,204 @@ +'use strict'; + +const fs = require('fs'); +const request = require('superagent'); +const expect = require('chai').expect; +const Promise = require('bluebird'); + +const Pic = require('../model/pic.js'); +const User = require('../model/user.js'); +const Gallery = require('../model/gallery.js'); + +const serverToggle = require('./lib/server-toggle.js'); +const server = require('../server.js'); +const s3Methods = require('../lib/s3-methods.js'); + +const url = `http://localhost:${process.env.PORT}`; +const sampleUser = { + username: 'sampleUser', + email: 'sample@user.com', + password: '1234' +}; +const sampleGallery = { + name: 'sample Gallery', + desc: 'sample description' +}; +const samplePic = { + name: 'samplePic', + desc: 'sample description', + image: `${__dirname}/data/sample.jpg` //not in data model. added only for test +}; + +describe('Pic Routes', function() { + before( done => serverToggle.serverOn(server, done)); + after(done => serverToggle.serverOff(server, done)); + afterEach( done => { + Promise.all([ + User.remove({}), + Gallery.remove({}), + Pic.remove({}) + ]) + .then( () => done()) + .catch(done); + }); + describe('POST /api/gallery/:galleryID/pic', function () { + before( done => { //new user + new User(sampleUser) + .createPasswordHash(sampleUser.password) + .then( user => user.save()) + .then( user => { + this.tempUser = user; + return user.generateToken(); + }) + .then( token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + before( done => { //new gallery + sampleGallery.userID = this.tempUser._id; + new Gallery(sampleGallery).save() + .then( gallery => { + this.tempGallery = gallery; + done(); + }) + .catch(done); + }); + after( done => { + s3Methods.deleteObjectProm({Bucket: process.env.AWS_BUCKET, Key:this.tempPic.objectKey}) + .then(() => done()) + .catch(done); + }); + describe('with an authorized user, valid galleryID, and valid data', () => { + it('should return a pic object from s3', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id}/pic`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .field('name', samplePic.name) + .field('desc', samplePic.desc) + .attach('image', samplePic.image) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.name).to.equal(samplePic.name); + expect(res.body.galleryID).to.equal(this.tempGallery._id.toString()); + this.tempPic = res.body; + done(); + }); + }); + }); + describe('with authorized user, valid galleryID, and missing data', () => { + it('should return with a 400 error', done => { + request.post(`${url}/api/gallery/${this.tempGallery._id}/pic`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .field('name', samplePic.name) + .field('desc', samplePic.desc) + .end((err, res) => { + expect(res.status).to.equal(400); + expect(err.message).to.equal('Bad Request'); + done(); + }); + }); + }); + describe('with an authorized user and invalid gallery id', () => { + it('should return a 404', done => { + request.post(`${url}/api/gallery/badGalleryID/pic`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .field('name', samplePic.name) + .field('desc', samplePic.desc) + .attach('image', samplePic.image) + .end((err, res) => { + expect(res.status).to.equal(404); + expect(err.message).to.equal('Not Found'); + done(); + }); + }); + }); + + }); + describe('DELETE /api/gallery/:galleryID/pic/:picID', function () { + before( done => { //new user + new User(sampleUser) + .createPasswordHash(sampleUser.password) + .then( user => user.save()) + .then( user => { + this.tempUser = user; + return user.generateToken(); + }) + .then( token => { + this.tempToken = token; + done(); + }) + .catch(done); + }); + before( done => { //new gallery + sampleGallery.userID = this.tempUser._id; + new Gallery(sampleGallery).save() + .then( gallery => { + this.tempGallery = gallery; + done(); + }) + .catch(done); + }); + before( done => { //new pic + + let params = { + ACL: 'public-read', + Bucket: process.env.AWS_BUCKET, + Key: `samplefilehash${Math.floor(Math.random()*100000)}.jpg`, + Body: fs.createReadStream(`${__dirname}/data/sample.jpg`), + }; + s3Methods.uploadObjectProm(params) + .then( s3Data => { + new Pic({ + name: samplePic.name, + desc: samplePic.desc, + userID: this.tempUser._id, + galleryID: this.tempGallery._id, + imageURI: s3Data.Location, + objectKey: s3Data.Key + }) + .save() + .then( pic => { + this.tempPic = pic; + done(); + }) + .catch(done); + }); + }); + describe('with a valid gallery and picture id', () => { + it('should return a 204', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/pic/${this.tempPic._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + if(err) return done(err); + expect(res.status).to.equal(204); + done(); + }); + }); + }); + describe('with an invalid picture id', () => { + it('should return a 404', done => { + request.delete(`${url}/api/gallery/${this.tempGallery._id}/pic/${this.tempPic._id}`) + .set({ + Authorization: `Bearer ${this.tempToken}` + }) + .end((err, res) => { + expect(err.message).to.equal('Not Found'); + expect(res.status).to.equal(404); + done(); + }); + }); + }); + + }); +}); + diff --git a/lab-zachary/test/user-auth-test.js b/lab-zachary/test/user-auth-test.js index 726166e..fe06449 100644 --- a/lab-zachary/test/user-auth-test.js +++ b/lab-zachary/test/user-auth-test.js @@ -6,7 +6,8 @@ const User = require('../model/user.js'); const url = `localhost:${process.env.PORT}`; -require('../server.js'); +const serverToggle = require('./lib/server-toggle.js'); +const server = require('../server.js'); const sampleUser = { username: 'testUser', @@ -15,6 +16,8 @@ const sampleUser = { }; describe('User Auth tests', function() { + before( done => serverToggle.serverOn(server, done)); + after(done => serverToggle.serverOff(server, done)); describe('POST: /api/signup', function() { describe('with a valid body', function() { after( done => { From 99b313f6b815c301e3e4132c3733c3fa66c51d4b Mon Sep 17 00:00:00 2001 From: Zachary Crumbo Date: Thu, 9 Mar 2017 15:08:03 -0800 Subject: [PATCH 5/8] added travis.yml, updated test to properly delete test data file --- lab-zachary/.travis.yml | 19 ++++++++++++++++++ .../data/1943fb9c89c3e9b4822ccc0defb97a6d | Bin 4653 -> 0 bytes lab-zachary/package.json | 3 ++- lab-zachary/test/pic-test.js | 4 +++- 4 files changed, 24 insertions(+), 2 deletions(-) create mode 100644 lab-zachary/.travis.yml delete mode 100644 lab-zachary/data/1943fb9c89c3e9b4822ccc0defb97a6d diff --git a/lab-zachary/.travis.yml b/lab-zachary/.travis.yml new file mode 100644 index 0000000..2c05066 --- /dev/null +++ b/lab-zachary/.travis.yml @@ -0,0 +1,19 @@ + language: node_js + node_js: + - 'stable' + services: + - mongodb + addons: + apt: + sources: + - ubuntu-toolchain-r-test + packages: + - gcc-4.8 + - g++-4.8 + env: + - CXX=g++-4.8 + sudo: required + before_script: npm i + script: + - npm test + - npm run lint \ No newline at end of file diff --git a/lab-zachary/data/1943fb9c89c3e9b4822ccc0defb97a6d b/lab-zachary/data/1943fb9c89c3e9b4822ccc0defb97a6d deleted file mode 100644 index 98c5b2467fcf1f58eb8a4d0b9f8f94786d9135e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 4653 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 TT%@MM?K)X!8QupK*cko~qX&Ar diff --git a/lab-zachary/package.json b/lab-zachary/package.json index 626b186..a4eaffe 100644 --- a/lab-zachary/package.json +++ b/lab-zachary/package.json @@ -5,7 +5,8 @@ "main": "gulpfile.js", "scripts": { "test": "DEBUG='cfgram*' mocha", - "start": "DEBUG='cfgram*' nodemon server.js" + "start": "DEBUG='cfgram*' nodemon server.js", + "lint": "gulp lint" }, "keywords": [], "author": "", diff --git a/lab-zachary/test/pic-test.js b/lab-zachary/test/pic-test.js index 1e37da1..ea01326 100644 --- a/lab-zachary/test/pic-test.js +++ b/lab-zachary/test/pic-test.js @@ -4,6 +4,7 @@ const fs = require('fs'); const request = require('superagent'); const expect = require('chai').expect; const Promise = require('bluebird'); +const del = require('del'); const Pic = require('../model/pic.js'); const User = require('../model/user.js'); @@ -36,7 +37,8 @@ describe('Pic Routes', function() { Promise.all([ User.remove({}), Gallery.remove({}), - Pic.remove({}) + Pic.remove({}), + del([`${__dirname}/../data/*`]) ]) .then( () => done()) .catch(done); From 7da0ff2c62638ae9bda3f895aea1914f482a6187 Mon Sep 17 00:00:00 2001 From: Zachary Crumbo Date: Thu, 9 Mar 2017 15:10:33 -0800 Subject: [PATCH 6/8] moved travis.yml to project root --- lab-zachary/.travis.yml => .travis.yml | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename lab-zachary/.travis.yml => .travis.yml (100%) diff --git a/lab-zachary/.travis.yml b/.travis.yml similarity index 100% rename from lab-zachary/.travis.yml rename to .travis.yml From 0e10d97fa1881e99a930c9587702ce16d377a3ef Mon Sep 17 00:00:00 2001 From: Zachary Crumbo Date: Thu, 9 Mar 2017 15:25:16 -0800 Subject: [PATCH 7/8] updated travis to point to subdirectory --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 2c05066..58437f2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -15,5 +15,6 @@ sudo: required before_script: npm i script: + - cd lab-zachary - npm test - - npm run lint \ No newline at end of file + - npm run lint From 9a3199e4b5a700bcea9562fc121d055eaf31e89c Mon Sep 17 00:00:00 2001 From: Zachary Crumbo Date: Thu, 9 Mar 2017 15:31:41 -0800 Subject: [PATCH 8/8] updated travis yml again --- .travis.yml | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.travis.yml b/.travis.yml index 58437f2..f300667 100644 --- a/.travis.yml +++ b/.travis.yml @@ -13,8 +13,9 @@ env: - CXX=g++-4.8 sudo: required - before_script: npm i - script: + before_script: - cd lab-zachary + - npm i + script: - npm test - npm run lint