diff --git a/lab-eddie/.eslintignore b/lab-eddie/.eslintignore new file mode 100644 index 0000000..ae8da71 --- /dev/null +++ b/lab-eddie/.eslintignore @@ -0,0 +1,6 @@ + +**/node_modules/* +**/vendor/* +**/*.min.js +**/coverage/* +**/build/* diff --git a/lab-eddie/.eslintrc b/lab-eddie/.eslintrc new file mode 100644 index 0000000..8dc6807 --- /dev/null +++ b/lab-eddie/.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-eddie/.gitignore b/lab-eddie/.gitignore new file mode 100644 index 0000000..e19c8ab --- /dev/null +++ b/lab-eddie/.gitignore @@ -0,0 +1,128 @@ + +# Created by https://www.gitignore.io/api/osx,vim,node,windows + +### 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 + +### Vim ### +# swap +[._]*.s[a-v][a-z] +[._]*.sw[a-p] +[._]s[a-v][a-z] +[._]sw[a-p] +# session +Session.vim +# temporary +.netrwhist +*~ +# auto-generated tag files +tags + +### 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/osx,vim,node,windows +data \ No newline at end of file diff --git a/lab-eddie/README.md b/lab-eddie/README.md new file mode 100644 index 0000000..9573976 --- /dev/null +++ b/lab-eddie/README.md @@ -0,0 +1,64 @@ +Vanilla REST API wth file system persistance :D + +This API is seperated into 6 major components: + +1.) The server + +2.) lib driectory + +3.) model directory + +4.) test directory + +5.) data directory + +6.) node_modules + +-----Component Overview----- + +1. The server. This is the meat and potatoes of it all. It uses the http module to host a server. It requires various modules for establishing routes, endponts and file scaffolding. + +2. lib directory houses all of our home made modules and other goodies. + +3. model directory houses all object constructors. + +4. test directory houses all of our tests >:( + +5. data driectory is used for file system persistent storage. It has a sub-directory for each model. + +6. node_modules houses all of our dependancies + +----Instructions---- + +Using httpie is probably the easiest way to go about this whole process. + +-Start off by running the server. Make sure you're in the lab directory and type the following : + +node server.js + +This should return a prompt telling you the sever is up. Also, if you don't have a data folder, it'll create one automatically for you and fill it with folders for each model. + +-Open another terminal tab. + +*To do a GET request type the following: +http GET :3000/api/(whatever model you pick) id==324235345234 + +this should return an object + +*To do a POST request, it's a bit harder. We have to know what our model looks like. We'll have to include more parameters in the request like this: + +http POST :3000/api/dog name==rover breed==pug age==7 + +This should return an object that looks like this: + +{ + name: 'rover', + breed: 'pug', + age: '7' +} + +*To do a DELETE request, it's identical to the GET request. Except it shouldn't return anything. It'll look like this: + +http DELETE :3000/api/car id==324235345234 + +All done! \ No newline at end of file diff --git a/lab-eddie/lib/auto-direct.js b/lab-eddie/lib/auto-direct.js new file mode 100644 index 0000000..b7b1859 --- /dev/null +++ b/lab-eddie/lib/auto-direct.js @@ -0,0 +1,18 @@ +'use strict' + +const fs = require('fs') + +//Automatically creates a data directory and folders for each model +const autoDataDir = module.exports = function(models) { + + let labDir = fs.readdirSync('.'); + if (!labDir.includes('data')) fs.mkdirSync('./data'); + autoModelDir(models); +}; + +const autoModelDir = function(models) { + let modelKeys = Object.keys(models); + let dataDir = fs.readdirSync('./data'); + modelKeys.forEach(key => { + if(!dataDir.includes(key)) fs.mkdirSync(`./data/${key}`)}); +} diff --git a/lab-eddie/lib/errorHandle.js b/lab-eddie/lib/errorHandle.js new file mode 100644 index 0000000..8f88ca1 --- /dev/null +++ b/lab-eddie/lib/errorHandle.js @@ -0,0 +1,12 @@ +'use strict' + +const errHandle = module.exports = {}; + +errHandle.normal = function(paramNames, params) { + let args = params.length; + if (paramNames.length !== args) throw new Error(`Expected ${paramNames[args]}`); +} + +errHandle.promiseErr = function(paramNames, params) { + +} \ No newline at end of file diff --git a/lab-eddie/lib/model-paths.js b/lab-eddie/lib/model-paths.js new file mode 100644 index 0000000..00b5b74 --- /dev/null +++ b/lab-eddie/lib/model-paths.js @@ -0,0 +1,100 @@ +'use strict'; + +const debug = require('debug')('app:model-paths'); +const createError = require('http-errors'); +const jsonParser = require('body-parser').json(); +const storage = require('./storage.js'); +const Person = require('../model/person.js'); +const Car = require('../model/car.js'); +const Dog = require('../model/dog.js'); +const Employee = require('../model/employee.js'); +const Character = require('../model/character.js'); + + + +const modelRoutes = module.exports = {}; + +modelRoutes.models = { + person : Person, + car: Car, + dog: Dog, + employee: Employee, + character: Character +}; + + +modelRoutes.allRoutes = function(model, router) { + modelRoutes.modelGet(model, router); + modelRoutes.modelDelete(model, router); + modelRoutes.modelPost(model, router); +}; + + +modelRoutes.modelGet = function(model,router) { + router.get(`/api/${model}`, function(req, res, next) { + debug(`GET: api/${model}`); + if (req.query) { + storage.fetchItem(`${model}`, req.query.id) + .then(item => res.json(item)) + .catch(err => { + err = createError(404, 'Resource not found'); + next(err); + }); + + return; + + } else if (!req.query.id) { + + + storage.fetchItem(`${model}`) + .then(item => res.json(item)) + .catch(err => { + err = createError(404, 'Resource not found'); + next(err); + }); + return; + }; + + }); +}; + +modelRoutes.modelPost = function(model, router) { + router.post(`/api/${model}`,jsonParser, function(req, res, next) { + debug(`POST: api/${model}`); + + let params = []; + for(let key in req.body) { + params.push(req.body[key]); + } + + try { + var newObj = new modelRoutes.models[model](...params); + storage.createItem(`${model}`, newObj) + .then(item => res.json(item)) + .catch(err => next(err)); + + } catch(err) { + err = createError(400, err.message); + next(err); + } + }); +} + +modelRoutes.modelDelete = function(model, router) { + + router.delete(`/api/${model}`,jsonParser, function(req, res, next) { + debug(`DELETE: api/${model}`); + if (req.query.id) { + + storage.deleteItem(`${model}`, req.query.id) + .then(item => res.json(item)) + .catch(err => next(err)); + + res.status(204) + + return; + }; + }); +} + +require('./auto-direct.js')(modelRoutes.models) \ No newline at end of file diff --git a/lab-eddie/lib/storage.js b/lab-eddie/lib/storage.js new file mode 100644 index 0000000..32c074a --- /dev/null +++ b/lab-eddie/lib/storage.js @@ -0,0 +1,60 @@ +'use strict'; + +const Promise = require('bluebird'); +const fs = Promise.promisifyAll(require('fs'), { suffix: 'Prom' }); +const createError = require('http-errors'); +const debug = require('debug')('note:storage') + +module.exports = exports = {}; + +exports.createItem = function(category, item) { + debug('createItem'); + if(!category) return Promise.reject(createError(400, 'expected category')); + if(!item) return Promise.reject(createError(400, 'expected item')); + + let stringObj = JSON.stringify(item); + return fs.writeFileProm(`${__dirname}/../data/${category}/${item.id}.json`, stringObj) + .then(() => item) + .catch(err => Promise.reject(createError(404, err.message))); +} + + +exports.fetchItem = function(category, id) { + debug('fetchItem'); + if(!category) return Promise.reject(createError(400, 'expected category')); + if(!id) return exports.fetchCategory(category); + + + return fs.readFileProm(`${__dirname}/../data/${category}/${id}.json`) + .then(data => { + try { + let item = JSON.parse(data.toString()); + return item; + } catch (err) { + return Promise.reject(createError(404, err.message)); + } + }) +}; + +exports.deleteItem = function(category, id) { + debug('deleteItem'); + if(!category) return Promise.reject(createError(400, 'expected category')); + if(!id) return Promise.reject(createError(400, 'expected id')); + + return fs.unlinkProm(`${__dirname}/../data/${category}/${id}.json`) + .then(() => '') + .catch(err => Promise.reject(createError(404, err.message))); + +}; + +exports.fetchCategory= function(category) { + debug('fetchCategory'); + if(!category) return Promise.reject(createError(400, 'expected category')); + + return fs.readdirProm(`${__dirname}/../data/${category}`) + .then(data => { + data = data.map(id => id.split('.json')[0]) + return data; + }) + .catch(err => Promise.reject(createError(404, err.message))) +} \ No newline at end of file diff --git a/lab-eddie/model/car.js b/lab-eddie/model/car.js new file mode 100644 index 0000000..fb64c89 --- /dev/null +++ b/lab-eddie/model/car.js @@ -0,0 +1,15 @@ +'use strict'; + +const uuidv4 = require('uuid/v4'); +const errHandle = require('../lib/errorHandle.js'); + +module.exports = function(make, model, year, color) { + errHandle.normal(['make', 'model', 'year', 'color'], arguments); + + this.id = uuidv4(); + this.make = make; + this.model = model; + this.year = year; + this.color = color; + +}; \ No newline at end of file diff --git a/lab-eddie/model/character.js b/lab-eddie/model/character.js new file mode 100644 index 0000000..8432229 --- /dev/null +++ b/lab-eddie/model/character.js @@ -0,0 +1,15 @@ +'use strict'; + +const uuidv4 = require('uuid/v4'); +const errHandle = require('../lib/errorHandle.js'); + +module.exports = function(name, healthPoints, experience, level) { + errHandle.normal(['name', 'healthPoints', 'experience', 'level'], arguments); + + this.id = uuidv4(); + this.name = name; + this.healthPoints = healthPoints; + this.experience = experience; + this.level = level + +}; \ No newline at end of file diff --git a/lab-eddie/model/dog.js b/lab-eddie/model/dog.js new file mode 100644 index 0000000..53cde1c --- /dev/null +++ b/lab-eddie/model/dog.js @@ -0,0 +1,15 @@ +'use strict'; + +const uuidv4 = require('uuid/v4'); +const errHandle = require('../lib/errorHandle.js'); + +module.exports = function(name, breed, age) { + console.log(name, breed, age) + errHandle.normal(['name', 'breed', 'age'], arguments); + + this.id = uuidv4(); + this.name = name; + this.breed = breed; + this.age = age; + +}; \ No newline at end of file diff --git a/lab-eddie/model/employee.js b/lab-eddie/model/employee.js new file mode 100644 index 0000000..12f0593 --- /dev/null +++ b/lab-eddie/model/employee.js @@ -0,0 +1,15 @@ +'use strict'; + +const uuidv4 = require('uuid/v4'); +const errHandle = require('../lib/errorHandle.js'); + +module.exports = function(name, position, pay) { + errHandle.normal(['name', 'position', 'pay'], arguments); + + this.id = uuidv4(); + this.name = name; + this.position = position; + this.pay = pay; + this.fired = false + +}; \ No newline at end of file diff --git a/lab-eddie/model/person.js b/lab-eddie/model/person.js new file mode 100644 index 0000000..7a07b79 --- /dev/null +++ b/lab-eddie/model/person.js @@ -0,0 +1,14 @@ +'use strict'; + +const uuidv4 = require('uuid/v4'); +const errHandle = require('../lib/errorHandle.js'); + +module.exports = function(first, last, age, job) { + errHandle.normal(['first', 'last', 'age', 'job'], arguments); + + this.id = uuidv4(); + this.first = first; + this.last = last + this.age = age; + this.job = job; +}; \ No newline at end of file diff --git a/lab-eddie/package.json b/lab-eddie/package.json new file mode 100644 index 0000000..65b86b7 --- /dev/null +++ b/lab-eddie/package.json @@ -0,0 +1,26 @@ +{ + "name": "lab-eddie", + "version": "1.0.0", + "description": "", + "main": "server.js", + "scripts": { + "test": "mocha", + "start": "DEBUG='note*' node server.js" + }, + "keywords": [], + "author": "", + "license": "ISC", + "devDependencies": { + "chai": "^4.1.0", + "mocha": "^3.4.2", + "superagent": "^3.5.2" + }, + "dependencies": { + "bluebird": "^3.5.0", + "body-parser": "^1.17.2", + "debug": "^2.6.8", + "express": "^4.15.3", + "morgan": "^1.8.2", + "uuid": "^3.1.0" + } +} diff --git a/lab-eddie/server.js b/lab-eddie/server.js new file mode 100644 index 0000000..87019d3 --- /dev/null +++ b/lab-eddie/server.js @@ -0,0 +1,37 @@ +'use strict'; + +const express = require('express'); +const morgan = require('morgan'); +const createError = require('http-errors'); +const jasonParser = require('body-parser').json(); +const modelPaths = require('./lib/model-paths.js') +const debug = require('debug')('note:server') +const PORT = process.env.PORT || 3000; + +const app = express(); + +app.use(morgan('dev')); + + +for(let key in modelPaths.models) { + modelPaths.allRoutes(key, app); +} + + +app.use(function(err, req, res, next) { + debug('error stuff'); + console.error(err.message); + + if (err.status) { + res.status(err.status).send(err.name); + return; + } + + err = createError(500, err.message); + res.status(err.status).send(err.name); +}); + + +app.listen(PORT, () => { + debug(console.log('server on at port:', PORT)); +}); \ No newline at end of file diff --git a/lab-eddie/test/car-test.js b/lab-eddie/test/car-test.js new file mode 100644 index 0000000..5102470 --- /dev/null +++ b/lab-eddie/test/car-test.js @@ -0,0 +1,58 @@ +'use strict'; + +const request = require('superagent'); +const expect = require('chai').expect; + +require('../server.js'); + +describe('car Routes', function() { + var car = null; + + describe('POST: /api/car', function() { + it('should return a car', function(done) { + request.post('localhost:3000/api/car') + .send({ make: 'Toyota', model: '4runner', year: 1987, color: 'black' }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.make).to.equal('Toyota'); + expect(res.body.model).to.equal('4runner'); + expect(res.body.color).to.equal('black'); + expect(res.body.year).to.equal(1987); + console.log('POST request car:', res.body); + car = res.body; + done(); + }); + }); + }); + describe('GET: /api/car', function() { + it('should return a car', function(done) { + request.get(`localhost:3000/api/car?id=${car.id}`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.make).to.equal('Toyota'); + expect(res.body.model).to.equal('4runner'); + expect(res.body.year).to.equal(1987); + expect(res.body.color).to.equal('black'); + console.log('GET request car:', res.body); + done(); + }); + }); + }); + describe('DELETE: /api/car', function() { + it('should return a an empty object', function(done) { + request.delete(`localhost:3000/api/car?id=${car.id}`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + expect(res.body.make).to.equal(undefined); + expect(res.body.model).to.equal(undefined); + expect(res.body.year).to.equal(undefined); + expect(res.body.color).to.equal(undefined); + console.log('DELETE request car:', res.body); + done(); + }); + }); + }); +}); diff --git a/lab-eddie/test/person-test.js b/lab-eddie/test/person-test.js new file mode 100644 index 0000000..040dbb0 --- /dev/null +++ b/lab-eddie/test/person-test.js @@ -0,0 +1,93 @@ +'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(no params)', function() { + it('should return a 400 code', function(done) { + request.post('localhost:3000/api/person') + .send({}) + .end((err, res) => { + if (err) { + expect(err.status).to.equal(400); + done(); + } + }); + }); + }); + describe('GET: /api/person (Invalid id)', function() { + it('should return a a 404 code', function(done) { + request.get(`localhost:3000/api/person?id=2123321423`) + .end((err, res) => { + if (err) { + expect(err.status).to.equal(404); + done() + } + }); + }); + }); + describe('POST: /api/person', function() { + it('should return a person', function(done) { + request.post('localhost:3000/api/person') + .send({ first: 'eddie', last: 'del rio', age: 28, job: 'bum' }) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.first).to.equal('eddie'); + expect(res.body.last).to.equal('del rio'); + expect(res.body.age).to.equal(28); + expect(res.body.job).to.equal('bum'); + console.log('POST request person:', res.body); + person = res.body; + done(); + }); + }); + }); + describe('GET: /api/person (No IDs)', function() { + it('should return an array of IDs', function(done) { + request.get('localhost:3000/api/person') + .end((err, res) => { + if(err) return done(err); + expect(Array.isArray(res.body)).to.equal(true); + expect(res.body.includes(person.id)).to.equal(true); + console.log('Array of IDs for person: ', res.body) + done() + }) + }) + }) + describe('GET: /api/person', function() { + it('should return a person', function(done) { + request.get(`localhost:3000/api/person?id=${person.id}`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(200); + expect(res.body.first).to.equal('eddie'); + expect(res.body.last).to.equal('del rio'); + expect(res.body.age).to.equal(28); + expect(res.body.job).to.equal('bum'); + console.log('GET request person:', res.body); + done(); + }); + }); + }); + + describe('DELETE: /api/person', function() { + it('should return a an empty object', function(done) { + request.delete(`localhost:3000/api/person?id=${person.id}`) + .end((err, res) => { + if (err) return done(err); + expect(res.status).to.equal(204); + expect(res.body.first).to.equal(undefined); + expect(res.body.last).to.equal(undefined); + expect(res.body.age).to.equal(undefined); + expect(res.body.job).to.equal(undefined); + console.log('DELETE request person:', res.body); + done(); + }); + }); + }); +});