From 5d9fd0ebdd63dd694bb5647cdb65cbabc481a540 Mon Sep 17 00:00:00 2001 From: shaallfar Date: Thu, 15 Dec 2016 11:46:16 -0800 Subject: [PATCH 1/4] tentative submission - missing gulp,eslintrc, and readme --- lab-shawn/.gitignore | 127 ++++++++++++++++++++++++++++ lab-shawn/lib/parse-json.js | 25 ++++++ lab-shawn/lib/parse-url.js | 10 +++ lab-shawn/lib/router.js | 56 ++++++++++++ lab-shawn/lib/storage.js | 43 ++++++++++ lab-shawn/model/person.js | 12 +++ lab-shawn/package.json | 24 ++++++ lab-shawn/server.js | 70 +++++++++++++++ lab-shawn/test/person-route-test.js | 57 +++++++++++++ 9 files changed, 424 insertions(+) create mode 100644 lab-shawn/.gitignore create mode 100644 lab-shawn/lib/parse-json.js create mode 100644 lab-shawn/lib/parse-url.js create mode 100644 lab-shawn/lib/router.js create mode 100644 lab-shawn/lib/storage.js create mode 100644 lab-shawn/model/person.js create mode 100644 lab-shawn/package.json create mode 100644 lab-shawn/server.js create mode 100644 lab-shawn/test/person-route-test.js diff --git a/lab-shawn/.gitignore b/lab-shawn/.gitignore new file mode 100644 index 0000000..96bb76c --- /dev/null +++ b/lab-shawn/.gitignore @@ -0,0 +1,127 @@ +Created by https://www.gitignore.io/api/macos,node,vim,windows,linux + +out.bmp +### macOS ### +*.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 + + +### Node ### +node_modules +# Logs +logs +*.log +npm-debug.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 + +# node-waf configuration +.lock-wscript + +# Compiled binary addons (http://nodejs.org/api/addons.html) +build/Release + +# Dependency directories +node_modules +jspm_packages + +# 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 + + + +### Vim ### +# swap +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + + +### Windows ### +# Windows image file caches +Thumbs.db +ehthumbs.db + +# Folder config file +Desktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msm +*.msp + +# Windows shortcuts +*.lnk + + +### 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* diff --git a/lab-shawn/lib/parse-json.js b/lab-shawn/lib/parse-json.js new file mode 100644 index 0000000..8e23bab --- /dev/null +++ b/lab-shawn/lib/parse-json.js @@ -0,0 +1,25 @@ +'use strict'; + +module.exports = function(req){ + return new Promise((resolve,reject) => { + if(req.method === 'POST' || req.method === 'PUT'){ + var body = ''; + + req.on('data',data => { + body += data.toString(); + }); + + req.on('end', () =>{ + try{ + req.body = JSON.parse(body); + resolve(req); + }catch(err){ + console.error(err); + reject(err); + } + }); + return; + } + resolve(); + }); +} diff --git a/lab-shawn/lib/parse-url.js b/lab-shawn/lib/parse-url.js new file mode 100644 index 0000000..f9c85e5 --- /dev/null +++ b/lab-shawn/lib/parse-url.js @@ -0,0 +1,10 @@ +'use strict'; + +const parseUrl = require('url').parse; +const parseQuery = require('querystring').parse; + +module.exports = function(req){ + req.url = parseUrl(req.url); + req.url.query = parseQuery(req.url.query); + return Promise.resolve(req); +}; diff --git a/lab-shawn/lib/router.js b/lab-shawn/lib/router.js new file mode 100644 index 0000000..6dba04c --- /dev/null +++ b/lab-shawn/lib/router.js @@ -0,0 +1,56 @@ +'use strict'; + +const parseUrl = require('./parse-url.js'); +const parseJSON = require('./parse-json.js'); + +const Router = module.exports = function(){ + this.routes = { + GET: {}, + POST: {}, + PUT: {}, + DELETE: {} + }; +}; + +Router.prototype.get = function(endpoint,callback){ + this.routes.GET[endpoint] = callback; +}; + +Router.prototype.post = function(endpoint,callback){ + this.routes.POST[endpoint] = callback; +}; + +Router.prototype.put = function(endpoint,callback){ + this.routes.PUT[endpoint] = callback; +}; + +Router.prototype.delete = function(endpoint,callback){ + this.routes.DELETE[endpoint] = callback; +}; + +Router.prototype.route = function(){ + return (req,res) => { + Promise.all([ + parseUrl(req), + parseJSON(req) + ]) + .then( () => { + if(typeof this.routes[req.method][req.url.pathname] === 'function'){ + this.routes[req.method][req.url.pathname](req,res); + return; + } + + console.error('route not found'); + + res.writeHead(404,{'Content-Type':'text/plain'}); + res.write('route not found'); + res.end(); + }) + .catch(err => { + console.error(err); + res.writeHead(400, {'Content-Type': 'text/plain'}); + res.write('bad request'); + res.end(); + }); + }; +}; diff --git a/lab-shawn/lib/storage.js b/lab-shawn/lib/storage.js new file mode 100644 index 0000000..4cb4b0a --- /dev/null +++ b/lab-shawn/lib/storage.js @@ -0,0 +1,43 @@ +'use strict'; + +const storage = {}; + +module.exports = exports = {}; + +exports.createPerson = function(schemaName, person){ + if(!schemaName) return Promise.reject(new Error('expected schema name')); + if(!person) return Promise.reject(new Error('expected a person')); + if(!storage[schemaName]) storage[schemaName] = {}; + + storage[schemaName][person.id] = person; + return Promise.resolve(person); +}; + +exports.fetchPerson = function(schemaName,id){ + return new Promise((resolve,reject) => { + if(!schemaName) return reject(new Error('expected schema name')); + if(!id) return reject(new Error('expected an id')); + + var schema = storage[schemaName]; + if(!schema) return reject(new Error('schema not found')); + + var person = schema[id]; + if(!person) return reject(new Error('person not found')); + + resolve(person); + }); + +}; +exports.deletePerson = function(schemaName,id){ + return new Promise((resolve,reject) => { + if(!schemaName) return reject(new Error('expected schema name')); + if(!id) return reject(new Error('expected id')); + + var schema = storage[schemaName]; + if(!schema) return reject(new Error('schema not found')); + + delete schema[id]; + resolve(); + + }); +}; diff --git a/lab-shawn/model/person.js b/lab-shawn/model/person.js new file mode 100644 index 0000000..993e410 --- /dev/null +++ b/lab-shawn/model/person.js @@ -0,0 +1,12 @@ +'use strict'; + +const uuid = require('node-uuid'); + +module.exports = function(name, gender){ + if(!name) throw new Error('expected name'); + if(!gender) throw new Error('expected gender'); + + this.id = uuid.v1(); + this.name = name; + this.gender = gender; +} diff --git a/lab-shawn/package.json b/lab-shawn/package.json new file mode 100644 index 0000000..cbb5eb7 --- /dev/null +++ b/lab-shawn/package.json @@ -0,0 +1,24 @@ +{ + "name": "lab-shawn", + "version": "1.0.0", + "description": "", + "main": "server.js", + "directories": { + "test": "test" + }, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "node-uuid": "^1.4.7" + }, + "devDependencies": { + "chai": "^3.5.0", + "mocha": "^3.2.0", + "superagent": "^3.3.0" + } +} diff --git a/lab-shawn/server.js b/lab-shawn/server.js new file mode 100644 index 0000000..044399d --- /dev/null +++ b/lab-shawn/server.js @@ -0,0 +1,70 @@ +'use strict'; + +const http = require('http'); +const Person = require('./model/person.js'); +const Router = require('./lib/router.js'); +const storage = require('./lib/storage.js'); +const PORT = process.env.PORT || 3000; + +const router = new Router(); + +router.get('/api/person', function(req,res){ + if(!req.url.query.id){ + res.writeHead(400,{'Content-Type':'text/plain'}); + res.write('bad request'); + res.end(); + } + if(req.url.query.id){ + storage.fetchPerson('person',req.url.query.id) + .then( person => { + res.writeHead(200, {'Content-Type':'application/json'}); + res.write(JSON.stringify(person)); + res.end(); + }) + .catch(err => { + console.error(err); + res.writeHead(404,{'Content-Type':'text/plain'}); + res.write('person not found'); + res.end(); + }); + return; + } +}); + +router.post('/api/person',function(req,res){ + try{ + var person = new Person(req.body.name,req.body.gender); + storage.createPerson('person',person); + res.writeHead(200, {'Content-Type':'application/json'}); + res.write(JSON.stringify(person)); + res.end(); + }catch(err){ + console.error(err); + res.writeHead(400, {'Content-Type':'text/plain'}); + res.write('bad request'); + res.end(); + } +}); + +router.delete('/api/person', function(req,res){ + if(req.url.query.id){ + storage.deletePerson('person',req.url.query.id) + .then(person => { + res.writeHead(204,{'Content-Type':'application/json'}); + res.write('Person Removed'); + res.end(); + }) + .catch(err => { + console.error(err); + res.writeHead(404,{'Content-Type': 'text/plain'}); + res.write('person not found'); + res.end(); + }); + } +}); + +const server = http.createServer(router.route()); + +server.listen(PORT, () => { + console.log('Served On:', PORT); +}); diff --git a/lab-shawn/test/person-route-test.js b/lab-shawn/test/person-route-test.js new file mode 100644 index 0000000..cb29ae8 --- /dev/null +++ b/lab-shawn/test/person-route-test.js @@ -0,0 +1,57 @@ +'use strict'; + +const request = require('superagent'); +const expect = require('chai').expect; + +require('../server.js'); + +describe('Person Routes', function(){ + var person = null; + + describe('POST: /api/person', function(){ + it('should return a person', function(done){ + request.post('localhost:8000/api/person') + .send({name:'test name', gender:'male'}) + .end((err,res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + person = res.body; + done(); + }); + }); + it('should return bad request', function(done){ + request.post('localhost:8000/api/person') + .send({name:'test name'}) + .end((err) => { + expect(err.status).to.equal(400); + done(); + }); + }); + }); + + describe('GET: /api/person', function(){ + it('should return with person', function(done){ + request.get(`localhost:8000/api/person?id=${person.id}`) + .end((err,res) => { + if(err) return done(err); + expect(res.status).to.equal(200); + person = res.body; + done(); + }); + }); + it('should return not found', function(done){ + request.get('localhost:8000/api/person?id=123') + .end((err) => { + expect(err.status).to.equal(404); + done(); + }); + }); + it('should return bad request', function(done){ + request.get('localhost:8000/api/person') + .end((err) => { + expect(err.status).to.equal(400); + done(); + }); + }); + }); +}); From cb3e0b100668378fe380ac7284daf73f3b9a8ef3 Mon Sep 17 00:00:00 2001 From: shaallfar Date: Fri, 16 Dec 2016 11:47:49 -0800 Subject: [PATCH 2/4] refactored code, added files, & README.md --- lab-shawn/.eslintrc | 21 +++++++ lab-shawn/README.md | 36 ++++++++++++ .../00b17950-c35b-11e6-91c0-2f447f841b80.json | 1 + .../282e3b50-c34f-11e6-a8f1-49626432423e.json | 1 + lab-shawn/gulpfile.js | 23 ++++++++ lab-shawn/lib/parse-json.js | 2 +- lab-shawn/lib/response.js | 19 ++++++ lab-shawn/lib/router.js | 9 +-- lab-shawn/lib/storage.js | 46 +++++++-------- lab-shawn/model/person.js | 2 +- lab-shawn/package.json | 4 ++ lab-shawn/route/person-route.js | 46 +++++++++++++++ lab-shawn/server.js | 58 +------------------ lab-shawn/test/person-route-test.js | 12 ++-- 14 files changed, 183 insertions(+), 97 deletions(-) create mode 100644 lab-shawn/.eslintrc create mode 100644 lab-shawn/README.md create mode 100644 lab-shawn/data/person/00b17950-c35b-11e6-91c0-2f447f841b80.json create mode 100644 lab-shawn/data/person/282e3b50-c34f-11e6-a8f1-49626432423e.json create mode 100644 lab-shawn/gulpfile.js create mode 100644 lab-shawn/lib/response.js create mode 100644 lab-shawn/route/person-route.js diff --git a/lab-shawn/.eslintrc b/lab-shawn/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/lab-shawn/.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-shawn/README.md b/lab-shawn/README.md new file mode 100644 index 0000000..d764589 --- /dev/null +++ b/lab-shawn/README.md @@ -0,0 +1,36 @@ +# **Vanilla HTTP RESTful API** + +## **Overview** + + This application is a HTTP RESTful API written in vanilla JavaScript. It utilizes the GET, POST, & DELETE methods to fetch, add, and remove files in a file system. + +## **How To Use API** + * Clone this repository + * Open a terminal and run `npm i` to install all the application dependencies + + #### Dependencies + * bluebird 3.4.6 + * node-uuid 1.4.7 + + #### Dev dependencies + * chai 3.5.0 + * gulp 3.9.1 + * gulp-eslint 3.0.1 + * gulp-mocha 3.0.1 + * mocha 3.2.0 + * superagent 3.3.0 + + +### **Run your server** + `node server.js` + +In a new terminal window/tab run your HTTP method commands + +### **POST Request** + `http POST localhost:[port number] name='[name]' gender='[gender]'` + +### **GET Request** + `http GET localhost:[port number]?id='[id]'` + +### **DELETE Request** + `http DELETE localhost:[port number]?id='[id]'` diff --git a/lab-shawn/data/person/00b17950-c35b-11e6-91c0-2f447f841b80.json b/lab-shawn/data/person/00b17950-c35b-11e6-91c0-2f447f841b80.json new file mode 100644 index 0000000..ac3e322 --- /dev/null +++ b/lab-shawn/data/person/00b17950-c35b-11e6-91c0-2f447f841b80.json @@ -0,0 +1 @@ +{"id":"00b17950-c35b-11e6-91c0-2f447f841b80","name":"test name","gender":"male"} \ No newline at end of file diff --git a/lab-shawn/data/person/282e3b50-c34f-11e6-a8f1-49626432423e.json b/lab-shawn/data/person/282e3b50-c34f-11e6-a8f1-49626432423e.json new file mode 100644 index 0000000..7f0e110 --- /dev/null +++ b/lab-shawn/data/person/282e3b50-c34f-11e6-a8f1-49626432423e.json @@ -0,0 +1 @@ +{"id":"282e3b50-c34f-11e6-a8f1-49626432423e","name":"test name","gender":"male"} \ No newline at end of file diff --git a/lab-shawn/gulpfile.js b/lab-shawn/gulpfile.js new file mode 100644 index 0000000..45a1a50 --- /dev/null +++ b/lab-shawn/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']); diff --git a/lab-shawn/lib/parse-json.js b/lab-shawn/lib/parse-json.js index 8e23bab..26d4fcc 100644 --- a/lab-shawn/lib/parse-json.js +++ b/lab-shawn/lib/parse-json.js @@ -22,4 +22,4 @@ module.exports = function(req){ } resolve(); }); -} +}; diff --git a/lab-shawn/lib/response.js b/lab-shawn/lib/response.js new file mode 100644 index 0000000..6a69540 --- /dev/null +++ b/lab-shawn/lib/response.js @@ -0,0 +1,19 @@ +'use strict'; + +module.exports = exports = {}; + +exports.sendJSON = function(res,status,data){ + res.writeHead(status,{ + 'Content-Type':'application/json' + }); + res.write(JSON.stringify(data)); + res.end(); +}; + +exports.sendText = function(res,status,msg){ + res.writeHead(status,{ + 'Content-Type':'text/plain' + }); + res.write(msg); + res.end(); +}; diff --git a/lab-shawn/lib/router.js b/lab-shawn/lib/router.js index 6dba04c..fe03588 100644 --- a/lab-shawn/lib/router.js +++ b/lab-shawn/lib/router.js @@ -41,16 +41,11 @@ Router.prototype.route = function(){ } console.error('route not found'); - - res.writeHead(404,{'Content-Type':'text/plain'}); - res.write('route not found'); - res.end(); + res.sendText(res,404,'route not found'); }) .catch(err => { console.error(err); - res.writeHead(400, {'Content-Type': 'text/plain'}); - res.write('bad request'); - res.end(); + res.sendText(res,400,'bad request'); }); }; }; diff --git a/lab-shawn/lib/storage.js b/lab-shawn/lib/storage.js index 4cb4b0a..df4dfed 100644 --- a/lab-shawn/lib/storage.js +++ b/lab-shawn/lib/storage.js @@ -1,43 +1,37 @@ 'use strict'; -const storage = {}; +const Promise = require('bluebird'); +const fs = Promise.promisifyAll(require('fs'),{suffix: 'Prom'}); module.exports = exports = {}; exports.createPerson = function(schemaName, person){ if(!schemaName) return Promise.reject(new Error('expected schema name')); if(!person) return Promise.reject(new Error('expected a person')); - if(!storage[schemaName]) storage[schemaName] = {}; - storage[schemaName][person.id] = person; - return Promise.resolve(person); + let json = JSON.stringify(person); + return fs.writeFileProm(`${__dirname}/../data/${schemaName}/${person.id}.json`,json) + .then( () => person ) + .catch( err => Promise.reject(err)); }; exports.fetchPerson = function(schemaName,id){ - return new Promise((resolve,reject) => { - if(!schemaName) return reject(new Error('expected schema name')); - if(!id) return reject(new Error('expected an id')); - - var schema = storage[schemaName]; - if(!schema) return reject(new Error('schema not found')); - - var person = schema[id]; - if(!person) return reject(new Error('person not found')); - - resolve(person); - }); - + if(!schemaName) return Promise.reject(new Error('expected schema name')); + if(!id) return Promise.reject(new Error('expected an id')); + + return fs.readFileProm(`${__dirname}/../data/${schemaName}/${id}.json`) + .then( data => { + let person = JSON.parse(data.toString()); + return person; + }) + .catch(err => Promise.reject(err)); }; -exports.deletePerson = function(schemaName,id){ - return new Promise((resolve,reject) => { - if(!schemaName) return reject(new Error('expected schema name')); - if(!id) return reject(new Error('expected id')); - var schema = storage[schemaName]; - if(!schema) return reject(new Error('schema not found')); +exports.deletePerson = function(schemaName,id){ + if(!schemaName) return Promise.reject(new Error('expected schema name')); + if(!id) return Promise.reject(new Error('expected id')); - delete schema[id]; - resolve(); + return fs.unlinkProm(`${__dirname}/../data/${schemaName}/${id}.json`) + .catch(err => Promise.reject(err)); - }); }; diff --git a/lab-shawn/model/person.js b/lab-shawn/model/person.js index 993e410..95220b0 100644 --- a/lab-shawn/model/person.js +++ b/lab-shawn/model/person.js @@ -9,4 +9,4 @@ module.exports = function(name, gender){ this.id = uuid.v1(); this.name = name; this.gender = gender; -} +}; diff --git a/lab-shawn/package.json b/lab-shawn/package.json index cbb5eb7..11fb171 100644 --- a/lab-shawn/package.json +++ b/lab-shawn/package.json @@ -14,10 +14,14 @@ "author": "", "license": "ISC", "dependencies": { + "bluebird": "^3.4.6", "node-uuid": "^1.4.7" }, "devDependencies": { "chai": "^3.5.0", + "gulp": "^3.9.1", + "gulp-eslint": "^3.0.1", + "gulp-mocha": "^3.0.1", "mocha": "^3.2.0", "superagent": "^3.3.0" } diff --git a/lab-shawn/route/person-route.js b/lab-shawn/route/person-route.js new file mode 100644 index 0000000..15d9371 --- /dev/null +++ b/lab-shawn/route/person-route.js @@ -0,0 +1,46 @@ +'use strict'; + +const storage = require('../lib/storage.js'); +const Person = require('../model/person.js'); +const response = require('../lib/response.js'); + +module.exports = function(router){ + router.get('/api/person', function(req,res){ + if(req.url.query.id){ + storage.fetchPerson('person',req.url.query.id) + .then( person => { + response.sendJSON(res,200,person); + }) + .catch(err => { + console.error(err); + response.sendText(res,404,'person not found'); + }); + return; + } + response.sendText(res,400,'bad request'); + }); + + router.post('/api/person',function(req,res){ + try{ + var person = new Person(req.body.name,req.body.gender); + storage.createPerson('person',person); + response.sendJSON(res,200,person); + }catch(err){ + console.error(err); + response.sendText(res,400,'bad request'); + } + }); + + router.delete('/api/person', function(req,res){ + if(req.url.query.id){ + storage.deletePerson('person',req.url.query.id) + .then( ()=> { + response.sendJSON(res,204,'person removed'); + }) + .catch(err => { + console.error(err); + response.sendText(res,404,'person not found'); + }); + } + }); +}; diff --git a/lab-shawn/server.js b/lab-shawn/server.js index 044399d..8c23783 100644 --- a/lab-shawn/server.js +++ b/lab-shawn/server.js @@ -1,67 +1,11 @@ 'use strict'; const http = require('http'); -const Person = require('./model/person.js'); const Router = require('./lib/router.js'); -const storage = require('./lib/storage.js'); const PORT = process.env.PORT || 3000; - const router = new Router(); -router.get('/api/person', function(req,res){ - if(!req.url.query.id){ - res.writeHead(400,{'Content-Type':'text/plain'}); - res.write('bad request'); - res.end(); - } - if(req.url.query.id){ - storage.fetchPerson('person',req.url.query.id) - .then( person => { - res.writeHead(200, {'Content-Type':'application/json'}); - res.write(JSON.stringify(person)); - res.end(); - }) - .catch(err => { - console.error(err); - res.writeHead(404,{'Content-Type':'text/plain'}); - res.write('person not found'); - res.end(); - }); - return; - } -}); - -router.post('/api/person',function(req,res){ - try{ - var person = new Person(req.body.name,req.body.gender); - storage.createPerson('person',person); - res.writeHead(200, {'Content-Type':'application/json'}); - res.write(JSON.stringify(person)); - res.end(); - }catch(err){ - console.error(err); - res.writeHead(400, {'Content-Type':'text/plain'}); - res.write('bad request'); - res.end(); - } -}); - -router.delete('/api/person', function(req,res){ - if(req.url.query.id){ - storage.deletePerson('person',req.url.query.id) - .then(person => { - res.writeHead(204,{'Content-Type':'application/json'}); - res.write('Person Removed'); - res.end(); - }) - .catch(err => { - console.error(err); - res.writeHead(404,{'Content-Type': 'text/plain'}); - res.write('person not found'); - res.end(); - }); - } -}); +require('./route/person-route.js')(router); const server = http.createServer(router.route()); diff --git a/lab-shawn/test/person-route-test.js b/lab-shawn/test/person-route-test.js index cb29ae8..34c6777 100644 --- a/lab-shawn/test/person-route-test.js +++ b/lab-shawn/test/person-route-test.js @@ -2,6 +2,8 @@ const request = require('superagent'); const expect = require('chai').expect; +const PORT = process.env.PORT || 3000; + require('../server.js'); @@ -10,7 +12,7 @@ describe('Person Routes', function(){ describe('POST: /api/person', function(){ it('should return a person', function(done){ - request.post('localhost:8000/api/person') + request.post(`localhost:${PORT}/api/person`) .send({name:'test name', gender:'male'}) .end((err,res) => { if(err) return done(err); @@ -20,7 +22,7 @@ describe('Person Routes', function(){ }); }); it('should return bad request', function(done){ - request.post('localhost:8000/api/person') + request.post(`localhost:${PORT}/api/person`) .send({name:'test name'}) .end((err) => { expect(err.status).to.equal(400); @@ -31,7 +33,7 @@ describe('Person Routes', function(){ describe('GET: /api/person', function(){ it('should return with person', function(done){ - request.get(`localhost:8000/api/person?id=${person.id}`) + request.get(`localhost:${PORT}/api/person?id=${person.id}`) .end((err,res) => { if(err) return done(err); expect(res.status).to.equal(200); @@ -40,14 +42,14 @@ describe('Person Routes', function(){ }); }); it('should return not found', function(done){ - request.get('localhost:8000/api/person?id=123') + request.get(`localhost:${PORT}/api/person?id=123`) .end((err) => { expect(err.status).to.equal(404); done(); }); }); it('should return bad request', function(done){ - request.get('localhost:8000/api/person') + request.get(`localhost:${PORT}/api/person`) .end((err) => { expect(err.status).to.equal(400); done(); From dd76dc85ce2d367b894646cebdc5a3f6a4a66713 Mon Sep 17 00:00:00 2001 From: shaallfar Date: Tue, 20 Dec 2016 11:37:27 -0800 Subject: [PATCH 3/4] express refactor --- lab-shawn/README.md | 19 ++----- .../282e3b50-c34f-11e6-a8f1-49626432423e.json | 1 - .../96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json | 1 + lab-shawn/gulpfile.js | 2 +- lab-shawn/lib/parse-json.js | 25 --------- lab-shawn/lib/parse-url.js | 10 ---- lab-shawn/lib/response.js | 19 ------- lab-shawn/lib/router.js | 51 ------------------ lab-shawn/lib/storage.js | 34 +++++++----- lab-shawn/model/person.js | 33 ++++++++++-- lab-shawn/package.json | 6 ++- lab-shawn/route/person-route.js | 46 ---------------- lab-shawn/server.js | 54 ++++++++++++++++--- lab-shawn/test/person-route-test.js | 16 ++++++ 14 files changed, 125 insertions(+), 192 deletions(-) delete mode 100644 lab-shawn/data/person/282e3b50-c34f-11e6-a8f1-49626432423e.json create mode 100644 lab-shawn/data/person/96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json delete mode 100644 lab-shawn/lib/parse-json.js delete mode 100644 lab-shawn/lib/parse-url.js delete mode 100644 lab-shawn/lib/response.js delete mode 100644 lab-shawn/lib/router.js delete mode 100644 lab-shawn/route/person-route.js diff --git a/lab-shawn/README.md b/lab-shawn/README.md index d764589..4d07b98 100644 --- a/lab-shawn/README.md +++ b/lab-shawn/README.md @@ -8,29 +8,16 @@ * Clone this repository * Open a terminal and run `npm i` to install all the application dependencies - #### Dependencies - * bluebird 3.4.6 - * node-uuid 1.4.7 - - #### Dev dependencies - * chai 3.5.0 - * gulp 3.9.1 - * gulp-eslint 3.0.1 - * gulp-mocha 3.0.1 - * mocha 3.2.0 - * superagent 3.3.0 - - ### **Run your server** `node server.js` In a new terminal window/tab run your HTTP method commands ### **POST Request** - `http POST localhost:[port number] name='[name]' gender='[gender]'` + `http POST localhost:[port number]/api/person name='[name]' gender='[gender]'` ### **GET Request** - `http GET localhost:[port number]?id='[id]'` + `http GET localhost:[port number]/api/person?id='[id]'` ### **DELETE Request** - `http DELETE localhost:[port number]?id='[id]'` + `http DELETE localhost:[port number]/api/person?id='[id]'` diff --git a/lab-shawn/data/person/282e3b50-c34f-11e6-a8f1-49626432423e.json b/lab-shawn/data/person/282e3b50-c34f-11e6-a8f1-49626432423e.json deleted file mode 100644 index 7f0e110..0000000 --- a/lab-shawn/data/person/282e3b50-c34f-11e6-a8f1-49626432423e.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"282e3b50-c34f-11e6-a8f1-49626432423e","name":"test name","gender":"male"} \ No newline at end of file diff --git a/lab-shawn/data/person/96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json b/lab-shawn/data/person/96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json new file mode 100644 index 0000000..6481380 --- /dev/null +++ b/lab-shawn/data/person/96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json @@ -0,0 +1 @@ +{"id":"96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e","name":"test name","gender":"male"} \ No newline at end of file diff --git a/lab-shawn/gulpfile.js b/lab-shawn/gulpfile.js index 45a1a50..16a8ef7 100644 --- a/lab-shawn/gulpfile.js +++ b/lab-shawn/gulpfile.js @@ -17,7 +17,7 @@ gulp.task('lint', function(){ }); gulp.task('dev', function(){ - gulp.watch(['**/*.js', '!node_modules'],['lint']); + gulp.watch(['**/*.js', '!node_modules'],['lint']['test']); }); gulp.task('default',['dev']); diff --git a/lab-shawn/lib/parse-json.js b/lab-shawn/lib/parse-json.js deleted file mode 100644 index 26d4fcc..0000000 --- a/lab-shawn/lib/parse-json.js +++ /dev/null @@ -1,25 +0,0 @@ -'use strict'; - -module.exports = function(req){ - return new Promise((resolve,reject) => { - if(req.method === 'POST' || req.method === 'PUT'){ - var body = ''; - - req.on('data',data => { - body += data.toString(); - }); - - req.on('end', () =>{ - try{ - req.body = JSON.parse(body); - resolve(req); - }catch(err){ - console.error(err); - reject(err); - } - }); - return; - } - resolve(); - }); -}; diff --git a/lab-shawn/lib/parse-url.js b/lab-shawn/lib/parse-url.js deleted file mode 100644 index f9c85e5..0000000 --- a/lab-shawn/lib/parse-url.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict'; - -const parseUrl = require('url').parse; -const parseQuery = require('querystring').parse; - -module.exports = function(req){ - req.url = parseUrl(req.url); - req.url.query = parseQuery(req.url.query); - return Promise.resolve(req); -}; diff --git a/lab-shawn/lib/response.js b/lab-shawn/lib/response.js deleted file mode 100644 index 6a69540..0000000 --- a/lab-shawn/lib/response.js +++ /dev/null @@ -1,19 +0,0 @@ -'use strict'; - -module.exports = exports = {}; - -exports.sendJSON = function(res,status,data){ - res.writeHead(status,{ - 'Content-Type':'application/json' - }); - res.write(JSON.stringify(data)); - res.end(); -}; - -exports.sendText = function(res,status,msg){ - res.writeHead(status,{ - 'Content-Type':'text/plain' - }); - res.write(msg); - res.end(); -}; diff --git a/lab-shawn/lib/router.js b/lab-shawn/lib/router.js deleted file mode 100644 index fe03588..0000000 --- a/lab-shawn/lib/router.js +++ /dev/null @@ -1,51 +0,0 @@ -'use strict'; - -const parseUrl = require('./parse-url.js'); -const parseJSON = require('./parse-json.js'); - -const Router = module.exports = function(){ - this.routes = { - GET: {}, - POST: {}, - PUT: {}, - DELETE: {} - }; -}; - -Router.prototype.get = function(endpoint,callback){ - this.routes.GET[endpoint] = callback; -}; - -Router.prototype.post = function(endpoint,callback){ - this.routes.POST[endpoint] = callback; -}; - -Router.prototype.put = function(endpoint,callback){ - this.routes.PUT[endpoint] = callback; -}; - -Router.prototype.delete = function(endpoint,callback){ - this.routes.DELETE[endpoint] = callback; -}; - -Router.prototype.route = function(){ - return (req,res) => { - Promise.all([ - parseUrl(req), - parseJSON(req) - ]) - .then( () => { - if(typeof this.routes[req.method][req.url.pathname] === 'function'){ - this.routes[req.method][req.url.pathname](req,res); - return; - } - - console.error('route not found'); - res.sendText(res,404,'route not found'); - }) - .catch(err => { - console.error(err); - res.sendText(res,400,'bad request'); - }); - }; -}; diff --git a/lab-shawn/lib/storage.js b/lab-shawn/lib/storage.js index df4dfed..79e3563 100644 --- a/lab-shawn/lib/storage.js +++ b/lab-shawn/lib/storage.js @@ -2,36 +2,46 @@ const Promise = require('bluebird'); const fs = Promise.promisifyAll(require('fs'),{suffix: 'Prom'}); +const createError = require('http-errors'); +const debug = require('debug')('person:storage'); + module.exports = exports = {}; -exports.createPerson = function(schemaName, person){ - if(!schemaName) return Promise.reject(new Error('expected schema name')); - if(!person) return Promise.reject(new Error('expected a person')); +exports.createInstance = function(schemaName, person){ + debug('createInstance'); + + if(!schemaName) return Promise.reject(createError(400, 'expected schema name')); + if(!person) return Promise.reject(createError(400, 'expected a person')); let json = JSON.stringify(person); + return fs.writeFileProm(`${__dirname}/../data/${schemaName}/${person.id}.json`,json) .then( () => person ) - .catch( err => Promise.reject(err)); + .catch( err => Promise.reject(createError(404,err.message))); }; -exports.fetchPerson = function(schemaName,id){ - if(!schemaName) return Promise.reject(new Error('expected schema name')); - if(!id) return Promise.reject(new Error('expected an id')); +exports.fetchInstance = function(schemaName,id){ + debug('fetchInstance'); + + if(!schemaName) return Promise.reject(createError(400, 'expected schema name')); + if(!id) return Promise.reject(createError(400, 'expected an id')); return fs.readFileProm(`${__dirname}/../data/${schemaName}/${id}.json`) .then( data => { let person = JSON.parse(data.toString()); return person; }) - .catch(err => Promise.reject(err)); + .catch(err => Promise.reject(createError(404,err.message))); }; -exports.deletePerson = function(schemaName,id){ - if(!schemaName) return Promise.reject(new Error('expected schema name')); - if(!id) return Promise.reject(new Error('expected id')); +exports.deleteInstance = function(schemaName,id){ + debug('deleteInstance'); + + if(!schemaName) return Promise.reject(createError(400, 'expected schema name')); + if(!id) return Promise.reject(createError(400, 'expected id')); return fs.unlinkProm(`${__dirname}/../data/${schemaName}/${id}.json`) - .catch(err => Promise.reject(err)); + .catch(err => Promise.reject(createError(404,err.message))); }; diff --git a/lab-shawn/model/person.js b/lab-shawn/model/person.js index 95220b0..a1fface 100644 --- a/lab-shawn/model/person.js +++ b/lab-shawn/model/person.js @@ -1,12 +1,39 @@ 'use strict'; const uuid = require('node-uuid'); +const createError = require('http-errors'); +const debug = require('debug')('person:person'); +const storage = require('../lib/storage.js'); -module.exports = function(name, gender){ - if(!name) throw new Error('expected name'); - if(!gender) throw new Error('expected gender'); + +const Person = module.exports = function(name, gender){ + debug('person constructor'); + + if(!name) throw createError(400, 'expected name'); + if(!gender) throw createError(400, 'expected gender'); this.id = uuid.v1(); this.name = name; this.gender = gender; }; + +Person.createPerson = function(_person){ + debug('createPerson'); + + try{ + let person = new Person(_person.name,_person.gender); + return storage.createInstance('person', person); + } catch (err){ + return Promise.reject(createError(400,err.message)); + } +}; + +Person.fetchPerson = function(id){ + debug('fetchPerson'); + return storage.fetchInstance('person',id); +}; + +Person.deletePerson = function(id){ + debug('deletePerson'); + return storage.deleteInstance('person',id); +}; diff --git a/lab-shawn/package.json b/lab-shawn/package.json index 11fb171..f8dbf46 100644 --- a/lab-shawn/package.json +++ b/lab-shawn/package.json @@ -8,13 +8,17 @@ }, "scripts": { "test": "echo \"Error: no test specified\" && exit 1", - "start": "node server.js" + "start": "DEBUG='person*' node server.js" }, "keywords": [], "author": "", "license": "ISC", "dependencies": { "bluebird": "^3.4.6", + "body-parser": "^1.15.2", + "express": "^4.14.0", + "http-errors": "^1.5.1", + "morgan": "^1.7.0", "node-uuid": "^1.4.7" }, "devDependencies": { diff --git a/lab-shawn/route/person-route.js b/lab-shawn/route/person-route.js deleted file mode 100644 index 15d9371..0000000 --- a/lab-shawn/route/person-route.js +++ /dev/null @@ -1,46 +0,0 @@ -'use strict'; - -const storage = require('../lib/storage.js'); -const Person = require('../model/person.js'); -const response = require('../lib/response.js'); - -module.exports = function(router){ - router.get('/api/person', function(req,res){ - if(req.url.query.id){ - storage.fetchPerson('person',req.url.query.id) - .then( person => { - response.sendJSON(res,200,person); - }) - .catch(err => { - console.error(err); - response.sendText(res,404,'person not found'); - }); - return; - } - response.sendText(res,400,'bad request'); - }); - - router.post('/api/person',function(req,res){ - try{ - var person = new Person(req.body.name,req.body.gender); - storage.createPerson('person',person); - response.sendJSON(res,200,person); - }catch(err){ - console.error(err); - response.sendText(res,400,'bad request'); - } - }); - - router.delete('/api/person', function(req,res){ - if(req.url.query.id){ - storage.deletePerson('person',req.url.query.id) - .then( ()=> { - response.sendJSON(res,204,'person removed'); - }) - .catch(err => { - console.error(err); - response.sendText(res,404,'person not found'); - }); - } - }); -}; diff --git a/lab-shawn/server.js b/lab-shawn/server.js index 8c23783..ec0fa00 100644 --- a/lab-shawn/server.js +++ b/lab-shawn/server.js @@ -1,14 +1,54 @@ 'use strict'; -const http = require('http'); -const Router = require('./lib/router.js'); +const express = require('express'); +const morgan = require('morgan'); +const createError = require('http-errors'); +const jsonParser = require('body-parser').json(); +const debug = require('debug')('person:server'); + +const app = express(); +const Person = require('./model/person.js'); const PORT = process.env.PORT || 3000; -const router = new Router(); -require('./route/person-route.js')(router); +app.use(morgan('dev')); + +app.post('/api/person', jsonParser, function(req,res,next){ + debug('POST: /api/person'); + + Person.createPerson(req.body) + .then(person => res.json(person)) + .catch(err => next(err)); +}); + +app.get('/api/person', function(req,res,next){ + debug('GET: /api/person'); + + Person.fetchPerson(req.query.id) + .then(person => res.json(person)) + .catch(err => next(err)); +}); -const server = http.createServer(router.route()); +app.delete('/api/person', function(req,res,next){ + debug('DELETE: /api/person'); + + Person.deletePerson(req.query.id) + .then(() => res.status(204).send()) + .catch(err => next(err)); +}); + +//eslint-disable-next-line +app.use(function(err,req,res,next){ + debug('error middleware'); + + if(err.status){ + res.status(err.status).send(err.name); + return; + } + + err = createError(500, err.message); + err.status(err.status).send(err.name); +}); -server.listen(PORT, () => { - console.log('Served On:', PORT); +app.listen(PORT, function(){ + console.log(`served on port: ${PORT}`); }); diff --git a/lab-shawn/test/person-route-test.js b/lab-shawn/test/person-route-test.js index 34c6777..d69f53f 100644 --- a/lab-shawn/test/person-route-test.js +++ b/lab-shawn/test/person-route-test.js @@ -56,4 +56,20 @@ describe('Person Routes', function(){ }); }); }); + describe('DELETE: /api/person', function(){ + it('should return with 404', function(done){ + request.get(`localhost:${PORT}/api/person?id=123`) + .end((err) => { + expect(err.status).to.equal(404); + done(); + }); + }); + it('should return with 204', function(done){ + request.get(`localhost:${PORT}/api/person?id=00b17950-c35b-11e6-91c0-2f447f841b80`) + .end((err) => { + expect(err.status).to.equal(204); + done(); + }); + }); + }); }); From 8c3b02ce4d7698421f298ea71193a2d143af84f4 Mon Sep 17 00:00:00 2001 From: shaallfar Date: Tue, 20 Dec 2016 21:45:25 -0800 Subject: [PATCH 4/4] fixed tests --- .../00b17950-c35b-11e6-91c0-2f447f841b80.json | 1 - .../1cb81800-c740-11e6-8f4d-b5aaa3bfd81a.json | 1 + .../1ec685a0-c740-11e6-9b41-31efed65bd5c.json | 1 + .../96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json | 1 - lab-shawn/test/person-route-test.js | 24 +++++++++---------- 5 files changed, 14 insertions(+), 14 deletions(-) delete mode 100644 lab-shawn/data/person/00b17950-c35b-11e6-91c0-2f447f841b80.json create mode 100644 lab-shawn/data/person/1cb81800-c740-11e6-8f4d-b5aaa3bfd81a.json create mode 100644 lab-shawn/data/person/1ec685a0-c740-11e6-9b41-31efed65bd5c.json delete mode 100644 lab-shawn/data/person/96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json diff --git a/lab-shawn/data/person/00b17950-c35b-11e6-91c0-2f447f841b80.json b/lab-shawn/data/person/00b17950-c35b-11e6-91c0-2f447f841b80.json deleted file mode 100644 index ac3e322..0000000 --- a/lab-shawn/data/person/00b17950-c35b-11e6-91c0-2f447f841b80.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"00b17950-c35b-11e6-91c0-2f447f841b80","name":"test name","gender":"male"} \ No newline at end of file diff --git a/lab-shawn/data/person/1cb81800-c740-11e6-8f4d-b5aaa3bfd81a.json b/lab-shawn/data/person/1cb81800-c740-11e6-8f4d-b5aaa3bfd81a.json new file mode 100644 index 0000000..865c1fb --- /dev/null +++ b/lab-shawn/data/person/1cb81800-c740-11e6-8f4d-b5aaa3bfd81a.json @@ -0,0 +1 @@ +{"id":"1cb81800-c740-11e6-8f4d-b5aaa3bfd81a","name":"test name","gender":"male"} \ No newline at end of file diff --git a/lab-shawn/data/person/1ec685a0-c740-11e6-9b41-31efed65bd5c.json b/lab-shawn/data/person/1ec685a0-c740-11e6-9b41-31efed65bd5c.json new file mode 100644 index 0000000..2a955a5 --- /dev/null +++ b/lab-shawn/data/person/1ec685a0-c740-11e6-9b41-31efed65bd5c.json @@ -0,0 +1 @@ +{"id":"1ec685a0-c740-11e6-9b41-31efed65bd5c","name":"test name","gender":"male"} \ No newline at end of file diff --git a/lab-shawn/data/person/96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json b/lab-shawn/data/person/96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json deleted file mode 100644 index 6481380..0000000 --- a/lab-shawn/data/person/96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e.json +++ /dev/null @@ -1 +0,0 @@ -{"id":"96e7ad60-c6d8-11e6-a1fc-c193b44cdb5e","name":"test name","gender":"male"} \ No newline at end of file diff --git a/lab-shawn/test/person-route-test.js b/lab-shawn/test/person-route-test.js index d69f53f..d844bef 100644 --- a/lab-shawn/test/person-route-test.js +++ b/lab-shawn/test/person-route-test.js @@ -24,8 +24,8 @@ describe('Person Routes', function(){ it('should return bad request', function(done){ request.post(`localhost:${PORT}/api/person`) .send({name:'test name'}) - .end((err) => { - expect(err.status).to.equal(400); + .end((err,res) => { + expect(res.status).to.equal(400); done(); }); }); @@ -43,31 +43,31 @@ describe('Person Routes', function(){ }); it('should return not found', function(done){ request.get(`localhost:${PORT}/api/person?id=123`) - .end((err) => { - expect(err.status).to.equal(404); + .end((err,res) => { + expect(res.status).to.equal(404); done(); }); }); it('should return bad request', function(done){ request.get(`localhost:${PORT}/api/person`) - .end((err) => { - expect(err.status).to.equal(400); + .end((err,res) => { + expect(res.status).to.equal(400); done(); }); }); }); describe('DELETE: /api/person', function(){ it('should return with 404', function(done){ - request.get(`localhost:${PORT}/api/person?id=123`) - .end((err) => { - expect(err.status).to.equal(404); + request.delete(`localhost:${PORT}/api/person?id=123`) + .end((err,res) => { + expect(res.status).to.equal(404); done(); }); }); it('should return with 204', function(done){ - request.get(`localhost:${PORT}/api/person?id=00b17950-c35b-11e6-91c0-2f447f841b80`) - .end((err) => { - expect(err.status).to.equal(204); + request.delete(`localhost:${PORT}/api/person?id=${person.id}`) + .end((err,res) => { + expect(res.status).to.equal(204); done(); }); });