├── Chapter02 ├── hello-node-http-server.js.txt ├── main.js.txt ├── modules │ ├── http-module.js.txt │ └── math.js.txt ├── package.json └── test │ ├── test-http-module.js.txt │ └── test-math.js.txt ├── Chapter03 ├── app.js.txt ├── data │ └── catalog.json ├── modules │ └── catalog.js.txt └── routes │ ├── catalog.js.txt │ └── index.js.txt ├── Chapter04 ├── app.js.txt ├── model │ └── item.js.txt ├── modules │ └── catalog.js.txt ├── mongodb-model.js.txt ├── package.json ├── routes │ ├── catalog.js.txt │ └── index.js.txt └── test │ ├── model-test.js.txt │ └── prepare.js.txt ├── Chapter05 ├── app.js.txt ├── model │ └── item.js.txt ├── modules │ ├── catalogV1.js.txt │ └── catalogV2.js.txt ├── mongodb-model.js.txt ├── routes │ ├── catalog.js.txt │ └── index.js.txt └── test │ ├── model-test.js.txt │ └── prepare.js.txt ├── Chapter06 ├── app.js.txt ├── catalog.wadl.txt ├── model │ └── item.js.txt ├── modules │ ├── catalogV1.js.txt │ └── catalogV2.js.txt └── routes │ ├── catalog.js.txt │ └── index.js.txt ├── Chapter07 ├── app.js.txt ├── doc │ ├── index.js.txt │ └── swagger.json ├── model │ └── item.js.txt ├── modules │ ├── catalogV1.js.txt │ └── catalogV2.js.txt ├── package.json.txt ├── routes │ ├── catalog.js.txt │ └── index.js.txt ├── static │ ├── catalog.wadl │ └── swagger.json └── test │ ├── model │ ├── model-test.js.txt │ └── prepare.js.txt │ └── routes │ └── routes-test.js.txt ├── Chapter08 ├── item.html.txt └── new.html.txt ├── Chapter09 ├── app.js.txt ├── doc │ ├── index.js.txt │ └── swagger.json.txt ├── model │ └── item.js.txt ├── modules │ ├── catalogV1.js.txt │ └── catalogV2.js.txt ├── static │ ├── catalog.wadl.txt │ └── swagger.json.txt └── test │ ├── model │ ├── model-test.js.txt │ └── prepare.js.txt │ └── routes │ └── routes-test.js.txt ├── LICENSE └── README.md /Chapter02/hello-node-http-server.js.txt: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var port = 8180; 3 | 4 | function handleGetRequest(response) { 5 | response.writeHead(200, {'Content-Type' : 'text/plain'}); 6 | response.end('Get action was requested'); 7 | } 8 | 9 | function handlePostRequest(response) { 10 | response.writeHead(200, {'Content-Type' : 'text/plain'}); 11 | response.end('Post action was requested'); 12 | } 13 | 14 | function handlePutRequest(response) { 15 | response.writeHead(200, {'Content-Type' : 'text/plain'}); 16 | response.end('Put action was requested'); 17 | } 18 | 19 | function handleDeleteRequest(response) { 20 | response.writeHead(200, {'Content-Type' : 'text/plain'}); 21 | response.end('Delete action was requested'); 22 | } 23 | 24 | function handleBadRequest(response) { 25 | console.log('Unsupported http mehtod'); 26 | response.writeHead(400, {'Content-Type' : 'text/plain' }); 27 | response.end('Bad request'); 28 | } 29 | 30 | function handleRequest(request, response) { 31 | switch (request.method) { 32 | case 'GET': 33 | handleGetRequest(response); 34 | break; 35 | case 'POST': 36 | handlePostRequest(response); 37 | break; 38 | case 'PUT': 39 | handlePutRequest(response); 40 | break; 41 | case 'DELETE': 42 | handleDeleteRequest(response); 43 | break; 44 | default: 45 | handleBadRequest(response); 46 | break; 47 | } 48 | console.log('Request processing completed'); 49 | } 50 | 51 | http.createServer(handleRequest).listen(8180, '127.0.0.1', () => { 52 | console.log('Started Node.js http server at http://127.0.0.1:8180'); 53 | }); 54 | -------------------------------------------------------------------------------- /Chapter02/main.js.txt: -------------------------------------------------------------------------------- 1 | var http = require('http'); 2 | var port = 8180; 3 | 4 | var httpModule = require('./modules/http-module'); 5 | 6 | http.createServer(httpModule.handleRequest).listen(8180, '127.0.0.1', () => { 7 | console.log('Started Node.js http server at http://127.0.0.1:8180'); 8 | }); 9 | -------------------------------------------------------------------------------- /Chapter02/modules/http-module.js.txt: -------------------------------------------------------------------------------- 1 | function handleGetRequest(response) { 2 | response.writeHead(200, {'Content-Type' : 'text/plain'}); 3 | response.end('Get action was requested'); 4 | } 5 | 6 | function handlePostRequest(response) { 7 | response.writeHead(200, {'Content-Type' : 'text/plain'}); 8 | response.end('Post action was requested'); 9 | } 10 | 11 | function handlePutRequest(response) { 12 | response.writeHead(200, {'Content-Type' : 'text/plain'}); 13 | response.end('Put action was requested'); 14 | } 15 | 16 | function handleDeleteRequest(response) { 17 | response.writeHead(200, {'Content-Type' : 'text/plain'}); 18 | response.end('Delete action was requested'); 19 | } 20 | 21 | function handleBadRequest(response) { 22 | console.log('Unsupported http mehtod'); 23 | response.writeHead(400, {'Content-Type' : 'text/plain' }); 24 | response.end('Bad request'); 25 | } 26 | 27 | exports.handleRequest = function(request, response) { 28 | switch (request.method) { 29 | case 'GET': 30 | handleGetRequest(response); 31 | break; 32 | case 'POST': 33 | handlePostRequest(response); 34 | break; 35 | case 'PUT': 36 | handlePutRequest(response); 37 | break; 38 | case 'DELETE': 39 | handleDeleteRequest(response); 40 | break; 41 | default: 42 | handleBadRequest(response); 43 | break; 44 | } 45 | console.log('Request processing completed'); 46 | } 47 | -------------------------------------------------------------------------------- /Chapter02/modules/math.js.txt: -------------------------------------------------------------------------------- 1 | exports.add = function (x, y) { 2 | return x + y; 3 | }; 4 | exports.subtract = function (x, y) { 5 | return x - y; 6 | }; 7 | -------------------------------------------------------------------------------- /Chapter02/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hello-node", 3 | "version": "1.0.0", 4 | "description": "Simple hello world http handler", 5 | "main": "app.js", 6 | "scripts": { 7 | "test": "test" 8 | }, 9 | "author": "Valentin Bojinov", 10 | "license": "ISC", 11 | "dependencies": { 12 | "http": "0.0.0" 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Chapter02/test/test-http-module.js.txt: -------------------------------------------------------------------------------- 1 | var sinon = require('sinon'); 2 | exports.handleGetRequestTest = (test) => { 3 | var response = {'writeHead' : () => {}, 'end': () => {}}; 4 | var responseMock = sinon.mock(response); 5 | responseMock.expects('end').once().withArgs('Get action was requested'); 6 | responseMock.expects('writeHead').once().withArgs(200, { 7 | 'Content-Type' : 'text/plain'}); 8 | 9 | var request = {}; 10 | var requestMock = sinon.mock(request); 11 | requestMock.method = 'GET'; 12 | 13 | var http_module = require('../modules/http-module'); 14 | http_module.handleRequest(requestMock, response); 15 | responseMock.verify(); 16 | test.done(); 17 | }; 18 | -------------------------------------------------------------------------------- /Chapter02/test/test-math.js.txt: -------------------------------------------------------------------------------- 1 | var math = require('../modules/math'); 2 | exports.addTest = function (test) { 3 | test.equal(math.add(1, 1), 3); 4 | test.done(); 5 | }; 6 | exports.subtractTest = function (test) { 7 | test.equals(math.subtract(4,2), 2); 8 | test.done(); 9 | }; 10 | -------------------------------------------------------------------------------- /Chapter03/app.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var catalog = require('./routes/catalog') 10 | var app = express(); 11 | 12 | //uncomment after placing your favicon in /public 13 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 14 | app.use(logger('dev')); 15 | app.use(bodyParser.json()); 16 | app.use(bodyParser.urlencoded({ extended: false })); 17 | app.use(cookieParser()); 18 | app.use(express.static(path.join(__dirname, 'public'))); 19 | 20 | app.use('/', routes); 21 | app.use('/catalog', catalog); 22 | 23 | 24 | // catch 404 and forward to error handler 25 | app.use(function(req, res, next) { 26 | var err = new Error('Not Found'); 27 | err.status = 404; 28 | next(err); 29 | }); 30 | 31 | //development error handler will print stacktrace 32 | if (app.get('env') === 'development') { 33 | app.use(function(err, req, res, next) { 34 | res.status(err.status || 500); 35 | res.render('error', { 36 | message: err.message, 37 | error: err 38 | }); 39 | }); 40 | } 41 | 42 | // production error handler no stacktraces leaked to user 43 | app.use(function(err, req, res, next) { 44 | res.status(err.status || 500); 45 | res.render('error', { 46 | message: err.message, 47 | error: {} 48 | }); 49 | }); 50 | 51 | module.exports = app; 52 | -------------------------------------------------------------------------------- /Chapter03/data/catalog.json: -------------------------------------------------------------------------------- 1 | {"catalog" : [{ 2 | "categoryName" : "Watches", 3 | "categoryId" : "1", 4 | "itemsCount" : 2, 5 | "items" : [{ 6 | "itemId" : "item-identifier-1", 7 | "itemName":"Sports Watch", 8 | "price": 150, 9 | "currency" : "EUR" 10 | }, 11 | { 12 | "itemId" : "item-identifier-2", 13 | "itemName":"Waterproof Sports Watch", 14 | "price": 180, 15 | "currency" : "EUR" 16 | }] 17 | }] 18 | } 19 | -------------------------------------------------------------------------------- /Chapter03/modules/catalog.js.txt: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | 3 | function readCatalogSync() { 4 | var file = './data/catalog.json'; 5 | if (fs.existsSync(file)) { 6 | var content = fs.readFileSync(file); 7 | var catalog = JSON.parse(content); 8 | return catalog; 9 | } 10 | return undefined; 11 | } 12 | 13 | exports.findItems = function(categoryId) { 14 | console.log('Returning all items for categoryId: ' + categoryId); 15 | var catalog = readCatalogSync(); 16 | if (catalog) { 17 | var items = []; 18 | for (var index in catalog.catalog) { 19 | if (catalog.catalog[index].categoryId === categoryId) { 20 | var category = catalog.catalog[index]; 21 | for (var itemIndex in category.items) { 22 | items.push(category.items[itemIndex]); 23 | } 24 | } 25 | } 26 | return items; 27 | } 28 | return undefined; 29 | } 30 | 31 | exports.findItem = function(categoryId, itemId) { 32 | console.log('Looking for item with id' + itemId); 33 | var catalog = readCatalogSync(); 34 | if (catalog) { 35 | for (var index in catalog.catalog) { 36 | if (catalog.catalog[index].categoryId === categoryId) { 37 | var category = catalog.catalog[index]; 38 | for (var itemIndex in category.items) { 39 | if (category.items[itemIndex].itemId === itemId) { 40 | return category.items[itemIndex]; 41 | } 42 | } 43 | } 44 | } 45 | } 46 | return undefined; 47 | } 48 | 49 | exports.findCategoryies = function() { 50 | console.log('Returning all categories'); 51 | var catalog = readCatalogSync(); 52 | if (catalog) { 53 | var categories = []; 54 | for (var index in catalog.catalog) { 55 | var category = {}; 56 | category["categoryId"] = catalog.catalog[index].categoryId; 57 | category["categoryName"] = catalog.catalog[index].categoryName; 58 | 59 | categories.push(category); 60 | } 61 | return categories; 62 | } 63 | return []; 64 | } 65 | -------------------------------------------------------------------------------- /Chapter03/routes/catalog.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var catalog = require('../modules/catalog.js') 3 | 4 | var router = express.Router(); 5 | 6 | router.get('/', function(request, response, next) { 7 | var categories = catalog.findCategoryies(); 8 | response.json(categories); 9 | }); 10 | 11 | router.get('/:categoryId', function(request, response, next) { 12 | var categories = catalog.findItems(request.params.categoryId); 13 | if (categories === undefined) { 14 | response.writeHead(404, {'Content-Type' : 'text/plain'}); 15 | response.end('Not found'); 16 | } else { 17 | response.json(categories); 18 | } 19 | }); 20 | 21 | router.get('/:categoryId/:itemId', function(request, response, next) { 22 | var item = catalog.findItem(request.params.categoryId, request.params.itemId); 23 | if (item === undefined) { 24 | response.writeHead(404, {'Content-Type' : 'text/plain'}); 25 | response.end('Not found'); 26 | } else { 27 | response.json(item); 28 | } 29 | }); 30 | module.exports = router; 31 | -------------------------------------------------------------------------------- /Chapter03/routes/index.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /Chapter04/app.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var catalog = require('./routes/catalog'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'jade'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | //app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', routes); 26 | app.use('/catalog', catalog); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function(req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handlers 36 | 37 | // development error handler 38 | // will print stacktrace 39 | if (app.get('env') === 'development') { 40 | app.use(function(err, req, res, next) { 41 | res.status(err.status || 500); 42 | res.render('error', { 43 | message: err.message, 44 | error: err 45 | }); 46 | }); 47 | } 48 | 49 | // production error handler 50 | // no stacktraces leaked to user 51 | app.use(function(err, req, res, next) { 52 | res.status(err.status || 500); 53 | res.render('error', { 54 | message: err.message, 55 | error: {} 56 | }); 57 | }); 58 | 59 | 60 | module.exports = app; 61 | -------------------------------------------------------------------------------- /Chapter04/model/item.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | mongoose.connect('mongodb://localhost/catalog'); 5 | 6 | var itemSchema = new Schema ({ 7 | "itemId" : {type: String, index: {unique: true}}, 8 | "itemName": String, 9 | "price": Number, 10 | "currency" : String, 11 | "categories": [String] 12 | }); 13 | 14 | var CatalogItem = mongoose.model('Item', itemSchema); 15 | 16 | module.exports = {CatalogItem : CatalogItem}; 17 | -------------------------------------------------------------------------------- /Chapter04/modules/catalog.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (itemId, response) { 26 | CatalogItem.findOne({itemId: itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | if (response != null){ 41 | response.setHeader('Content-Type', 'application/json'); 42 | response.send(result); 43 | } 44 | console.log(result); 45 | } 46 | }); 47 | } 48 | 49 | exports.findItemsByCategory = function (category, response) { 50 | CatalogItem.find({categories: category}, function(error, result) { 51 | if (error) { 52 | console.error(error); 53 | response.writeHead(500, contentTypePlainText); 54 | return; 55 | } else { 56 | if (!result) { 57 | if (response != null) { 58 | response.writeHead(404, contentTypePlainText); 59 | response.end('Not Found'); 60 | } 61 | return; 62 | } 63 | 64 | if (response != null){ 65 | response.setHeader('Content-Type', 'application/json'); 66 | response.send(result); 67 | } 68 | console.log(result); 69 | } 70 | }); 71 | } 72 | 73 | exports.saveItem = function(request, response) 74 | { 75 | var item = toItem(request.body); 76 | item.save((error) => { 77 | if (!error) { 78 | item.save(); 79 | response.writeHead(201, contentTypeJson); 80 | response.end(JSON.stringify(request.body)); 81 | } else { 82 | console.log(error); 83 | CatalogItem.findOne({itemId : item.itemId }, 84 | (error, result) => { 85 | console.log('Check if such an item exists'); 86 | if (error) { 87 | console.log(error); 88 | response.writeHead(500, contentTypePlainText); 89 | response.end('Internal Server Error'); 90 | } else { 91 | if (!result) { 92 | console.log('Item does not exist. Creating a new one'); 93 | item.save(); 94 | response.writeHead(201, contentTypeJson); 95 | response. 96 | response.end(JSON.stringify(request.body)); 97 | } else { 98 | console.log('Updating existing item'); 99 | result.itemId = item.itemId; 100 | result.itemName = item.itemName; 101 | result.price = item.price; 102 | result.currency = item.currency; 103 | result.categories = item.categories; 104 | result.save(); 105 | response.json(JSON.stringify(result)); 106 | } 107 | } 108 | }); 109 | } 110 | }); 111 | }; 112 | 113 | exports.remove = function (request, response) { 114 | console.log('Deleting item with id: ' + request.body.itemId); 115 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 116 | if (error) { 117 | console.log(error); 118 | if (response != null) { 119 | response.writeHead(500, contentTypePlainText); 120 | response.end('Internal server error'); 121 | } 122 | return; 123 | } else { 124 | if (!data) { 125 | console.log('Item not found'); 126 | if (response != null) { 127 | response.writeHead(404, contentTypePlainText); 128 | response.end('Not Found'); 129 | } 130 | return; 131 | } else { 132 | data.remove(function(error){ 133 | if (!error) { 134 | data.remove(); 135 | response.json({'Status': 'Successfully deleted'}); 136 | } 137 | else { 138 | console.log(error); 139 | response.writeHead(500, contentTypePlainText); 140 | response.end('Internal Server Error'); 141 | } 142 | }); 143 | } 144 | } 145 | }); 146 | } 147 | 148 | function toItem(body) { 149 | return new CatalogItem({ 150 | itemId: body.itemId, 151 | itemName: body.itemName, 152 | price: body.price, 153 | currency: body.currency, 154 | categories: body.categories 155 | }); 156 | } 157 | -------------------------------------------------------------------------------- /Chapter04/mongodb-model.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var itemSchema = new Schema ({ 5 | "itemId" : {type: String, index: {unique: true}}, 6 | "itemName": String, 7 | "price": Number, 8 | "currency" : String, 9 | "categories": [String] 10 | }); 11 | 12 | var CatalogItem = mongoose.model('Item', itemSchema); 13 | 14 | mongoose.connect('mongodb://localhost/catalog'); 15 | var db = mongoose.connection; 16 | 17 | db.on('error', console.error.bind(console, 'connection error:')); 18 | db.once('open', function() { 19 | var watch = new CatalogItem({ 20 | itemId: 9 , 21 | itemName: "Sports Watch1", 22 | brand: 'А1', 23 | price: 100, 24 | currency: "EUR", 25 | categories: ["Watches", "Sports Watches"] 26 | }); 27 | watch.save((error, item, affectedNo)=> { 28 | if (!error) { 29 | console.log('Item added successfully to the catalog'); 30 | } else { 31 | console.log('Cannot add item to the catlog'); 32 | } 33 | }); 34 | }); 35 | 36 | db.once('open', function() { 37 | var filter = { 38 | 'itemName' : 'Sports Watch1', 39 | 'price': 100 40 | } 41 | 42 | CatalogItem.find(filter, (error, result) => { 43 | if (error) { 44 | consoloe.log('Error occured'); 45 | } else { 46 | console.log('Results found:'+ result.length); 47 | console.log(result); 48 | } 49 | }); 50 | }); 51 | 52 | db.once('open', function() { 53 | var filter = { 54 | 'itemName' : 'Sports Watch1', 55 | 'price': 100 56 | } 57 | CatalogItem.findOne(filter, (error, result) => { 58 | if (error) { 59 | consoloe.log('Error occured'); 60 | } else { 61 | console.log(result); 62 | } 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /Chapter04/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter4", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "test" : "mocha test/model-test.js" 8 | }, 9 | "dependencies": { 10 | "body-parser": "~1.13.2", 11 | "cookie-parser": "~1.3.5", 12 | "debug": "~2.2.0", 13 | "express": "^4.16.0", 14 | "jade": "~1.11.0", 15 | "morgan": "~1.6.1", 16 | "serve-favicon": "~2.3.0" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /Chapter04/routes/catalog.js.txt: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const router = express.Router(); 3 | 4 | const catalog = require('../modules/catalog'); 5 | const model = require('../model/item.js'); 6 | 7 | router.get('/', function(request, response, next) { 8 | catalog.findAllItems(response); 9 | }); 10 | 11 | router.get('/item/:itemId', function(request, response, next) { 12 | console.log(request.url + ' : querying for ' + request.params.itemId); 13 | catalog.findItemById(request.params.itemId, response); 14 | }); 15 | 16 | 17 | router.get('/:categoryId', function(request, response, next) { 18 | console.log(request.url + ' : querying for ' + request.params.categoryId); 19 | catalog.findItemsByCategory(request.params.categoryId, response); 20 | }); 21 | 22 | 23 | router.post('/', function(request, response, next) { 24 | catalog.saveItem(request, response); 25 | }); 26 | 27 | router.put('/', function(request, response, next) { 28 | catalog.saveItem(request, response); 29 | }); 30 | 31 | router.delete('/item/:itemId', function(request, response, next) { 32 | catalog.remove(request, response); 33 | }); 34 | 35 | module.exports = router; 36 | -------------------------------------------------------------------------------- /Chapter04/routes/index.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /Chapter04/test/model-test.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var should = require('should'); 3 | var prepare = require('./prepare'); 4 | 5 | 6 | 7 | const model = require('../model/item.js'); 8 | const CatalogItem = model.CatalogItem; 9 | 10 | mongoose.createConnection('mongodb://localhost/catalog'); 11 | 12 | 13 | describe('CatalogItem: models', function () { 14 | describe('#create()', function () { 15 | it('Should create a new CatalogItem', function (done) { 16 | 17 | var item = { 18 | "itemId": "1", 19 | "itemName": "Sports Watch", 20 | "price": 100, 21 | "currency": "EUR", 22 | "categories": [ 23 | "Watches", 24 | "Sports Watches" 25 | ] 26 | 27 | }; 28 | 29 | CatalogItem.create(item, function (err, createdItem) { 30 | // Check that no error occured 31 | should.not.exist(err); 32 | // Assert that the returned item has is what we expect 33 | 34 | createdItem.itemId.should.equal('1'); 35 | createdItem.itemName.should.equal('Sports Watch'); 36 | createdItem.price.should.equal(100); 37 | createdItem.currency.should.equal('EUR'); 38 | createdItem.categories[0].should.equal('Watches'); 39 | createdItem.categories[1].should.equal('Sports Watches'); 40 | //Notify mocha that the test has completed 41 | done(); 42 | }); 43 | }); 44 | }); 45 | 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /Chapter04/test/prepare.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | beforeEach(function (done) { 4 | function clearDatabase() { 5 | for (var i in mongoose.connection.collections) { 6 | mongoose.connection.collections[i].remove(function() {}); 7 | } 8 | return done(); 9 | } 10 | 11 | if (mongoose.connection.readyState === 0) { 12 | mongoose.connect(config.db.test, function (err) { 13 | if (err) { 14 | throw err; 15 | } 16 | return clearDatabase(); 17 | }); 18 | } else { 19 | return clearDatabase(); 20 | } 21 | }); 22 | 23 | afterEach(function (done) { 24 | mongoose.disconnect(); 25 | return done(); 26 | }); 27 | -------------------------------------------------------------------------------- /Chapter05/app.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var catalog = require('./routes/catalog'); 10 | 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'jade'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | //app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', routes); 26 | app.use('/catalog', catalog); 27 | 28 | // catch 404 and forward to error handler 29 | app.use(function(req, res, next) { 30 | var err = new Error('Not Found'); 31 | err.status = 404; 32 | next(err); 33 | }); 34 | 35 | // error handlers 36 | 37 | // development error handler 38 | // will print stacktrace 39 | if (app.get('env') === 'development') { 40 | app.use(function(err, req, res, next) { 41 | res.status(err.status || 500); 42 | res.render('error', { 43 | message: err.message, 44 | error: err 45 | }); 46 | }); 47 | } 48 | 49 | // production error handler 50 | // no stacktraces leaked to user 51 | app.use(function(err, req, res, next) { 52 | res.status(err.status || 500); 53 | res.render('error', { 54 | message: err.message, 55 | error: {} 56 | }); 57 | }); 58 | 59 | 60 | module.exports = app; 61 | -------------------------------------------------------------------------------- /Chapter05/model/item.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | mongoose.connect('mongodb://localhost/catalog'); 5 | 6 | var itemSchema = new Schema ({ 7 | "itemId" : {type: String, index: {unique: true}}, 8 | "itemName": String, 9 | "price": Number, 10 | "currency" : String, 11 | "categories": [String] 12 | }); 13 | 14 | var CatalogItem = mongoose.model('Item', itemSchema); 15 | 16 | module.exports = {CatalogItem : CatalogItem}; 17 | -------------------------------------------------------------------------------- /Chapter05/modules/catalogV1.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (itemId, response) { 26 | CatalogItem.findOne({itemId: itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | if (response != null){ 41 | response.setHeader('Content-Type', 'application/json'); 42 | response.send(result); 43 | } 44 | console.log(result); 45 | } 46 | }); 47 | } 48 | 49 | exports.findItemsByCategory = function (category, response) { 50 | CatalogItem.find({categories: category}, function(error, result) { 51 | if (error) { 52 | console.error(error); 53 | response.writeHead(500, contentTypePlainText); 54 | return; 55 | } else { 56 | if (!result) { 57 | if (response != null) { 58 | response.writeHead(404, contentTypePlainText); 59 | response.end('Not Found'); 60 | } 61 | return; 62 | } 63 | 64 | if (response != null){ 65 | response.setHeader('Content-Type', 'application/json'); 66 | response.send(result); 67 | } 68 | console.log(result); 69 | } 70 | }); 71 | } 72 | 73 | exports.saveItem = function(request, response) 74 | { 75 | var item = toItem(request.body); 76 | item.save((error) => { 77 | if (!error) { 78 | item.save(); 79 | response.setHeader('Location', request.get('host') + '/item/' + item.itemId); 80 | response.writeHead(201, contentTypeJson); 81 | response.end(JSON.stringify(request.body)); 82 | } else { 83 | console.log(error); 84 | CatalogItem.findOne({itemId : item.itemId }, 85 | (error, result) => { 86 | console.log('Check if such an item exists'); 87 | if (error) { 88 | console.log(error); 89 | response.writeHead(500, contentTypePlainText); 90 | response.end('Internal Server Error'); 91 | } else { 92 | if (!result) { 93 | console.log('Item does not exist. Creating a new one'); 94 | item.save(); 95 | response.writeHead(201, contentTypeJson); 96 | response.end(JSON.stringify(request.body)); 97 | } else { 98 | console.log('Updating existing item'); 99 | result.itemId = item.itemId; 100 | result.itemName = item.itemName; 101 | result.price = item.price; 102 | result.currency = item.currency; 103 | result.categories = item.categories; 104 | result.save(); 105 | response.json(JSON.stringify(result)); 106 | } 107 | } 108 | }); 109 | } 110 | }); 111 | }; 112 | 113 | exports.remove = function (request, response) { 114 | console.log('Deleting item with id: ' + request.body.itemId); 115 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 116 | if (error) { 117 | console.log(error); 118 | if (response != null) { 119 | response.writeHead(500, contentTypePlainText); 120 | response.end('Internal server error'); 121 | } 122 | return; 123 | } else { 124 | if (!data) { 125 | console.log('Item not found'); 126 | if (response != null) { 127 | response.writeHead(404, contentTypePlainText); 128 | response.end('Not Found'); 129 | } 130 | return; 131 | } else { 132 | data.remove(function(error){ 133 | if (!error) { 134 | data.remove(); 135 | response.json({'Status': 'Successfully deleted'}); 136 | } 137 | else { 138 | console.log(error); 139 | response.writeHead(500, contentTypePlainText); 140 | response.end('Internal Server Error'); 141 | } 142 | }); 143 | } 144 | } 145 | }); 146 | } 147 | 148 | function toItem(body) { 149 | return new CatalogItem({ 150 | itemId: body.itemId, 151 | itemName: body.itemName, 152 | price: body.price, 153 | currency: body.currency, 154 | categories: body.categories 155 | }); 156 | } 157 | -------------------------------------------------------------------------------- /Chapter05/modules/catalogV2.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (itemId, response) { 26 | CatalogItem.findOne({itemId: itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | if (response != null){ 41 | response.setHeader('Content-Type', 'application/json'); 42 | response.send(result); 43 | } 44 | console.log(result); 45 | } 46 | }); 47 | } 48 | 49 | exports.findItemsByAttribute = function (key, value, response) { 50 | var filter = {}; 51 | filter[key] = value; 52 | CatalogItem.find(filter, function(error, result) { 53 | if (error) { 54 | console.error(error); 55 | response.writeHead(500, contentTypePlainText); 56 | response.end('Internal server error'); 57 | return; 58 | } else { 59 | if (!result) { 60 | if (response != null) { 61 | response.writeHead(200, contentTypeJson); 62 | response.end({}); 63 | } 64 | return; 65 | } 66 | if (response != null){ 67 | response.setHeader('Content-Type', 68 | 'application/json'); 69 | response.send(result); 70 | } 71 | } 72 | }); 73 | } 74 | 75 | exports.findItemsByCategory = function (category, response) { 76 | CatalogItem.find({categories: category}, function(error, result) { 77 | if (error) { 78 | console.error(error); 79 | response.writeHead(500, contentTypePlainText); 80 | return; 81 | } else { 82 | if (!result) { 83 | if (response != null) { 84 | response.writeHead(404, contentTypePlainText); 85 | response.end('Not Found'); 86 | } 87 | return; 88 | } 89 | 90 | if (response != null){ 91 | response.setHeader('Content-Type', 'application/json'); 92 | response.send(result); 93 | } 94 | console.log(result); 95 | } 96 | }); 97 | } 98 | 99 | exports.saveItem = function(request, response) { 100 | var item = toItem(request.body); 101 | item.save((error) => { 102 | if (!error) { 103 | item.save(); 104 | response.setHeader('Location', request.get('host') + '/item/' + item.itemId); 105 | response.writeHead(201, contentTypeJson); 106 | response.end(JSON.stringify(request.body)); 107 | } else { 108 | console.log(error); 109 | CatalogItem.findOne({itemId : item.itemId }, 110 | (error, result) => { 111 | console.log('Check if such an item exists'); 112 | if (error) { 113 | console.log(error); 114 | response.writeHead(500, contentTypePlainText); 115 | response.end('Internal Server Error'); 116 | } else { 117 | if (!result) { 118 | console.log('Item does not exist. Creating a new one'); 119 | item.save(); 120 | response.writeHead(201, contentTypeJson); 121 | response.end(JSON.stringify(request.body)); 122 | } else { 123 | console.log('Updating existing item'); 124 | result.itemId = item.itemId; 125 | result.itemName = item.itemName; 126 | result.price = item.price; 127 | result.currency = item.currency; 128 | result.categories = item.categories; 129 | result.save(); 130 | response.json(JSON.stringify(result)); 131 | } 132 | } 133 | }); 134 | } 135 | }); 136 | } 137 | 138 | exports.remove = function (request, response) { 139 | console.log('Deleting item with id: ' + request.body.itemId); 140 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 141 | if (error) { 142 | console.log(error); 143 | if (response != null) { 144 | response.writeHead(500, contentTypePlainText); 145 | response.end('Internal server error'); 146 | } 147 | return; 148 | } else { 149 | if (!data) { 150 | console.log('Item not found'); 151 | if (response != null) { 152 | response.writeHead(404, contentTypePlainText); 153 | response.end('Not Found'); 154 | } 155 | return; 156 | } else { 157 | data.remove(function(error){ 158 | if (!error) { 159 | data.remove(); 160 | response.json({'Status': 'Successfully deleted'}); 161 | } 162 | else { 163 | console.log(error); 164 | response.writeHead(500, contentTypePlainText); 165 | response.end('Internal Server Error'); 166 | } 167 | }); 168 | } 169 | } 170 | }); 171 | } 172 | 173 | function toItem(body) { 174 | return new CatalogItem({ 175 | itemId: body.itemId, 176 | itemName: body.itemName, 177 | price: body.price, 178 | currency: body.currency, 179 | categories: body.categories 180 | }); 181 | } 182 | -------------------------------------------------------------------------------- /Chapter05/mongodb-model.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | 4 | var itemSchema = new Schema ({ 5 | "itemId" : {type: String, index: {unique: true}}, 6 | "itemName": String, 7 | "price": Number, 8 | "currency" : String, 9 | "categories": [String] 10 | }); 11 | 12 | var CatalogItem = mongoose.model('Item', itemSchema); 13 | 14 | mongoose.connect('mongodb://localhost/catalog'); 15 | var db = mongoose.connection; 16 | 17 | db.on('error', console.error.bind(console, 'connection error:')); 18 | db.once('open', function() { 19 | var watch = new CatalogItem({ 20 | itemId: 9 , 21 | itemName: "Sports Watch1", 22 | brand: 'А1', 23 | price: 100, 24 | currency: "EUR", 25 | categories: ["Watches", "Sports Watches"] 26 | }); 27 | watch.save((error, item, affectedNo)=> { 28 | if (!error) { 29 | console.log('Item added successfully to the catalog'); 30 | } else { 31 | console.log('Cannot add item to the catlog'); 32 | } 33 | }); 34 | }); 35 | 36 | db.once('open', function() { 37 | var filter = { 38 | 'itemName' : 'Sports Watch1', 39 | 'price': 100 40 | } 41 | 42 | CatalogItem.find(filter, (error, result) => { 43 | if (error) { 44 | consoloe.log('Error occured'); 45 | } else { 46 | console.log('Results found:'+ result.length); 47 | console.log(result); 48 | } 49 | }); 50 | }); 51 | 52 | db.once('open', function() { 53 | var filter = { 54 | 'itemName' : 'Sports Watch1', 55 | 'price': 100 56 | } 57 | CatalogItem.findOne(filter, (error, result) => { 58 | if (error) { 59 | consoloe.log('Error occured'); 60 | } else { 61 | console.log(result); 62 | } 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /Chapter05/routes/catalog.js.txt: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const url = require('url'); 3 | 4 | const router = express.Router(); 5 | 6 | const catalogV1 = require('../modules/catalogV1'); 7 | const catalogV2 = require('../modules/catalogV2'); 8 | 9 | const model = require('../model/item.js'); 10 | 11 | router.get('/v1/', function(request, response, next) { 12 | catalogV1.findAllItems(response); 13 | }); 14 | 15 | router.get('/v1/item/:itemId', function(request, response, next) { 16 | console.log(request.url + ' : querying for ' + request.params.itemId); 17 | catalogV1.findItemById(request.params.itemId, response); 18 | }); 19 | 20 | 21 | 22 | router.get('/v1/:categoryId', function(request, response, next) { 23 | console.log(request.url + ' : querying for ' + request.params.categoryId); 24 | catalogV1.findItemsByCategory(request.params.categoryId, response); 25 | }); 26 | 27 | router.post('/v1/', function(request, response, next) { 28 | catalogV1.saveItem(request, response); 29 | }); 30 | 31 | router.put('/v1/', function(request, response, next) { 32 | catalogV1.saveItem(request, response); 33 | }); 34 | 35 | router.put('/v1/:itemId', function(request, response, next) { 36 | catalogV1.saveItem(request, response); 37 | }); 38 | 39 | router.delete('/v1/item/:itemId', function(request, response, next) { 40 | catalogV1.remove(request, response); 41 | }); 42 | 43 | router.get('/', function(request, response) { 44 | console.log('Redirecting to v1'); 45 | response.writeHead(301, {'Location' : '/catalog/v1/'}); 46 | response.end('Version 1 is moved to /catalog/v1/: '); 47 | }); 48 | 49 | 50 | router.get('/v2/:categoryId', function(request, response, next) { 51 | console.log(request.url + ' : querying for ' + request.params.categoryId); 52 | catalogV2.findItemsByCategory(request.params.categoryId, response); 53 | }); 54 | 55 | router.get('/v2/item/:itemId', function(request, response, next) { 56 | console.log(request.url + ' : querying for ' + request.params.itemId); 57 | catalogV2.findItemById(request.params.itemId, response); 58 | }); 59 | 60 | router.post('/v2/', function(request, response, next) { 61 | catalogV2.saveItem(request, response); 62 | }); 63 | 64 | router.put('/v2/', function(request, response, next) { 65 | catalogV2.saveItem(request, response); 66 | }); 67 | 68 | router.put('/v2/:itemId', function(request, response, next) { 69 | catalogV1.saveItem(request, response); 70 | }); 71 | 72 | router.delete('/v2/item/:itemId', function(request, response, next) { 73 | catalogV2.remove(request, response); 74 | }); 75 | 76 | router.get('/', function(request, response) { 77 | console.log('Redirecting to v1'); 78 | response.writeHead(301, {'Location' : '/catalog/v1/'}); 79 | response.end('Version 1 is moved to /catalog/v1/: '); 80 | }); 81 | 82 | router.get('/v2/items', function(request, response) { 83 | var getParams = url.parse(request.url, true).query; 84 | if (Object.keys(getParams).length == 0) { 85 | catalogV2 .findAllItems(response); 86 | } else { 87 | var key = Object.keys(getParams)[0]; 88 | var value = getParams[key]; 89 | catalogV2.findItemsByAttribute(key, value, response); 90 | } 91 | }); 92 | 93 | module.exports = router; 94 | -------------------------------------------------------------------------------- /Chapter05/routes/index.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /Chapter05/test/model-test.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var should = require('should'); 3 | var prepare = require('./prepare'); 4 | 5 | 6 | 7 | const model = require('../model/item.js'); 8 | const CatalogItem = model.CatalogItem; 9 | 10 | mongoose.createConnection('mongodb://localhost/catalog'); 11 | 12 | 13 | describe('CatalogItem: models', function () { 14 | describe('#create()', function () { 15 | it('Should create a new CatalogItem', function (done) { 16 | 17 | var item = { 18 | "itemId": "1", 19 | "itemName": "Sports Watch", 20 | "price": 100, 21 | "currency": "EUR", 22 | "categories": [ 23 | "Watches", 24 | "Sports Watches" 25 | ] 26 | 27 | }; 28 | 29 | CatalogItem.create(item, function (err, createdItem) { 30 | // Check that no error occured 31 | should.not.exist(err); 32 | // Assert that the returned item has is what we expect 33 | 34 | createdItem.itemId.should.equal('1'); 35 | createdItem.itemName.should.equal('Sports Watch'); 36 | createdItem.price.should.equal(100); 37 | createdItem.currency.should.equal('EUR'); 38 | createdItem.categories[0].should.equal('Watches'); 39 | createdItem.categories[1].should.equal('Sports Watches'); 40 | //Notify mocha that the test has completed 41 | done(); 42 | }); 43 | }); 44 | }); 45 | 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /Chapter05/test/prepare.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | beforeEach(function (done) { 4 | function clearDatabase() { 5 | for (var i in mongoose.connection.collections) { 6 | mongoose.connection.collections[i].remove(function() {}); 7 | } 8 | return done(); 9 | } 10 | 11 | if (mongoose.connection.readyState === 0) { 12 | mongoose.connect(config.db.test, function (err) { 13 | if (err) { 14 | throw err; 15 | } 16 | return clearDatabase(); 17 | }); 18 | } else { 19 | return clearDatabase(); 20 | } 21 | }); 22 | 23 | afterEach(function (done) { 24 | mongoose.disconnect(); 25 | return done(); 26 | }); 27 | -------------------------------------------------------------------------------- /Chapter06/app.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var catalog = require('./routes/catalog'); 10 | var expressPaginate = require('express-paginate') 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'jade'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | //app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', routes); 26 | app.use('/catalog', catalog); 27 | app.use(expressPaginate.middleware(10,100)); 28 | 29 | // catch 404 and forward to error handler 30 | app.use(function(req, res, next) { 31 | var err = new Error('Not Found'); 32 | err.status = 404; 33 | next(err); 34 | }); 35 | 36 | // error handlers 37 | 38 | // development error handler 39 | // will print stacktrace 40 | if (app.get('env') === 'development') { 41 | app.use(function(err, req, res, next) { 42 | res.status(err.status || 500); 43 | res.render('error', { 44 | message: err.message, 45 | error: err 46 | }); 47 | }); 48 | } 49 | 50 | // production error handler 51 | // no stacktraces leaked to user 52 | app.use(function(err, req, res, next) { 53 | res.status(err.status || 500); 54 | res.render('error', { 55 | message: err.message, 56 | error: {} 57 | }); 58 | }); 59 | 60 | 61 | module.exports = app; 62 | -------------------------------------------------------------------------------- /Chapter06/catalog.wadl.txt: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /Chapter06/model/item.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var mongoosePaginate = require('mongoose-paginate'); 3 | var Schema = mongoose.Schema; 4 | 5 | mongoose.connect('mongodb://localhost/catalog'); 6 | 7 | var itemSchema = new Schema ({ 8 | "itemId" : {type: String, index: {unique: true}}, 9 | "itemName": String, 10 | "price": Number, 11 | "currency" : String, 12 | "categories": [String] 13 | }); 14 | console.log('paginate'); 15 | itemSchema.plugin(mongoosePaginate); 16 | var CatalogItem = mongoose.model('Item', itemSchema); 17 | 18 | module.exports = {CatalogItem : CatalogItem, connection : mongoose.connection}; 19 | -------------------------------------------------------------------------------- /Chapter06/modules/catalogV1.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (itemId, response) { 26 | CatalogItem.findOne({itemId: itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | if (response != null){ 41 | response.setHeader('Content-Type', 'application/json'); 42 | response.send(result); 43 | } 44 | console.log(result); 45 | } 46 | }); 47 | } 48 | 49 | exports.findItemsByCategory = function (category, response) { 50 | CatalogItem.find({categories: category}, function(error, result) { 51 | if (error) { 52 | console.error(error); 53 | response.writeHead(500, contentTypePlainText); 54 | return; 55 | } else { 56 | if (!result) { 57 | if (response != null) { 58 | response.writeHead(404, contentTypePlainText); 59 | response.end('Not Found'); 60 | } 61 | return; 62 | } 63 | 64 | if (response != null){ 65 | response.setHeader('Content-Type', 'application/json'); 66 | response.send(result); 67 | } 68 | console.log(result); 69 | } 70 | }); 71 | } 72 | 73 | exports.saveItem = function(request, response) 74 | { 75 | var item = toItem(request.body); 76 | item.save((error) => { 77 | if (!error) { 78 | item.save(); 79 | response.setHeader('Location', request.get('host') + '/item/' + item.itemId); 80 | response.writeHead(201, contentTypeJson); 81 | response.end(JSON.stringify(request.body)); 82 | } else { 83 | console.log(error); 84 | CatalogItem.findOne({itemId : item.itemId }, 85 | (error, result) => { 86 | console.log('Check if such an item exists'); 87 | if (error) { 88 | console.log(error); 89 | response.writeHead(500, contentTypePlainText); 90 | response.end('Internal Server Error'); 91 | } else { 92 | if (!result) { 93 | console.log('Item does not exist. Creating a new one'); 94 | item.save(); 95 | response.writeHead(201, contentTypeJson); 96 | response.end(JSON.stringify(request.body)); 97 | } else { 98 | console.log('Updating existing item'); 99 | result.itemId = item.itemId; 100 | result.itemName = item.itemName; 101 | result.price = item.price; 102 | result.currency = item.currency; 103 | result.categories = item.categories; 104 | result.save(); 105 | response.json(JSON.stringify(result)); 106 | } 107 | } 108 | }); 109 | } 110 | }); 111 | }; 112 | 113 | exports.remove = function (request, response) { 114 | console.log('Deleting item with id: ' + request.body.itemId); 115 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 116 | if (error) { 117 | console.log(error); 118 | if (response != null) { 119 | response.writeHead(500, contentTypePlainText); 120 | response.end('Internal server error'); 121 | } 122 | return; 123 | } else { 124 | if (!data) { 125 | console.log('Item not found'); 126 | if (response != null) { 127 | response.writeHead(404, contentTypePlainText); 128 | response.end('Not Found'); 129 | } 130 | return; 131 | } else { 132 | data.remove(function(error){ 133 | if (!error) { 134 | data.remove(); 135 | response.json({'Status': 'Successfully deleted'}); 136 | } 137 | else { 138 | console.log(error); 139 | response.writeHead(500, contentTypePlainText); 140 | response.end('Internal Server Error'); 141 | } 142 | }); 143 | } 144 | } 145 | }); 146 | } 147 | 148 | function toItem(body) { 149 | return new CatalogItem({ 150 | itemId: body.itemId, 151 | itemName: body.itemName, 152 | price: body.price, 153 | currency: body.currency, 154 | categories: body.categories 155 | }); 156 | } 157 | -------------------------------------------------------------------------------- /Chapter06/modules/catalogV2.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (gfs, request, response) { 26 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | var options = { 41 | filename : result.itemId, 42 | }; 43 | gfs.exist(options, function(error, found) { 44 | if (found) { 45 | response.setHeader('Content-Type', 'application/json'); 46 | var imageUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + '/image'; 47 | response.setHeader('Image-Url', imageUrl); 48 | response.send(result); 49 | } else { 50 | response.json(result); 51 | } 52 | }); 53 | } 54 | }); 55 | } 56 | 57 | exports.findItemsByAttribute = function (key, value, response) { 58 | var filter = {}; 59 | filter[key] = value; 60 | CatalogItem.find(filter, function(error, result) { 61 | if (error) { 62 | console.error(error); 63 | response.writeHead(500, contentTypePlainText); 64 | response.end('Internal server error'); 65 | return; 66 | } else { 67 | if (!result) { 68 | if (response != null) { 69 | response.writeHead(200, contentTypeJson); 70 | response.end({}); 71 | } 72 | return; 73 | } 74 | if (response != null){ 75 | response.setHeader('Content-Type', 76 | 'application/json'); 77 | response.send(result); 78 | } 79 | } 80 | }); 81 | } 82 | 83 | exports.findItemsByCategory = function (category, response) { 84 | CatalogItem.find({categories: category}, function(error, result) { 85 | if (error) { 86 | console.error(error); 87 | response.writeHead(500, contentTypePlainText); 88 | return; 89 | } else { 90 | if (!result) { 91 | if (response != null) { 92 | response.writeHead(404, contentTypePlainText); 93 | response.end('Not Found'); 94 | } 95 | return; 96 | } 97 | 98 | if (response != null){ 99 | response.setHeader('Content-Type', 'application/json'); 100 | response.send(result); 101 | } 102 | console.log(result); 103 | } 104 | }); 105 | } 106 | 107 | exports.saveItem = function(request, response) { 108 | var item = toItem(request.body); 109 | console.log(item); 110 | item.save((error) => { 111 | if (!error) { 112 | item.save(); 113 | var locationUrl; 114 | if (!request.path.endsWith('/')) { 115 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + '/items/' + item.itemId; 116 | } else { 117 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + 'item/' + item.itemId; 118 | } 119 | response.setHeader('Location', locationUrl); 120 | response.writeHead(201, contentTypeJson); 121 | response.end(JSON.stringify(request.body)); 122 | } else { 123 | console.log(error); 124 | CatalogItem.findOne({itemId : item.itemId }, 125 | (error, result) => { 126 | console.log('Check if such an item exists'); 127 | if (error) { 128 | console.log(error); 129 | response.writeHead(500, contentTypePlainText); 130 | response.end('Internal Server Error'); 131 | } else { 132 | if (!result) { 133 | console.log('Item does not exist. Creating a new one'); 134 | item.save(); 135 | if (!request.path.endsWith('/')) { 136 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl+ request.path + '/' + item.itemId; 137 | } else { 138 | locationUrl = itemImageUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + item.itemId; 139 | } 140 | response.setHeader('Location', locationUrl); 141 | response.end(JSON.stringify(request.body)); 142 | } else { 143 | console.log('Updating existing item'); 144 | result.itemId = item.itemId; 145 | result.itemName = item.itemName; 146 | result.price = item.price; 147 | result.currency = item.currency; 148 | result.categories = item.categories; 149 | result.save(); 150 | response.json(JSON.stringify(result)); 151 | } 152 | } 153 | }); 154 | } 155 | }); 156 | } 157 | 158 | 159 | 160 | 161 | exports.remove = function (request, response) { 162 | console.log('Deleting item with id: ' + request.body.itemId); 163 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 164 | if (error) { 165 | console.log(error); 166 | if (response != null) { 167 | response.writeHead(500, contentTypePlainText); 168 | response.end('Internal server error'); 169 | } 170 | return; 171 | } else { 172 | if (!data) { 173 | console.log('Item not found'); 174 | if (response != null) { 175 | response.writeHead(404, contentTypePlainText); 176 | response.end('Not Found'); 177 | } 178 | return; 179 | } else { 180 | data.remove(function(error){ 181 | if (!error) { 182 | data.remove(); 183 | response.json({'Status': 'Successfully deleted'}); 184 | } 185 | else { 186 | console.log(error); 187 | response.writeHead(500, contentTypePlainText); 188 | response.end('Internal Server Error'); 189 | } 190 | }); 191 | } 192 | } 193 | }); 194 | } 195 | 196 | exports.paginate = function(model, request, response) { 197 | var pageSize = request.query.limit; 198 | var page = request.query.page; 199 | if (pageSize === undefined) { 200 | pageSize = 100; 201 | } 202 | if (page === undefined) { 203 | page = 1; 204 | } 205 | 206 | model.paginate({}, {page:page, limit:pageSize}, 207 | function (error, result){ 208 | if(error) { 209 | console.log(error); 210 | response.writeHead('500', 211 | {'Content-Type' : 'text/plain'}); 212 | response.end('Internal Server Error'); 213 | } 214 | else { 215 | response.json(result); 216 | } 217 | }); 218 | } 219 | 220 | exports.deleteImage = function(gfs, mongodb, itemId, response) { 221 | console.log('Deleting image for itemId:' + itemId); 222 | 223 | var options = { 224 | filename : itemId, 225 | }; 226 | 227 | var chunks = mongodb.collection('fs.files.chunks'); 228 | chunks.remove(options, function (error, image) { 229 | if (error) { 230 | console.log(error); 231 | response.send('500', 'Internal Server Error'); 232 | return; 233 | } else { 234 | console.log('Successfully deleted image for primary contact number: ' + itemId); 235 | } 236 | }); 237 | 238 | 239 | var files = mongodb.collection('fs.files'); 240 | 241 | files.remove(options, function (error, image) { 242 | if (error) { 243 | console.log(error); 244 | response.send('500', 'Internal Server Error'); 245 | return; 246 | } 247 | 248 | if (image === null) { 249 | response.send('404', 'Not found'); 250 | return; 251 | } 252 | else { 253 | console.log('Successfully deleted image for primary contact number: ' + itemId); 254 | response.json({'deleted': true}); 255 | } 256 | }); 257 | } 258 | 259 | 260 | exports.getImage = function(gfs, request, response) { 261 | readImage(gfs, request, response); 262 | }; 263 | 264 | exports.saveImage = function(gfs, request, response) { 265 | 266 | var writeStream = gfs.createWriteStream({ 267 | filename : request.params.itemId, 268 | mode : 'w' 269 | }); 270 | 271 | writeStream.on('error', function(error) { 272 | response.send('500', 'Internal Server Error'); 273 | console.log(error); 274 | return; 275 | }) 276 | 277 | writeStream.on('close', function() { 278 | readImage(gfs, request, response); 279 | }); 280 | 281 | request.pipe(writeStream); 282 | } 283 | 284 | function readImage(gfs, request, response) { 285 | 286 | var imageStream = gfs.createReadStream({ 287 | filename : request.params.itemId, 288 | mode : 'r' 289 | }); 290 | 291 | imageStream.on('error', function(error) { 292 | console.log(error); 293 | response.send('404', 'Not found'); 294 | return; 295 | }); 296 | 297 | var itemImageUrl = request.protocol + '://' + request.get('host') + request.baseUrl+ request.path; 298 | var itemUrl = itemImageUrl.substring(0, itemImageUrl.indexOf('/image')); 299 | response.setHeader('Content-Type', 'image/jpeg'); 300 | response.setHeader('Item-Url', itemUrl); 301 | 302 | 303 | imageStream.pipe(response); 304 | } 305 | 306 | 307 | function toItem(body) { 308 | return new CatalogItem({ 309 | itemId: body.itemId, 310 | itemName: body.itemName, 311 | price: body.price, 312 | currency: body.currency, 313 | categories: body.categories 314 | }); 315 | } 316 | -------------------------------------------------------------------------------- /Chapter06/routes/catalog.js.txt: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const url = require('url'); 3 | const mongoose = require('mongoose'); 4 | const Grid = require('gridfs-stream'); 5 | const router = express.Router(); 6 | const CacheControl = require("express-cache-control") 7 | 8 | const catalogV1 = require('../modules/catalogV1'); 9 | const catalogV2 = require('../modules/catalogV2'); 10 | 11 | const model = require('../model/item.js'); 12 | 13 | var cache = new CacheControl().middleware; 14 | 15 | router.get('/v1/', function(request, response, next) { 16 | catalogV1.findAllItems(response); 17 | }); 18 | 19 | router.get('/v1/item/:itemId', function(request, response, next) { 20 | console.log(request.url + ' : querying for ' + request.params.itemId); 21 | catalogV1.findItemById(request.params.itemId, response); 22 | }); 23 | 24 | 25 | 26 | router.get('/v1/:categoryId', function(request, response, next) { 27 | console.log(request.url + ' : querying for ' + request.params.categoryId); 28 | catalogV1.findItemsByCategory(request.params.categoryId, response); 29 | }); 30 | 31 | router.post('/v1/', function(request, response, next) { 32 | catalogV1.saveItem(request, response); 33 | }); 34 | 35 | router.put('/v1/', function(request, response, next) { 36 | catalogV1.saveItem(request, response); 37 | }); 38 | 39 | router.put('/v1/:itemId', function(request, response, next) { 40 | catalogV1.saveItem(request, response); 41 | }); 42 | 43 | router.delete('/v1/item/:itemId', function(request, response, next) { 44 | catalogV1.remove(request, response); 45 | }); 46 | 47 | router.get('/v2/:categoryId', function(request, response, next) { 48 | console.log(request.url + ' : querying for ' + request.params.categoryId); 49 | catalogV2.findItemsByCategory(request.params.categoryId, response); 50 | }); 51 | 52 | router.get('/v2/item/:itemId', function(request, response, next) { 53 | console.log(request.url + ' : querying for ' + request.params.itemId); 54 | var gfs = Grid(model.connection.db, mongoose.mongo); 55 | 56 | catalogV2.findItemById(gfs, request, response); 57 | }); 58 | 59 | router.post('/v2/', function(request, response, next) { 60 | catalogV2.saveItem(request, response); 61 | }); 62 | 63 | router.put('/v2/', function(request, response, next) { 64 | catalogV2.saveItem(request, response); 65 | }); 66 | 67 | router.put('/v2/:itemId', function(request, response, next) { 68 | catalogV1.saveItem(request, response); 69 | }); 70 | 71 | router.delete('/v2/item/:itemId', function(request, response, next) { 72 | catalogV2.remove(request, response); 73 | }); 74 | 75 | router.get('/', function(request, response) { 76 | console.log('Redirecting to v2'); 77 | response.writeHead(302, {'Location' : '/catalog/v2/'}); 78 | response.end('Version 2 is is available at /catalog/v2/: '); 79 | }); 80 | 81 | router.get('/v2/', cache('minutes', 1), function(request, response) { 82 | var getParams = url.parse(request.url, true).query; 83 | if (getParams['page'] !=null || getParams['limit'] != null) { 84 | catalogV2.paginate(model.CatalogItem, request, response); 85 | } else { 86 | var key = Object.keys(getParams)[0]; 87 | var value = getParams[key]; 88 | catalogV2.findItemsByAttribute(key, value, response); 89 | } 90 | }); 91 | 92 | router.get('/v2/item/:itemId/image', 93 | function(request, response){ 94 | var gfs = Grid(model.connection.db, mongoose.mongo); 95 | catalogV2.getImage(gfs, request, response); 96 | }); 97 | 98 | router.get('/item/:itemId/image', 99 | function(request, response){ 100 | var gfs = Grid(model.connection.db, mongoose.mongo); 101 | catalogV2.getImage(gfs, request, response); 102 | }); 103 | 104 | router.post('/v2/item/:itemId/image', 105 | function(request, response){ 106 | var gfs = Grid(model.connection.db, mongoose.mongo); 107 | catalogV2.saveImage(gfs, request, response); 108 | }); 109 | 110 | router.post('/item/:itemId/image', 111 | function(request, response){ 112 | var gfs = Grid(model.connection.db, mongoose.mongo); 113 | catalogV2.saveImage(gfs, request.params.itemId, response); 114 | }); 115 | 116 | router.put('/v2/item/:itemId/image', 117 | function(request, response){ 118 | var gfs = Grid(model.connection.db, mongoose.mongo); 119 | catalogV2.saveImage (gfs, request.params.itemId, response); 120 | }); 121 | 122 | router.put('/item/:itemId/image', 123 | function(request, response){ 124 | var gfs = Grid(model.connection.db, mongoose.mongo); 125 | catalogV2.saveImage(gfs, request.params.itemId, response); 126 | }); 127 | 128 | router.delete('/v2/item/:itemId/image', 129 | function(request, response){ 130 | var gfs = Grid(model.connection.db, mongoose.mongo); 131 | catalogV2.deleteImage(gfs, model.connection, 132 | request.params.itemId, response); 133 | }); 134 | 135 | router.delete('/item/:itemId/image', 136 | function(request, response){ 137 | var gfs = Grid(model.connection.db, mongoose.mongo); 138 | catalogV2.deleteImage(gfs, model.connection, request.params.itemId, response); 139 | }); 140 | 141 | module.exports = router; 142 | -------------------------------------------------------------------------------- /Chapter06/routes/index.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /Chapter07/app.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var catalog = require('./routes/catalog'); 10 | var expressPaginate = require('express-paginate') 11 | var app = express(); 12 | 13 | // view engine setup 14 | app.set('views', path.join(__dirname, 'views')); 15 | app.set('view engine', 'jade'); 16 | 17 | // uncomment after placing your favicon in /public 18 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 19 | app.use(logger('dev')); 20 | app.use(bodyParser.json()); 21 | //app.use(bodyParser.urlencoded({ extended: false })); 22 | app.use(cookieParser()); 23 | app.use(express.static(path.join(__dirname, 'public'))); 24 | 25 | app.use('/', routes); 26 | app.use('/catalog', catalog); 27 | app.use(expressPaginate.middleware(10,100)); 28 | 29 | 30 | const swaggerUi = require('swagger-ui-express'); 31 | const swaggerDocument = require('./static/swagger.json'); 32 | 33 | app.use('/catalog/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); 34 | app.use('/catalog/static', express.static('static')) 35 | // catch 404 and forward to error handler 36 | app.use(function(req, res, next) { 37 | var err = new Error('Not Found'); 38 | err.status = 404; 39 | next(err); 40 | }); 41 | 42 | // error handlers 43 | 44 | // development error handler 45 | // will print stacktrace 46 | if (app.get('env') === 'development') { 47 | app.use(function(err, req, res, next) { 48 | res.status(err.status || 500); 49 | res.render('error', { 50 | message: err.message, 51 | error: err 52 | }); 53 | }); 54 | } 55 | 56 | // production error handler 57 | // no stacktraces leaked to user 58 | app.use(function(err, req, res, next) { 59 | res.status(err.status || 500); 60 | res.render('error', { 61 | message: err.message, 62 | error: {} 63 | }); 64 | }); 65 | 66 | 67 | module.exports = app; 68 | -------------------------------------------------------------------------------- /Chapter07/doc/index.js.txt: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs'); 4 | var express = require('express'); 5 | 6 | var favIconHtml = '' + 7 | '' 8 | 9 | 10 | var setup = function (swaggerDoc, opts, options, customCss, customfavIcon, swaggerUrl, customeSiteTitle) { 11 | var isExplorer 12 | var customJs 13 | if (opts && typeof opts === 'object') { 14 | isExplorer = opts.explorer 15 | options = opts.swaggerOptions 16 | customCss = opts.customCss 17 | customJs = opts.customJs 18 | customfavIcon = opts.customfavIcon 19 | swaggerUrl = opts.swaggerUrl 20 | customeSiteTitle = opts.customSiteTitle 21 | } else { 22 | //support legacy params based function 23 | isExplorer = opts 24 | } 25 | options = options || {}; 26 | var explorerString = isExplorer ? '' : '.swagger-ui .topbar .download-url-wrapper { display: none }'; 27 | customCss = explorerString + ' ' + customCss || explorerString; 28 | customfavIcon = customfavIcon || false; 29 | customeSiteTitle = customeSiteTitle || 'Swagger UI'; 30 | var html = fs.readFileSync(__dirname + '/indexTemplate.html'); 31 | try { 32 | fs.unlinkSync(__dirname + '/index.html'); 33 | } catch (e) { 34 | 35 | } 36 | 37 | var favIconString = customfavIcon ? '' : favIconHtml; 38 | var htmlWithCustomCss = html.toString().replace('<% customCss %>', customCss); 39 | var htmlWithFavIcon = htmlWithCustomCss.replace('<% favIconString %>', favIconString); 40 | var htmlWithCustomJs = htmlWithFavIcon.replace('<% customJs %>', customJs ? `` : ''); 41 | 42 | var initOptions = { 43 | swaggerDoc: swaggerDoc || undefined, 44 | customOptions: options, 45 | swaggerUrl: swaggerUrl || undefined 46 | } 47 | var htmlWithOptions = htmlWithCustomJs.replace('<% swaggerOptions %>', JSON.stringify(initOptions)).replace('<% title %>', customeSiteTitle) 48 | 49 | return function (req, res) { res.send(htmlWithOptions) }; 50 | }; 51 | 52 | var serve = express.static(__dirname + '/static'); 53 | 54 | var stringify = function (obj, prop) { 55 | var placeholder = '____FUNCTIONPLACEHOLDER____'; 56 | var fns = []; 57 | var json = JSON.stringify(obj, function (key, value) { 58 | if (typeof value === 'function') { 59 | fns.push(value); 60 | return placeholder; 61 | } 62 | return value; 63 | }, 2); 64 | json = json.replace(new RegExp('"' + placeholder + '"', 'g'), function (_) { 65 | return fns.shift(); 66 | }); 67 | return json + ';'; 68 | }; 69 | 70 | module.exports = { 71 | setup: setup, 72 | serve: serve 73 | }; 74 | -------------------------------------------------------------------------------- /Chapter07/doc/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Catalog API Documentation", 5 | "version": "v1" 6 | }, 7 | "paths": { 8 | "/catalog/{categoryId}": { 9 | "get": { 10 | "operationId": "getCategoryV2", 11 | "summary": "Get all items for a category ", 12 | "produces": [ 13 | "application/json" 14 | ], 15 | "responses": { 16 | "200": { 17 | "description": "200 OK", 18 | "examples": { 19 | "application/json": [ 20 | { 21 | "_id": "5a4c004b0eed73835833cc9a", 22 | "itemId": "1", 23 | "itemName": "Sports Watch", 24 | "price": 100, 25 | "currency": "EUR", 26 | "__v": 0, 27 | "categories": [ 28 | "Watches", 29 | "Sports Watches" 30 | ] 31 | }, 32 | { 33 | "_id": "5a4c0b7aad0ebbce584593ee", 34 | "itemId": "2", 35 | "itemName": "Sports Watch 2", 36 | "price": 100, 37 | "currency": "USD", 38 | "__v": 0, 39 | "categories": [ 40 | "Watches", 41 | "Sports Watches" 42 | ] 43 | } 44 | ] 45 | } 46 | }, 47 | "404": { 48 | "description": "404 Not Found" 49 | }, 50 | "500": { 51 | "description": "500 Internal Server Error" 52 | } 53 | } 54 | } 55 | }, 56 | "/catalog/v1/{categoryId}": { 57 | "get": { 58 | "operationId": "getCategoryV1", 59 | "summary": "Get all items for a category ", 60 | "produces": [ 61 | "application/json" 62 | ], 63 | "responses": { 64 | "200": { 65 | "description": "200 OK", 66 | "examples": { 67 | "application/json": [ 68 | { 69 | "_id": "5a4c004b0eed73835833cc9a", 70 | "itemId": "1", 71 | "itemName": "Sports Watch", 72 | "price": 100, 73 | "currency": "EUR", 74 | "__v": 0, 75 | "categories": [ 76 | "Watches", 77 | "Sports Watches" 78 | ] 79 | }, 80 | { 81 | "_id": "5a4c0b7aad0ebbce584593ee", 82 | "itemId": "2", 83 | "itemName": "Sports Watch 2", 84 | "price": 100, 85 | "currency": "USD", 86 | "__v": 0, 87 | "categories": [ 88 | "Watches", 89 | "Sports Watches" 90 | ] 91 | } 92 | ] 93 | } 94 | }, 95 | "404": { 96 | "description": "404 Not Found" 97 | }, 98 | "500": { 99 | "description": "500 Internal Server Error" 100 | } 101 | } 102 | } 103 | }, 104 | "/catalog/v2/{categoryId}": { 105 | "get": { 106 | "operationId": "getCategoryV2", 107 | "summary": "Get all items for a category ", 108 | "produces": [ 109 | "application/json" 110 | ], 111 | "responses": { 112 | "200": { 113 | "description": "200 OK", 114 | "examples": { 115 | "application/json": [ 116 | { 117 | "_id": "5a4c004b0eed73835833cc9a", 118 | "itemId": "1", 119 | "itemName": "Sports Watch", 120 | "price": 100, 121 | "currency": "EUR", 122 | "__v": 0, 123 | "categories": [ 124 | "Watches", 125 | "Sports Watches" 126 | ] 127 | }, 128 | { 129 | "_id": "5a4c0b7aad0ebbce584593ee", 130 | "itemId": "2", 131 | "itemName": "Sports Watch 2", 132 | "price": 100, 133 | "currency": "USD", 134 | "__v": 0, 135 | "categories": [ 136 | "Watches", 137 | "Sports Watches" 138 | ] 139 | } 140 | ] 141 | } 142 | }, 143 | "404": { 144 | "description": "404 Not Found" 145 | }, 146 | "500": { 147 | "description": "500 Internal Server Error" 148 | } 149 | } 150 | } 151 | }, 152 | "/catalog/item/{itemId}": { 153 | "get": { 154 | "operationId": "getItemV2", 155 | "summary": "Get an existing item", 156 | "produces": [ 157 | "application/json" 158 | ], 159 | "responses": { 160 | "200": { 161 | "description": "200 OK", 162 | "examples": { 163 | "application/json": { 164 | "_id": "5a4c004b0eed73835833cc9a", 165 | "itemId": "1", 166 | "itemName": "Sports Watch", 167 | "price": 100, 168 | "currency": "EUR", 169 | "__v": 0, 170 | "categories": [ 171 | "Watches", 172 | "Sports Watches" 173 | ] 174 | } 175 | } 176 | }, 177 | "404": { 178 | "description": "404 Not Found" 179 | }, 180 | "500": { 181 | "description": "500 Internal Server Error" 182 | } 183 | } 184 | }, 185 | "post": { 186 | "404": { 187 | "description": "404 Not Found" 188 | }, 189 | "500": { 190 | "description": "500 Internal Server Error" 191 | }, 192 | "operationId": "postItemV2", 193 | "summary": "Creates new or updates existing item", 194 | "produces": [ 195 | "application/json" 196 | ], 197 | "responses": { 198 | "200": { 199 | "itemId": 19, 200 | "itemName": "Sports Watch 19", 201 | "price": 100, 202 | "currency": "USD", 203 | "__v": 0, 204 | "categories": [ 205 | "Watches", 206 | "Sports Watches" 207 | ] 208 | }, 209 | "201": { 210 | "itemId": 19, 211 | "itemName": "Sports Watch 19", 212 | "price": 100, 213 | "currency": "USD", 214 | "__v": 0, 215 | "categories": [ 216 | "Watches", 217 | "Sports Watches" 218 | ] 219 | }, 220 | "500": "text/html" 221 | } 222 | }, 223 | "put": { 224 | "404": { 225 | "description": "404 Not Found" 226 | }, 227 | "500": { 228 | "description": "500 Internal Server Error" 229 | }, 230 | "operationId": "putItemV2", 231 | "summary": "Creates new or updates existing item", 232 | "produces": [ 233 | "application/json" 234 | ], 235 | "responses": { 236 | "200": { 237 | "itemId": 19, 238 | "itemName": "Sports Watch 19", 239 | "price": 100, 240 | "currency": "USD", 241 | "__v": 0, 242 | "categories": [ 243 | "Watches", 244 | "Sports Watches" 245 | ] 246 | }, 247 | "201": { 248 | "itemId": 19, 249 | "itemName": "Sports Watch 19", 250 | "price": 100, 251 | "currency": "USD", 252 | "__v": 0, 253 | "categories": [ 254 | "Watches", 255 | "Sports Watches" 256 | ] 257 | }, 258 | "500": "text/html" 259 | } 260 | }, 261 | "delete": { 262 | "404": { 263 | "description": "404 Not Found" 264 | }, 265 | "500": { 266 | "description": "500 Internal Server Error" 267 | }, 268 | "operationId": "deleteItemV2", 269 | "summary": "Deletes an existing item", 270 | "produces": [ 271 | "application/json" 272 | ], 273 | "responses": { 274 | "200": { 275 | "deleted": true 276 | }, 277 | "500": "text/html" 278 | } 279 | } 280 | }, 281 | "/catalog/v1/item/{id}": { 282 | "get": { 283 | "operationId": "getItemV1", 284 | "summary": "Get an existing item", 285 | "produces": [ 286 | "application/json" 287 | ], 288 | "responses": { 289 | "200": { 290 | "description": "200 OK", 291 | "examples": { 292 | "application/json": { 293 | "_id": "5a4c004b0eed73835833cc9a", 294 | "itemId": "1", 295 | "itemName": "Sports Watch", 296 | "price": 100, 297 | "currency": "EUR", 298 | "__v": 0, 299 | "categories": [ 300 | "Watches", 301 | "Sports Watches" 302 | ] 303 | } 304 | } 305 | }, 306 | "404": { 307 | "description": "404 Not Found" 308 | }, 309 | "500": { 310 | "description": "500 Internal Server Error" 311 | } 312 | } 313 | }, 314 | "post": { 315 | "404": { 316 | "description": "404 Not Found" 317 | }, 318 | "500": { 319 | "description": "500 Internal Server Error" 320 | }, 321 | "operationId": "postItemV1", 322 | "summary": "Creates new or updates existing item", 323 | "produces": [ 324 | "application/json" 325 | ], 326 | "responses": { 327 | "200": { 328 | "itemId": 19, 329 | "itemName": "Sports Watch 19", 330 | "price": 100, 331 | "currency": "USD", 332 | "__v": 0, 333 | "categories": [ 334 | "Watches", 335 | "Sports Watches" 336 | ] 337 | }, 338 | "201": { 339 | "itemId": 19, 340 | "itemName": "Sports Watch 19", 341 | "price": 100, 342 | "currency": "USD", 343 | "__v": 0, 344 | "categories": [ 345 | "Watches", 346 | "Sports Watches" 347 | ] 348 | }, 349 | "500": "text/html" 350 | } 351 | }, 352 | "put": { 353 | "404": { 354 | "description": "404 Not Found" 355 | }, 356 | "500": { 357 | "description": "500 Internal Server Error" 358 | }, 359 | "operationId": "putItemV1", 360 | "summary": "Creates new or updates existing item", 361 | "produces": [ 362 | "application/json" 363 | ], 364 | "responses": { 365 | "200": { 366 | "itemId": 19, 367 | "itemName": "Sports Watch 19", 368 | "price": 100, 369 | "currency": "USD", 370 | "__v": 0, 371 | "categories": [ 372 | "Watches", 373 | "Sports Watches" 374 | ] 375 | }, 376 | "201": { 377 | "itemId": 19, 378 | "itemName": "Sports Watch 19", 379 | "price": 100, 380 | "currency": "USD", 381 | "__v": 0, 382 | "categories": [ 383 | "Watches", 384 | "Sports Watches" 385 | ] 386 | }, 387 | "500": "text/html" 388 | } 389 | }, 390 | "delete": { 391 | "404": { 392 | "description": "404 Not Found" 393 | }, 394 | "500": { 395 | "description": "500 Internal Server Error" 396 | }, 397 | "operationId": "deleteItemV1", 398 | "summary": "Deletes an existing item", 399 | "produces": [ 400 | "application/json" 401 | ], 402 | "responses": { 403 | "200": { 404 | "deleted": true 405 | }, 406 | "500": "text/html" 407 | } 408 | } 409 | }, 410 | "/catalog/v2/item/{id}": { 411 | "get": { 412 | "operationId": "getItemV2", 413 | "summary": "Get an existing item", 414 | "produces": [ 415 | "application/json" 416 | ], 417 | "responses": { 418 | "200": { 419 | "description": "200 OK", 420 | "examples": { 421 | "application/json": 422 | { 423 | "_id": "5a4c004b0eed73835833cc9a", 424 | "itemId": "1", 425 | "itemName": "Sports Watch", 426 | "price": 100, 427 | "currency": "EUR", 428 | "__v": 0, 429 | "categories": [ 430 | "Watches", 431 | "Sports Watches" 432 | ] 433 | } 434 | } 435 | }, 436 | "404": { 437 | "description": "404 Not Found" 438 | }, 439 | "500": { 440 | "description": "500 Internal Server Error" 441 | } 442 | } 443 | }, 444 | "post": { 445 | "operationId": "postItemImageV2", 446 | "summary": "Provide image for existing item", 447 | "produces": [ 448 | "image/jpeg" 449 | ], 450 | "responses": { 451 | "200": { 452 | "404": { 453 | "description": "404 Not Found" 454 | }, 455 | "500": { 456 | "description": "500 Internal Server Error" 457 | }, 458 | "description": "200 OK", 459 | "examples": { 460 | "image/jpeg": "image" 461 | } 462 | } 463 | } 464 | }, 465 | "put": { 466 | "404": { 467 | "description": "404 Not Found" 468 | }, 469 | "500": { 470 | "description": "500 Internal Server Error" 471 | }, 472 | "operationId": "putItemV2", 473 | "summary": "Creates new or updates existing item", 474 | "produces": [ 475 | "application/json" 476 | ], 477 | "responses": { 478 | "200": { 479 | "itemId": 19, 480 | "itemName": "Sports Watch 19", 481 | "price": 100, 482 | "currency": "USD", 483 | "__v": 0, 484 | "categories": [ 485 | "Watches", 486 | "Sports Watches" 487 | ] 488 | }, 489 | "201": { 490 | "itemId": 19, 491 | "itemName": "Sports Watch 19", 492 | "price": 100, 493 | "currency": "USD", 494 | "__v": 0, 495 | "categories": [ 496 | "Watches", 497 | "Sports Watches" 498 | ] 499 | }, 500 | "500": "text/html" 501 | } 502 | }, 503 | "delete": { 504 | "404": { 505 | "description": "404 Not Found" 506 | }, 507 | "500": { 508 | "description": "500 Internal Server Error" 509 | }, 510 | "operationId": "deleteItemV2", 511 | "summary": "Deletes an existing item", 512 | "produces": [ 513 | "application/json" 514 | ], 515 | "responses": { 516 | "200": { 517 | "deleted": true 518 | }, 519 | "500": "text/html" 520 | } 521 | } 522 | }, 523 | "/catalog/v2/item/{id}/image": { 524 | "get": { 525 | "404": { 526 | "description": "404 Not Found" 527 | }, 528 | "500": { 529 | "description": "500 Internal Server Error" 530 | }, 531 | "operationId": "getItemImageV2", 532 | "summary": "Get an image bound to an existing item", 533 | "produces": [ 534 | "application/json" 535 | ], 536 | "responses": { 537 | "200": "application/jpeg" 538 | } 539 | }, 540 | "post": { 541 | "404": { 542 | "description": "404 Not Found" 543 | }, 544 | "500": { 545 | "description": "500 Internal Server Error" 546 | }, 547 | "operationId": "postItemImageV2", 548 | "summary": "Creates or updates an image bound to an existing item", 549 | "produces": [ 550 | "application/json" 551 | ], 552 | "responses": { 553 | "200": "application/jpeg" 554 | } 555 | }, 556 | "put": { 557 | "404": { 558 | "description": "404 Not Found" 559 | }, 560 | "500": { 561 | "description": "500 Internal Server Error" 562 | }, 563 | "operationId": "putItemV2", 564 | "summary": "Creates or updates an image bound to an existing item", 565 | "produces": [ 566 | "application/json" 567 | ], 568 | "responses": { 569 | "200": "application/jpeg" 570 | } 571 | }, 572 | "delete": { 573 | "404": { 574 | "description": "404 Not Found" 575 | }, 576 | "500": { 577 | "description": "500 Internal Server Error" 578 | }, 579 | "operationId": "deleteItemV2", 580 | "summary": "Creates or updates an image bound to an existing item", 581 | "produces": [ 582 | "application/json" 583 | ], 584 | "responses": { 585 | "200": "application/jpeg" 586 | } 587 | } 588 | }, 589 | "/catalog/item/": { 590 | "post": { 591 | "404": { 592 | "description": "404 Not Found" 593 | }, 594 | "500": { 595 | "description": "500 Internal Server Error" 596 | }, 597 | "operationId": "postItemV21", 598 | "summary": "Creates new or updates existing item", 599 | "produces": [ 600 | "application/json" 601 | ], 602 | "responses": { 603 | "200": { 604 | "itemId": 19, 605 | "itemName": "Sports Watch 19", 606 | "price": 100, 607 | "currency": "USD", 608 | "__v": 0, 609 | "categories": [ 610 | "Watches", 611 | "Sports Watches" 612 | ] 613 | }, 614 | "201": { 615 | "itemId": 19, 616 | "itemName": "Sports Watch 19", 617 | "price": 100, 618 | "currency": "USD", 619 | "__v": 0, 620 | "categories": [ 621 | "Watches", 622 | "Sports Watches" 623 | ] 624 | }, 625 | "500": "text/html" 626 | } 627 | } 628 | }, 629 | "/catalog/v1/item/": { 630 | "post": { 631 | "operationId": "postItemV1", 632 | "summary": "Create new or update existing item", 633 | "produces": [ 634 | "image/jpeg" 635 | ], 636 | "responses": { 637 | "200": { 638 | "description": "200 OK", 639 | "examples": { 640 | "application/json": 641 | { 642 | "_id": "5a4c004b0eed73835833cc9a", 643 | "itemId": "1", 644 | "itemName": "Sports Watch", 645 | "price": 100, 646 | "currency": "EUR", 647 | "__v": 0, 648 | "categories": [ 649 | "Watches", 650 | "Sports Watches" 651 | ] 652 | } 653 | } 654 | }, 655 | "404": { 656 | "description": "404 Not Found" 657 | }, 658 | "500": { 659 | "description": "500 Internal Server Error" 660 | } 661 | } 662 | } 663 | }, 664 | "/catalog/v2/item/": { 665 | "post": { 666 | "operationId": "postItemV2", 667 | "summary": "Create new or update existing item", 668 | "produces": [ 669 | "image/jpeg" 670 | ], 671 | "responses": { 672 | "200": { 673 | "description": "200 OK", 674 | "examples": { 675 | "application/json": 676 | { 677 | "_id": "5a4c004b0eed73835833cc9a", 678 | "itemId": "1", 679 | "itemName": "Sports Watch", 680 | "price": 100, 681 | "currency": "EUR", 682 | "__v": 0, 683 | "categories": [ 684 | "Watches", 685 | "Sports Watches" 686 | ] 687 | } 688 | } 689 | }, 690 | "404": { 691 | "description": "404 Not Found" 692 | }, 693 | "500": { 694 | "description": "500 Internal Server Error" 695 | } 696 | } 697 | } 698 | } 699 | }, 700 | "consumes": [ 701 | "application/json" 702 | ] 703 | } 704 | -------------------------------------------------------------------------------- /Chapter07/model/item.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var mongoosePaginate = require('mongoose-paginate'); 3 | var Schema = mongoose.Schema; 4 | 5 | mongoose.connect('mongodb://localhost/catalog'); 6 | 7 | var itemSchema = new Schema ({ 8 | "itemId" : {type: String, index: {unique: true}}, 9 | "itemName": String, 10 | "price": Number, 11 | "currency" : String, 12 | "categories": [String] 13 | }); 14 | itemSchema.plugin(mongoosePaginate); 15 | var CatalogItem = mongoose.model('Item', itemSchema); 16 | 17 | module.exports = {CatalogItem : CatalogItem, connection : mongoose.connection}; 18 | -------------------------------------------------------------------------------- /Chapter07/modules/catalogV1.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (itemId, response) { 26 | CatalogItem.findOne({itemId: itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | if (response != null){ 41 | response.setHeader('Content-Type', 'application/json'); 42 | response.send(result); 43 | } 44 | console.log(result); 45 | } 46 | }); 47 | } 48 | 49 | exports.findItemsByCategory = function (category, response) { 50 | CatalogItem.find({categories: category}, function(error, result) { 51 | if (error) { 52 | console.error(error); 53 | response.writeHead(500, contentTypePlainText); 54 | return; 55 | } else { 56 | if (!result) { 57 | if (response != null) { 58 | response.writeHead(404, contentTypePlainText); 59 | response.end('Not Found'); 60 | } 61 | return; 62 | } 63 | 64 | if (response != null){ 65 | response.setHeader('Content-Type', 'application/json'); 66 | response.send(result); 67 | } 68 | console.log(result); 69 | } 70 | }); 71 | } 72 | 73 | exports.saveItem = function(request, response) 74 | { 75 | var item = toItem(request.body); 76 | item.save((error) => { 77 | if (!error) { 78 | item.save(); 79 | response.setHeader('Location', request.get('host') + '/item/' + item.itemId); 80 | response.writeHead(201, contentTypeJson); 81 | response.end(JSON.stringify(request.body)); 82 | } else { 83 | console.log(error); 84 | CatalogItem.findOne({itemId : item.itemId }, 85 | (error, result) => { 86 | console.log('Check if such an item exists'); 87 | if (error) { 88 | console.log(error); 89 | response.writeHead(500, contentTypePlainText); 90 | response.end('Internal Server Error'); 91 | } else { 92 | if (!result) { 93 | console.log('Item does not exist. Creating a new one'); 94 | item.save(); 95 | response.writeHead(201, contentTypeJson); 96 | response.end(JSON.stringify(request.body)); 97 | } else { 98 | console.log('Updating existing item'); 99 | result.itemId = item.itemId; 100 | result.itemName = item.itemName; 101 | result.price = item.price; 102 | result.currency = item.currency; 103 | result.categories = item.categories; 104 | result.save(); 105 | response.json(JSON.stringify(result)); 106 | } 107 | } 108 | }); 109 | } 110 | }); 111 | }; 112 | 113 | exports.remove = function (request, response) { 114 | console.log('Deleting item with id: ' + request.body.itemId); 115 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 116 | if (error) { 117 | console.log(error); 118 | if (response != null) { 119 | response.writeHead(500, contentTypePlainText); 120 | response.end('Internal server error'); 121 | } 122 | return; 123 | } else { 124 | if (!data) { 125 | console.log('Item not found'); 126 | if (response != null) { 127 | response.writeHead(404, contentTypePlainText); 128 | response.end('Not Found'); 129 | } 130 | return; 131 | } else { 132 | data.remove(function(error){ 133 | if (!error) { 134 | data.remove(); 135 | response.json({'Status': 'Successfully deleted'}); 136 | } 137 | else { 138 | console.log(error); 139 | response.writeHead(500, contentTypePlainText); 140 | response.end('Internal Server Error'); 141 | } 142 | }); 143 | } 144 | } 145 | }); 146 | } 147 | 148 | function toItem(body) { 149 | return new CatalogItem({ 150 | itemId: body.itemId, 151 | itemName: body.itemName, 152 | price: body.price, 153 | currency: body.currency, 154 | categories: body.categories 155 | }); 156 | } 157 | -------------------------------------------------------------------------------- /Chapter07/modules/catalogV2.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (gfs, request, response) { 26 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | var options = { 41 | filename : result.itemId, 42 | }; 43 | gfs.exist(options, function(error, found) { 44 | if (found) { 45 | response.setHeader('Content-Type', 'application/json'); 46 | var imageUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + '/image'; 47 | response.setHeader('Image-Url', imageUrl); 48 | response.send(result); 49 | } else { 50 | response.json(result); 51 | } 52 | }); 53 | } 54 | }); 55 | } 56 | 57 | exports.findItemsByAttribute = function (key, value, response) { 58 | var filter = {}; 59 | filter[key] = value; 60 | CatalogItem.find(filter, function(error, result) { 61 | if (error) { 62 | console.error(error); 63 | response.writeHead(500, contentTypePlainText); 64 | response.end('Internal server error'); 65 | return; 66 | } else { 67 | if (!result) { 68 | if (response != null) { 69 | response.writeHead(200, contentTypeJson); 70 | response.end({}); 71 | } 72 | return; 73 | } 74 | if (response != null){ 75 | response.setHeader('Content-Type', 76 | 'application/json'); 77 | response.send(result); 78 | } 79 | } 80 | }); 81 | } 82 | 83 | exports.findItemsByCategory = function (category, response) { 84 | CatalogItem.find({categories: category}, function(error, result) { 85 | if (error) { 86 | console.error(error); 87 | response.writeHead(500, contentTypePlainText); 88 | return; 89 | } else { 90 | if (!result) { 91 | if (response != null) { 92 | response.writeHead(404, contentTypePlainText); 93 | response.end('Not Found'); 94 | } 95 | return; 96 | } 97 | 98 | if (response != null){ 99 | response.setHeader('Content-Type', 'application/json'); 100 | response.send(result); 101 | } 102 | console.log(result); 103 | } 104 | }); 105 | } 106 | 107 | exports.saveItem = function(request, response) { 108 | var item = toItem(request.body); 109 | console.log(item); 110 | item.save((error) => { 111 | if (!error) { 112 | item.save(); 113 | var locationUrl; 114 | if (!request.path.endsWith('/')) { 115 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + '/items/' + item.itemId; 116 | } else { 117 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + 'item/' + item.itemId; 118 | } 119 | response.setHeader('Location', locationUrl); 120 | response.writeHead(201, contentTypeJson); 121 | response.end(JSON.stringify(request.body)); 122 | } else { 123 | console.log(error); 124 | CatalogItem.findOne({itemId : item.itemId }, 125 | (error, result) => { 126 | console.log('Check if such an item exists'); 127 | if (error) { 128 | console.log(error); 129 | response.writeHead(500, contentTypePlainText); 130 | response.end('Internal Server Error'); 131 | } else { 132 | if (!result) { 133 | console.log('Item does not exist. Creating a new one'); 134 | item.save(); 135 | if (!request.path.endsWith('/')) { 136 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl+ request.path + '/' + item.itemId; 137 | } else { 138 | locationUrl = itemImageUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + item.itemId; 139 | } 140 | response.setHeader('Location', locationUrl); 141 | response.end(JSON.stringify(request.body)); 142 | } else { 143 | console.log('Updating existing item'); 144 | result.itemId = item.itemId; 145 | result.itemName = item.itemName; 146 | result.price = item.price; 147 | result.currency = item.currency; 148 | result.categories = item.categories; 149 | result.save(); 150 | response.json(JSON.stringify(result)); 151 | } 152 | } 153 | }); 154 | } 155 | }); 156 | } 157 | 158 | 159 | 160 | 161 | exports.remove = function (request, response) { 162 | console.log('Deleting item with id: ' + request.body.itemId); 163 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 164 | if (error) { 165 | console.log(error); 166 | if (response != null) { 167 | response.writeHead(500, contentTypePlainText); 168 | response.end('Internal server error'); 169 | } 170 | return; 171 | } else { 172 | if (!data) { 173 | console.log('Item not found'); 174 | if (response != null) { 175 | response.writeHead(404, contentTypePlainText); 176 | response.end('Not Found'); 177 | } 178 | return; 179 | } else { 180 | data.remove(function(error){ 181 | if (!error) { 182 | data.remove(); 183 | response.json({'Status': 'Successfully deleted'}); 184 | } 185 | else { 186 | console.log(error); 187 | response.writeHead(500, contentTypePlainText); 188 | response.end('Internal Server Error'); 189 | } 190 | }); 191 | } 192 | } 193 | }); 194 | } 195 | 196 | exports.paginate = function(model, request, response) { 197 | var pageSize = request.query.limit; 198 | var page = request.query.page; 199 | if (pageSize === undefined) { 200 | pageSize = 100; 201 | } 202 | if (page === undefined) { 203 | page = 1; 204 | } 205 | 206 | model.paginate({}, {page:page, limit:pageSize}, 207 | function (error, result){ 208 | if(error) { 209 | console.log(error); 210 | response.writeHead('500', 211 | {'Content-Type' : 'text/plain'}); 212 | response.end('Internal Server Error'); 213 | } 214 | else { 215 | response.json(result); 216 | } 217 | }); 218 | } 219 | 220 | exports.deleteImage = function(gfs, mongodb, itemId, response) { 221 | console.log('Deleting image for itemId:' + itemId); 222 | 223 | var options = { 224 | filename : itemId, 225 | }; 226 | 227 | var chunks = mongodb.collection('fs.files.chunks'); 228 | chunks.remove(options, function (error, image) { 229 | if (error) { 230 | console.log(error); 231 | response.send('500', 'Internal Server Error'); 232 | return; 233 | } else { 234 | console.log('Successfully deleted image for primary contact number: ' + itemId); 235 | } 236 | }); 237 | 238 | 239 | var files = mongodb.collection('fs.files'); 240 | 241 | files.remove(options, function (error, image) { 242 | if (error) { 243 | console.log(error); 244 | response.send('500', 'Internal Server Error'); 245 | return; 246 | } 247 | 248 | if (image === null) { 249 | response.send('404', 'Not found'); 250 | return; 251 | } 252 | else { 253 | console.log('Successfully deleted image for primary contact number: ' + itemId); 254 | response.json({'deleted': true}); 255 | } 256 | }); 257 | } 258 | 259 | 260 | exports.getImage = function(gfs, request, response) { 261 | readImage(gfs, request, response); 262 | }; 263 | 264 | exports.saveImage = function(gfs, request, response) { 265 | 266 | var writeStream = gfs.createWriteStream({ 267 | filename : request.params.itemId, 268 | mode : 'w' 269 | }); 270 | 271 | writeStream.on('error', function(error) { 272 | response.send('500', 'Internal Server Error'); 273 | console.log(error); 274 | return; 275 | }) 276 | 277 | writeStream.on('close', function() { 278 | readImage(gfs, request, response); 279 | }); 280 | 281 | request.pipe(writeStream); 282 | } 283 | 284 | function readImage(gfs, request, response) { 285 | 286 | var imageStream = gfs.createReadStream({ 287 | filename : request.params.itemId, 288 | mode : 'r' 289 | }); 290 | 291 | imageStream.on('error', function(error) { 292 | console.log(error); 293 | response.send('404', 'Not found'); 294 | return; 295 | }); 296 | 297 | var itemImageUrl = request.protocol + '://' + request.get('host') + request.baseUrl+ request.path; 298 | var itemUrl = itemImageUrl.substring(0, itemImageUrl.indexOf('/image')); 299 | response.setHeader('Content-Type', 'image/jpeg'); 300 | response.setHeader('Item-Url', itemUrl); 301 | 302 | 303 | imageStream.pipe(response); 304 | } 305 | 306 | 307 | function toItem(body) { 308 | return new CatalogItem({ 309 | itemId: body.itemId, 310 | itemName: body.itemName, 311 | price: body.price, 312 | currency: body.currency, 313 | categories: body.categories 314 | }); 315 | } 316 | -------------------------------------------------------------------------------- /Chapter07/package.json.txt: -------------------------------------------------------------------------------- 1 | { 2 | "name": "chapter7", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "start": "node ./bin/www", 7 | "test": "mocha test/routes" 8 | }, 9 | "dependencies": { 10 | "body-parser": "~1.13.2", 11 | "chai": "^4.1.2", 12 | "chai-http": "^3.0.0", 13 | "cookie-parser": "~1.3.5", 14 | "debug": "~2.2.0", 15 | "express": "^4.16.3", 16 | "express-cache-control": "^1.0.2", 17 | "express-paginate": "^0.3.0", 18 | "jade": "~1.11.0", 19 | "mocha": "^5.0.4", 20 | "mongoose-paginate": "^5.0.3", 21 | "morgan": "~1.6.1", 22 | "serve-favicon": "~2.3.0", 23 | "should": "^13.2.1", 24 | "swagger-ui": "^3.13.0", 25 | "swagger-ui-express": "^2.0.15" 26 | }, 27 | "main": "app.js", 28 | "directories": { 29 | "test": "test" 30 | }, 31 | "devDependencies": {}, 32 | "author": "", 33 | "license": "ISC", 34 | "description": "" 35 | } 36 | -------------------------------------------------------------------------------- /Chapter07/routes/catalog.js.txt: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const url = require('url'); 3 | const mongoose = require('mongoose'); 4 | const Grid = require('gridfs-stream'); 5 | const router = express.Router(); 6 | const CacheControl = require("express-cache-control") 7 | 8 | const catalogV1 = require('../modules/catalogV1'); 9 | const catalogV2 = require('../modules/catalogV2'); 10 | 11 | const model = require('../model/item.js'); 12 | 13 | var cache = new CacheControl().middleware; 14 | 15 | router.get('/v1/', function(request, response, next) { 16 | catalogV1.findAllItems(response); 17 | }); 18 | 19 | router.get('/v1/item/:itemId', function(request, response, next) { 20 | console.log(request.url + ' : querying for ' + request.params.itemId); 21 | catalogV1.findItemById(request.params.itemId, response); 22 | }); 23 | 24 | 25 | 26 | router.get('/v1/:categoryId', function(request, response, next) { 27 | console.log(request.url + ' : querying for ' + request.params.categoryId); 28 | catalogV1.findItemsByCategory(request.params.categoryId, response); 29 | }); 30 | 31 | router.post('/v1/', function(request, response, next) { 32 | catalogV1.saveItem(request, response); 33 | }); 34 | 35 | router.put('/v1/', function(request, response, next) { 36 | catalogV1.saveItem(request, response); 37 | }); 38 | 39 | router.put('/v1/:itemId', function(request, response, next) { 40 | catalogV1.saveItem(request, response); 41 | }); 42 | 43 | router.delete('/v1/item/:itemId', function(request, response, next) { 44 | catalogV1.remove(request, response); 45 | }); 46 | 47 | router.get('/v2/:categoryId', function(request, response, next) { 48 | console.log(request.url + ' : querying for ' + request.params.categoryId); 49 | catalogV2.findItemsByCategory(request.params.categoryId, response); 50 | }); 51 | 52 | router.get('/v2/item/:itemId', function(request, response, next) { 53 | console.log(request.url + ' : querying for ' + request.params.itemId); 54 | var gfs = Grid(model.connection.db, mongoose.mongo); 55 | 56 | catalogV2.findItemById(gfs, request, response); 57 | }); 58 | 59 | router.post('/v2/', function(request, response, next) { 60 | catalogV2.saveItem(request, response); 61 | }); 62 | 63 | router.put('/v2/', function(request, response, next) { 64 | catalogV2.saveItem(request, response); 65 | }); 66 | 67 | router.put('/v2/:itemId', function(request, response, next) { 68 | catalogV1.saveItem(request, response); 69 | }); 70 | 71 | router.delete('/v2/item/:itemId', function(request, response, next) { 72 | catalogV2.remove(request, response); 73 | }); 74 | 75 | router.get('/', function(request, response) { 76 | console.log('Redirecting to v2'); 77 | response.writeHead(302, {'Location' : '/catalog/v2/'}); 78 | response.end('Version 2 is is available at /catalog/v2/: '); 79 | }); 80 | 81 | router.get('/v2/', cache('minutes', 1), function(request, response) { 82 | var getParams = url.parse(request.url, true).query; 83 | if (getParams['page'] !=null || getParams['limit'] != null) { 84 | catalogV2.paginate(model.CatalogItem, request, response); 85 | } else { 86 | var key = Object.keys(getParams)[0]; 87 | var value = getParams[key]; 88 | catalogV2.findItemsByAttribute(key, value, response); 89 | } 90 | }); 91 | 92 | router.get('/v2/item/:itemId/image', 93 | function(request, response){ 94 | var gfs = Grid(model.connection.db, mongoose.mongo); 95 | catalogV2.getImage(gfs, request, response); 96 | }); 97 | 98 | router.get('/item/:itemId/image', 99 | function(request, response){ 100 | var gfs = Grid(model.connection.db, mongoose.mongo); 101 | catalogV2.getImage(gfs, request, response); 102 | }); 103 | 104 | router.post('/v2/item/:itemId/image', 105 | function(request, response){ 106 | var gfs = Grid(model.connection.db, mongoose.mongo); 107 | catalogV2.saveImage(gfs, request, response); 108 | }); 109 | 110 | router.post('/item/:itemId/image', 111 | function(request, response){ 112 | var gfs = Grid(model.connection.db, mongoose.mongo); 113 | catalogV2.saveImage(gfs, request.params.itemId, response); 114 | }); 115 | 116 | router.put('/v2/item/:itemId/image', 117 | function(request, response){ 118 | var gfs = Grid(model.connection.db, mongoose.mongo); 119 | catalogV2.saveImage (gfs, request.params.itemId, response); 120 | }); 121 | 122 | router.put('/item/:itemId/image', 123 | function(request, response){ 124 | var gfs = Grid(model.connection.db, mongoose.mongo); 125 | catalogV2.saveImage(gfs, request.params.itemId, response); 126 | }); 127 | 128 | router.delete('/v2/item/:itemId/image', 129 | function(request, response){ 130 | var gfs = Grid(model.connection.db, mongoose.mongo); 131 | catalogV2.deleteImage(gfs, model.connection, 132 | request.params.itemId, response); 133 | }); 134 | 135 | router.delete('/item/:itemId/image', 136 | function(request, response){ 137 | var gfs = Grid(model.connection.db, mongoose.mongo); 138 | catalogV2.deleteImage(gfs, model.connection, request.params.itemId, response); 139 | }); 140 | 141 | module.exports = router; 142 | -------------------------------------------------------------------------------- /Chapter07/routes/index.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var router = express.Router(); 3 | 4 | /* GET home page. */ 5 | router.get('/', function(req, res, next) { 6 | res.render('index', { title: 'Express' }); 7 | }); 8 | 9 | module.exports = router; 10 | -------------------------------------------------------------------------------- /Chapter07/static/catalog.wadl: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | itemId 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | -------------------------------------------------------------------------------- /Chapter07/static/swagger.json: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Catalog API Documentation", 5 | "version": "v1" 6 | }, 7 | "paths": { 8 | "/catalog/{categoryId}": { 9 | "get": { 10 | "operationId": "getCategoryV2", 11 | "summary": "Get all items for a category ", 12 | "produces": [ 13 | "application/json" 14 | ], 15 | "responses": { 16 | "200": { 17 | "description": "200 OK", 18 | "examples": { 19 | "application/json": [ 20 | { 21 | "_id": "5a4c004b0eed73835833cc9a", 22 | "itemId": "1", 23 | "itemName": "Sports Watch", 24 | "price": 100, 25 | "currency": "EUR", 26 | "__v": 0, 27 | "categories": [ 28 | "Watches", 29 | "Sports Watches" 30 | ] 31 | }, 32 | { 33 | "_id": "5a4c0b7aad0ebbce584593ee", 34 | "itemId": "2", 35 | "itemName": "Sports Watch 2", 36 | "price": 100, 37 | "currency": "USD", 38 | "__v": 0, 39 | "categories": [ 40 | "Watches", 41 | "Sports Watches" 42 | ] 43 | } 44 | ] 45 | } 46 | }, 47 | "404": { 48 | "description": "404 Not Found" 49 | }, 50 | "500": { 51 | "description": "500 Internal Server Error" 52 | } 53 | } 54 | } 55 | }, 56 | "/catalog/v1/{categoryId}": { 57 | "get": { 58 | "operationId": "getCategoryV1", 59 | "summary": "Get all items for a category ", 60 | "produces": [ 61 | "application/json" 62 | ], 63 | "responses": { 64 | "200": { 65 | "description": "200 OK", 66 | "examples": { 67 | "application/json": [ 68 | { 69 | "_id": "5a4c004b0eed73835833cc9a", 70 | "itemId": "1", 71 | "itemName": "Sports Watch", 72 | "price": 100, 73 | "currency": "EUR", 74 | "__v": 0, 75 | "categories": [ 76 | "Watches", 77 | "Sports Watches" 78 | ] 79 | }, 80 | { 81 | "_id": "5a4c0b7aad0ebbce584593ee", 82 | "itemId": "2", 83 | "itemName": "Sports Watch 2", 84 | "price": 100, 85 | "currency": "USD", 86 | "__v": 0, 87 | "categories": [ 88 | "Watches", 89 | "Sports Watches" 90 | ] 91 | } 92 | ] 93 | } 94 | }, 95 | "404": { 96 | "description": "404 Not Found" 97 | }, 98 | "500": { 99 | "description": "500 Internal Server Error" 100 | } 101 | } 102 | } 103 | }, 104 | "/catalog/v2/{categoryId}": { 105 | "get": { 106 | "operationId": "getCategoryV2", 107 | "summary": "Get all items for a category ", 108 | "produces": [ 109 | "application/json" 110 | ], 111 | "responses": { 112 | "200": { 113 | "description": "200 OK", 114 | "examples": { 115 | "application/json": [ 116 | { 117 | "_id": "5a4c004b0eed73835833cc9a", 118 | "itemId": "1", 119 | "itemName": "Sports Watch", 120 | "price": 100, 121 | "currency": "EUR", 122 | "__v": 0, 123 | "categories": [ 124 | "Watches", 125 | "Sports Watches" 126 | ] 127 | }, 128 | { 129 | "_id": "5a4c0b7aad0ebbce584593ee", 130 | "itemId": "2", 131 | "itemName": "Sports Watch 2", 132 | "price": 100, 133 | "currency": "USD", 134 | "__v": 0, 135 | "categories": [ 136 | "Watches", 137 | "Sports Watches" 138 | ] 139 | } 140 | ] 141 | } 142 | }, 143 | "404": { 144 | "description": "404 Not Found" 145 | }, 146 | "500": { 147 | "description": "500 Internal Server Error" 148 | } 149 | } 150 | } 151 | }, 152 | "/catalog/item/{itemId}": { 153 | "get": { 154 | "operationId": "getItemV2", 155 | "summary": "Get an existing item", 156 | "produces": [ 157 | "application/json" 158 | ], 159 | "responses": { 160 | "200": { 161 | "description": "200 OK", 162 | "examples": { 163 | "application/json": { 164 | "_id": "5a4c004b0eed73835833cc9a", 165 | "itemId": "1", 166 | "itemName": "Sports Watch", 167 | "price": 100, 168 | "currency": "EUR", 169 | "__v": 0, 170 | "categories": [ 171 | "Watches", 172 | "Sports Watches" 173 | ] 174 | } 175 | } 176 | }, 177 | "404": { 178 | "description": "404 Not Found" 179 | }, 180 | "500": { 181 | "description": "500 Internal Server Error" 182 | } 183 | } 184 | }, 185 | "post": { 186 | "404": { 187 | "description": "404 Not Found" 188 | }, 189 | "500": { 190 | "description": "500 Internal Server Error" 191 | }, 192 | "operationId": "postItemV2", 193 | "summary": "Creates new or updates existing item", 194 | "produces": [ 195 | "application/json" 196 | ], 197 | "responses": { 198 | "200": { 199 | "itemId": 19, 200 | "itemName": "Sports Watch 19", 201 | "price": 100, 202 | "currency": "USD", 203 | "__v": 0, 204 | "categories": [ 205 | "Watches", 206 | "Sports Watches" 207 | ] 208 | }, 209 | "201": { 210 | "itemId": 19, 211 | "itemName": "Sports Watch 19", 212 | "price": 100, 213 | "currency": "USD", 214 | "__v": 0, 215 | "categories": [ 216 | "Watches", 217 | "Sports Watches" 218 | ] 219 | }, 220 | "500": "text/html" 221 | } 222 | }, 223 | "put": { 224 | "404": { 225 | "description": "404 Not Found" 226 | }, 227 | "500": { 228 | "description": "500 Internal Server Error" 229 | }, 230 | "operationId": "putItemV2", 231 | "summary": "Creates new or updates existing item", 232 | "produces": [ 233 | "application/json" 234 | ], 235 | "responses": { 236 | "200": { 237 | "itemId": 19, 238 | "itemName": "Sports Watch 19", 239 | "price": 100, 240 | "currency": "USD", 241 | "__v": 0, 242 | "categories": [ 243 | "Watches", 244 | "Sports Watches" 245 | ] 246 | }, 247 | "201": { 248 | "itemId": 19, 249 | "itemName": "Sports Watch 19", 250 | "price": 100, 251 | "currency": "USD", 252 | "__v": 0, 253 | "categories": [ 254 | "Watches", 255 | "Sports Watches" 256 | ] 257 | }, 258 | "500": "text/html" 259 | } 260 | }, 261 | "delete": { 262 | "404": { 263 | "description": "404 Not Found" 264 | }, 265 | "500": { 266 | "description": "500 Internal Server Error" 267 | }, 268 | "operationId": "deleteItemV2", 269 | "summary": "Deletes an existing item", 270 | "produces": [ 271 | "application/json" 272 | ], 273 | "responses": { 274 | "200": { 275 | "deleted": true 276 | }, 277 | "500": "text/html" 278 | } 279 | } 280 | }, 281 | "/catalog/v1/item/{id}": { 282 | "get": { 283 | "operationId": "getItemV1", 284 | "summary": "Get an existing item", 285 | "produces": [ 286 | "application/json" 287 | ], 288 | "responses": { 289 | "200": { 290 | "description": "200 OK", 291 | "examples": { 292 | "application/json": { 293 | "_id": "5a4c004b0eed73835833cc9a", 294 | "itemId": "1", 295 | "itemName": "Sports Watch", 296 | "price": 100, 297 | "currency": "EUR", 298 | "__v": 0, 299 | "categories": [ 300 | "Watches", 301 | "Sports Watches" 302 | ] 303 | } 304 | } 305 | }, 306 | "404": { 307 | "description": "404 Not Found" 308 | }, 309 | "500": { 310 | "description": "500 Internal Server Error" 311 | } 312 | } 313 | }, 314 | "post": { 315 | "404": { 316 | "description": "404 Not Found" 317 | }, 318 | "500": { 319 | "description": "500 Internal Server Error" 320 | }, 321 | "operationId": "postItemV1", 322 | "summary": "Creates new or updates existing item", 323 | "produces": [ 324 | "application/json" 325 | ], 326 | "responses": { 327 | "200": { 328 | "itemId": 19, 329 | "itemName": "Sports Watch 19", 330 | "price": 100, 331 | "currency": "USD", 332 | "__v": 0, 333 | "categories": [ 334 | "Watches", 335 | "Sports Watches" 336 | ] 337 | }, 338 | "201": { 339 | "itemId": 19, 340 | "itemName": "Sports Watch 19", 341 | "price": 100, 342 | "currency": "USD", 343 | "__v": 0, 344 | "categories": [ 345 | "Watches", 346 | "Sports Watches" 347 | ] 348 | }, 349 | "500": "text/html" 350 | } 351 | }, 352 | "put": { 353 | "404": { 354 | "description": "404 Not Found" 355 | }, 356 | "500": { 357 | "description": "500 Internal Server Error" 358 | }, 359 | "operationId": "putItemV1", 360 | "summary": "Creates new or updates existing item", 361 | "produces": [ 362 | "application/json" 363 | ], 364 | "responses": { 365 | "200": { 366 | "itemId": 19, 367 | "itemName": "Sports Watch 19", 368 | "price": 100, 369 | "currency": "USD", 370 | "__v": 0, 371 | "categories": [ 372 | "Watches", 373 | "Sports Watches" 374 | ] 375 | }, 376 | "201": { 377 | "itemId": 19, 378 | "itemName": "Sports Watch 19", 379 | "price": 100, 380 | "currency": "USD", 381 | "__v": 0, 382 | "categories": [ 383 | "Watches", 384 | "Sports Watches" 385 | ] 386 | }, 387 | "500": "text/html" 388 | } 389 | }, 390 | "delete": { 391 | "404": { 392 | "description": "404 Not Found" 393 | }, 394 | "500": { 395 | "description": "500 Internal Server Error" 396 | }, 397 | "operationId": "deleteItemV1", 398 | "summary": "Deletes an existing item", 399 | "produces": [ 400 | "application/json" 401 | ], 402 | "responses": { 403 | "200": { 404 | "deleted": true 405 | }, 406 | "500": "text/html" 407 | } 408 | } 409 | }, 410 | "/catalog/v2/item/{id}": { 411 | "get": { 412 | "operationId": "getItemV2", 413 | "summary": "Get an existing item", 414 | "produces": [ 415 | "application/json" 416 | ], 417 | "responses": { 418 | "200": { 419 | "description": "200 OK", 420 | "examples": { 421 | "application/json": 422 | { 423 | "_id": "5a4c004b0eed73835833cc9a", 424 | "itemId": "1", 425 | "itemName": "Sports Watch", 426 | "price": 100, 427 | "currency": "EUR", 428 | "__v": 0, 429 | "categories": [ 430 | "Watches", 431 | "Sports Watches" 432 | ] 433 | } 434 | } 435 | }, 436 | "404": { 437 | "description": "404 Not Found" 438 | }, 439 | "500": { 440 | "description": "500 Internal Server Error" 441 | } 442 | } 443 | }, 444 | "post": { 445 | "operationId": "postItemImageV2", 446 | "summary": "Provide image for existing item", 447 | "produces": [ 448 | "image/jpeg" 449 | ], 450 | "responses": { 451 | "200": { 452 | "404": { 453 | "description": "404 Not Found" 454 | }, 455 | "500": { 456 | "description": "500 Internal Server Error" 457 | }, 458 | "description": "200 OK", 459 | "examples": { 460 | "image/jpeg": "image" 461 | } 462 | } 463 | } 464 | }, 465 | "put": { 466 | "404": { 467 | "description": "404 Not Found" 468 | }, 469 | "500": { 470 | "description": "500 Internal Server Error" 471 | }, 472 | "operationId": "putItemV2", 473 | "summary": "Creates new or updates existing item", 474 | "produces": [ 475 | "application/json" 476 | ], 477 | "responses": { 478 | "200": { 479 | "itemId": 19, 480 | "itemName": "Sports Watch 19", 481 | "price": 100, 482 | "currency": "USD", 483 | "__v": 0, 484 | "categories": [ 485 | "Watches", 486 | "Sports Watches" 487 | ] 488 | }, 489 | "201": { 490 | "itemId": 19, 491 | "itemName": "Sports Watch 19", 492 | "price": 100, 493 | "currency": "USD", 494 | "__v": 0, 495 | "categories": [ 496 | "Watches", 497 | "Sports Watches" 498 | ] 499 | }, 500 | "500": "text/html" 501 | } 502 | }, 503 | "delete": { 504 | "404": { 505 | "description": "404 Not Found" 506 | }, 507 | "500": { 508 | "description": "500 Internal Server Error" 509 | }, 510 | "operationId": "deleteItemV2", 511 | "summary": "Deletes an existing item", 512 | "produces": [ 513 | "application/json" 514 | ], 515 | "responses": { 516 | "200": { 517 | "deleted": true 518 | }, 519 | "500": "text/html" 520 | } 521 | } 522 | }, 523 | "/catalog/v2/item/{id}/image": { 524 | "get": { 525 | "404": { 526 | "description": "404 Not Found" 527 | }, 528 | "500": { 529 | "description": "500 Internal Server Error" 530 | }, 531 | "operationId": "getItemImageV2", 532 | "summary": "Get an image bound to an existing item", 533 | "produces": [ 534 | "application/json" 535 | ], 536 | "responses": { 537 | "200": "application/jpeg" 538 | } 539 | }, 540 | "post": { 541 | "404": { 542 | "description": "404 Not Found" 543 | }, 544 | "500": { 545 | "description": "500 Internal Server Error" 546 | }, 547 | "operationId": "postItemImageV2", 548 | "summary": "Creates or updates an image bound to an existing item", 549 | "produces": [ 550 | "application/json" 551 | ], 552 | "responses": { 553 | "200": "application/jpeg" 554 | } 555 | }, 556 | "put": { 557 | "404": { 558 | "description": "404 Not Found" 559 | }, 560 | "500": { 561 | "description": "500 Internal Server Error" 562 | }, 563 | "operationId": "putItemV2", 564 | "summary": "Creates or updates an image bound to an existing item", 565 | "produces": [ 566 | "application/json" 567 | ], 568 | "responses": { 569 | "200": "application/jpeg" 570 | } 571 | }, 572 | "delete": { 573 | "404": { 574 | "description": "404 Not Found" 575 | }, 576 | "500": { 577 | "description": "500 Internal Server Error" 578 | }, 579 | "operationId": "deleteItemV2", 580 | "summary": "Creates or updates an image bound to an existing item", 581 | "produces": [ 582 | "application/json" 583 | ], 584 | "responses": { 585 | "200": "application/jpeg" 586 | } 587 | } 588 | }, 589 | "/catalog/item/": { 590 | "post": { 591 | "404": { 592 | "description": "404 Not Found" 593 | }, 594 | "500": { 595 | "description": "500 Internal Server Error" 596 | }, 597 | "operationId": "postItemV21", 598 | "summary": "Creates new or updates existing item", 599 | "produces": [ 600 | "application/json" 601 | ], 602 | "responses": { 603 | "200": { 604 | "itemId": 19, 605 | "itemName": "Sports Watch 19", 606 | "price": 100, 607 | "currency": "USD", 608 | "__v": 0, 609 | "categories": [ 610 | "Watches", 611 | "Sports Watches" 612 | ] 613 | }, 614 | "201": { 615 | "itemId": 19, 616 | "itemName": "Sports Watch 19", 617 | "price": 100, 618 | "currency": "USD", 619 | "__v": 0, 620 | "categories": [ 621 | "Watches", 622 | "Sports Watches" 623 | ] 624 | }, 625 | "500": "text/html" 626 | } 627 | } 628 | }, 629 | "/catalog/v1/item/": { 630 | "post": { 631 | "operationId": "postItemV1", 632 | "summary": "Create new or update existing item", 633 | "produces": [ 634 | "image/jpeg" 635 | ], 636 | "responses": { 637 | "200": { 638 | "description": "200 OK", 639 | "examples": { 640 | "application/json": 641 | { 642 | "_id": "5a4c004b0eed73835833cc9a", 643 | "itemId": "1", 644 | "itemName": "Sports Watch", 645 | "price": 100, 646 | "currency": "EUR", 647 | "__v": 0, 648 | "categories": [ 649 | "Watches", 650 | "Sports Watches" 651 | ] 652 | } 653 | } 654 | }, 655 | "404": { 656 | "description": "404 Not Found" 657 | }, 658 | "500": { 659 | "description": "500 Internal Server Error" 660 | } 661 | } 662 | } 663 | }, 664 | "/catalog/v2/item/": { 665 | "post": { 666 | "operationId": "postItemV2", 667 | "summary": "Create new or update existing item", 668 | "produces": [ 669 | "image/jpeg" 670 | ], 671 | "responses": { 672 | "200": { 673 | "description": "200 OK", 674 | "examples": { 675 | "application/json": 676 | { 677 | "_id": "5a4c004b0eed73835833cc9a", 678 | "itemId": "1", 679 | "itemName": "Sports Watch", 680 | "price": 100, 681 | "currency": "EUR", 682 | "__v": 0, 683 | "categories": [ 684 | "Watches", 685 | "Sports Watches" 686 | ] 687 | } 688 | } 689 | }, 690 | "404": { 691 | "description": "404 Not Found" 692 | }, 693 | "500": { 694 | "description": "500 Internal Server Error" 695 | } 696 | } 697 | } 698 | } 699 | }, 700 | "consumes": [ 701 | "application/json" 702 | ] 703 | } 704 | -------------------------------------------------------------------------------- /Chapter07/test/model/model-test.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var should = require('should'); 3 | var prepare = require('./prepare'); 4 | 5 | 6 | 7 | const model = require('../model/item.js'); 8 | const CatalogItem = model.CatalogItem; 9 | 10 | mongoose.createConnection('mongodb://localhost/catalog'); 11 | 12 | 13 | describe('CatalogItem: models', function () { 14 | describe('#create()', function () { 15 | it('Should create a new CatalogItem', function (done) { 16 | 17 | var item = { 18 | "itemId": "1", 19 | "itemName": "Sports Watch", 20 | "price": 100, 21 | "currency": "EUR", 22 | "categories": [ 23 | "Watches", 24 | "Sports Watches" 25 | ] 26 | 27 | }; 28 | 29 | CatalogItem.create(item, function (err, createdItem) { 30 | // Check that no error occured 31 | should.not.exist(err); 32 | // Assert that the returned item has is what we expect 33 | 34 | createdItem.itemId.should.equal('1'); 35 | createdItem.itemName.should.equal('Sports Watch'); 36 | createdItem.price.should.equal(100); 37 | createdItem.currency.should.equal('EUR'); 38 | createdItem.categories[0].should.equal('Watches'); 39 | createdItem.categories[1].should.equal('Sports Watches'); 40 | //Notify mocha that the test has completed 41 | done(); 42 | }); 43 | }); 44 | }); 45 | 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /Chapter07/test/model/prepare.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | beforeEach(function (done) { 4 | function clearDatabase() { 5 | for (var i in mongoose.connection.collections) { 6 | mongoose.connection.collections[i].remove(function() {}); 7 | } 8 | return done(); 9 | } 10 | 11 | if (mongoose.connection.readyState === 0) { 12 | mongoose.connect(config.db.test, function (err) { 13 | if (err) { 14 | throw err; 15 | } 16 | return clearDatabase(); 17 | }); 18 | } else { 19 | return clearDatabase(); 20 | } 21 | }); 22 | 23 | afterEach(function (done) { 24 | mongoose.disconnect(); 25 | return done(); 26 | }); 27 | -------------------------------------------------------------------------------- /Chapter07/test/routes/routes-test.js.txt: -------------------------------------------------------------------------------- 1 | var expressApp = require('../../app'); 2 | var chai = require('chai'); 3 | var chaiHttp = require('chai-http'); 4 | var mongoose = require('mongoose'); 5 | var should = chai.should(); 6 | 7 | 8 | mongoose.createConnection('mongodb://localhost/catalog-test'); 9 | 10 | chai.use(chaiHttp); 11 | 12 | 13 | describe('/get', function() { 14 | it('get test', function(done) { 15 | chai.request(expressApp) 16 | .get('/catalog/v2') 17 | .end(function(error, response) { 18 | should.equal(200 , response.status); 19 | done(); 20 | }); 21 | }); 22 | }); 23 | 24 | describe('/post', function() { 25 | it('post test', function(done) { 26 | var item ={ 27 | "itemId":19, 28 | "itemName": "Sports Watch 10", 29 | "price": 100, 30 | "currency": "USD", 31 | "__v": 0, 32 | "categories": [ 33 | "Watches", 34 | "Sports Watches" 35 | ] 36 | }; 37 | chai.request(expressApp) 38 | .post('/catalog/v2') 39 | .send(item ) 40 | .end(function(err, response){ 41 | should.equal(201, response.status) 42 | done(); 43 | }); 44 | }); 45 | }); 46 | 47 | describe('/delete', function() { 48 | it('delete test', function(done) { 49 | var item ={ 50 | "itemId":19, 51 | "itemName": "Sports Watch 10", 52 | "price": 100, 53 | "currency": "USD", 54 | "__v": 0, 55 | "categories": [ 56 | "Watches", 57 | "Sports Watches" 58 | ] 59 | }; 60 | chai.request(expressApp) 61 | .delete('/catalog/v2/item/19') 62 | .send(item ) 63 | .end(function(err, response){ 64 | should.equal(200, response.status) 65 | done(); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /Chapter08/item.html.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Item 4 | 5 | 6 | 7 | 45 |
46 |
47 |
Item:
48 |
49 |
50 |
51 |
Price:
52 |
53 |
54 |
55 |
Categories:
56 |
57 |
58 |
59 |
60 | 61 | 62 | -------------------------------------------------------------------------------- /Chapter08/new.html.txt: -------------------------------------------------------------------------------- 1 | 2 | 3 | Item 4 | 7 | 8 | 9 | 10 | 11 | 37 |
38 |
39 |
Id:
40 |
41 | 42 |
Item:
43 |
44 |
45 |
46 |
Price:
47 |
48 |
49 |
50 |
Categories:
51 |
52 |
53 |
54 |
55 | 56 | 57 | -------------------------------------------------------------------------------- /Chapter09/app.js.txt: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var path = require('path'); 3 | var favicon = require('serve-favicon'); 4 | var logger = require('morgan'); 5 | var cookieParser = require('cookie-parser'); 6 | var bodyParser = require('body-parser'); 7 | 8 | var routes = require('./routes/index'); 9 | var catalog = require('./routes/catalog'); 10 | var expressPaginate = require('express-paginate') 11 | var app = express(); 12 | const cors = require('cors'); 13 | 14 | // view engine setup 15 | app.set('views', path.join(__dirname, 'views')); 16 | app.set('view engine', 'jade'); 17 | 18 | // uncomment after placing your favicon in /public 19 | //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); 20 | app.use(logger('dev')); 21 | app.use(bodyParser.json()); 22 | //app.use(bodyParser.urlencoded({ extended: false })); 23 | app.use(cookieParser()); 24 | app.use(express.static(path.join(__dirname, 'public'))); 25 | 26 | //app.use(cors()); 27 | app.use('/', routes); 28 | app.use('/catalog', catalog); 29 | app.use(expressPaginate.middleware(10,100)); 30 | 31 | 32 | const swaggerUi = require('swagger-ui-express'); 33 | const swaggerDocument = require('./static/swagger.json'); 34 | 35 | app.use('/catalog/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); 36 | app.use('/catalog/static', express.static('static')) 37 | // catch 404 and forward to error handler 38 | app.use(function(req, res, next) { 39 | var err = new Error('Not Found'); 40 | err.status = 404; 41 | next(err); 42 | }); 43 | 44 | // error handlers 45 | 46 | // development error handler 47 | // will print stacktrace 48 | if (app.get('env') === 'development') { 49 | app.use(function(err, req, res, next) { 50 | res.status(err.status || 500); 51 | res.render('error', { 52 | message: err.message, 53 | error: err 54 | }); 55 | }); 56 | } 57 | 58 | // production error handler 59 | // no stacktraces leaked to user 60 | app.use(function(err, req, res, next) { 61 | res.status(err.status || 500); 62 | res.render('error', { 63 | message: err.message, 64 | error: {} 65 | }); 66 | }); 67 | 68 | 69 | module.exports = app; 70 | -------------------------------------------------------------------------------- /Chapter09/doc/index.js.txt: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | 3 | var fs = require('fs'); 4 | var express = require('express'); 5 | 6 | var favIconHtml = '' + 7 | '' 8 | 9 | 10 | var setup = function (swaggerDoc, opts, options, customCss, customfavIcon, swaggerUrl, customeSiteTitle) { 11 | var isExplorer 12 | var customJs 13 | if (opts && typeof opts === 'object') { 14 | isExplorer = opts.explorer 15 | options = opts.swaggerOptions 16 | customCss = opts.customCss 17 | customJs = opts.customJs 18 | customfavIcon = opts.customfavIcon 19 | swaggerUrl = opts.swaggerUrl 20 | customeSiteTitle = opts.customSiteTitle 21 | } else { 22 | //support legacy params based function 23 | isExplorer = opts 24 | } 25 | options = options || {}; 26 | var explorerString = isExplorer ? '' : '.swagger-ui .topbar .download-url-wrapper { display: none }'; 27 | customCss = explorerString + ' ' + customCss || explorerString; 28 | customfavIcon = customfavIcon || false; 29 | customeSiteTitle = customeSiteTitle || 'Swagger UI'; 30 | var html = fs.readFileSync(__dirname + '/indexTemplate.html'); 31 | try { 32 | fs.unlinkSync(__dirname + '/index.html'); 33 | } catch (e) { 34 | 35 | } 36 | 37 | var favIconString = customfavIcon ? '' : favIconHtml; 38 | var htmlWithCustomCss = html.toString().replace('<% customCss %>', customCss); 39 | var htmlWithFavIcon = htmlWithCustomCss.replace('<% favIconString %>', favIconString); 40 | var htmlWithCustomJs = htmlWithFavIcon.replace('<% customJs %>', customJs ? `` : ''); 41 | 42 | var initOptions = { 43 | swaggerDoc: swaggerDoc || undefined, 44 | customOptions: options, 45 | swaggerUrl: swaggerUrl || undefined 46 | } 47 | var htmlWithOptions = htmlWithCustomJs.replace('<% swaggerOptions %>', JSON.stringify(initOptions)).replace('<% title %>', customeSiteTitle) 48 | 49 | return function (req, res) { res.send(htmlWithOptions) }; 50 | }; 51 | 52 | var serve = express.static(__dirname + '/static'); 53 | 54 | var stringify = function (obj, prop) { 55 | var placeholder = '____FUNCTIONPLACEHOLDER____'; 56 | var fns = []; 57 | var json = JSON.stringify(obj, function (key, value) { 58 | if (typeof value === 'function') { 59 | fns.push(value); 60 | return placeholder; 61 | } 62 | return value; 63 | }, 2); 64 | json = json.replace(new RegExp('"' + placeholder + '"', 'g'), function (_) { 65 | return fns.shift(); 66 | }); 67 | return json + ';'; 68 | }; 69 | 70 | module.exports = { 71 | setup: setup, 72 | serve: serve 73 | }; 74 | -------------------------------------------------------------------------------- /Chapter09/doc/swagger.json.txt: -------------------------------------------------------------------------------- 1 | { 2 | "swagger": "2.0", 3 | "info": { 4 | "title": "Catalog API Documentation", 5 | "version": "v1" 6 | }, 7 | "paths": { 8 | "/catalog/{categoryId}": { 9 | "get": { 10 | "operationId": "getCategoryV2", 11 | "summary": "Get all items for a category ", 12 | "produces": [ 13 | "application/json" 14 | ], 15 | "responses": { 16 | "200": { 17 | "description": "200 OK", 18 | "examples": { 19 | "application/json": [ 20 | { 21 | "_id": "5a4c004b0eed73835833cc9a", 22 | "itemId": "1", 23 | "itemName": "Sports Watch", 24 | "price": 100, 25 | "currency": "EUR", 26 | "__v": 0, 27 | "categories": [ 28 | "Watches", 29 | "Sports Watches" 30 | ] 31 | }, 32 | { 33 | "_id": "5a4c0b7aad0ebbce584593ee", 34 | "itemId": "2", 35 | "itemName": "Sports Watch 2", 36 | "price": 100, 37 | "currency": "USD", 38 | "__v": 0, 39 | "categories": [ 40 | "Watches", 41 | "Sports Watches" 42 | ] 43 | } 44 | ] 45 | } 46 | }, 47 | "404": { 48 | "description": "404 Not Found" 49 | }, 50 | "500": { 51 | "description": "500 Internal Server Error" 52 | } 53 | } 54 | } 55 | }, 56 | "/catalog/v1/{categoryId}": { 57 | "get": { 58 | "operationId": "getCategoryV1", 59 | "summary": "Get all items for a category ", 60 | "produces": [ 61 | "application/json" 62 | ], 63 | "responses": { 64 | "200": { 65 | "description": "200 OK", 66 | "examples": { 67 | "application/json": [ 68 | { 69 | "_id": "5a4c004b0eed73835833cc9a", 70 | "itemId": "1", 71 | "itemName": "Sports Watch", 72 | "price": 100, 73 | "currency": "EUR", 74 | "__v": 0, 75 | "categories": [ 76 | "Watches", 77 | "Sports Watches" 78 | ] 79 | }, 80 | { 81 | "_id": "5a4c0b7aad0ebbce584593ee", 82 | "itemId": "2", 83 | "itemName": "Sports Watch 2", 84 | "price": 100, 85 | "currency": "USD", 86 | "__v": 0, 87 | "categories": [ 88 | "Watches", 89 | "Sports Watches" 90 | ] 91 | } 92 | ] 93 | } 94 | }, 95 | "404": { 96 | "description": "404 Not Found" 97 | }, 98 | "500": { 99 | "description": "500 Internal Server Error" 100 | } 101 | } 102 | } 103 | }, 104 | "/catalog/v2/{categoryId}": { 105 | "get": { 106 | "operationId": "getCategoryV2", 107 | "summary": "Get all items for a category ", 108 | "produces": [ 109 | "application/json" 110 | ], 111 | "responses": { 112 | "200": { 113 | "description": "200 OK", 114 | "examples": { 115 | "application/json": [ 116 | { 117 | "_id": "5a4c004b0eed73835833cc9a", 118 | "itemId": "1", 119 | "itemName": "Sports Watch", 120 | "price": 100, 121 | "currency": "EUR", 122 | "__v": 0, 123 | "categories": [ 124 | "Watches", 125 | "Sports Watches" 126 | ] 127 | }, 128 | { 129 | "_id": "5a4c0b7aad0ebbce584593ee", 130 | "itemId": "2", 131 | "itemName": "Sports Watch 2", 132 | "price": 100, 133 | "currency": "USD", 134 | "__v": 0, 135 | "categories": [ 136 | "Watches", 137 | "Sports Watches" 138 | ] 139 | } 140 | ] 141 | } 142 | }, 143 | "404": { 144 | "description": "404 Not Found" 145 | }, 146 | "500": { 147 | "description": "500 Internal Server Error" 148 | } 149 | } 150 | } 151 | }, 152 | "/catalog/item/{itemId}": { 153 | "get": { 154 | "operationId": "getItemV2", 155 | "summary": "Get an existing item", 156 | "produces": [ 157 | "application/json" 158 | ], 159 | "responses": { 160 | "200": { 161 | "description": "200 OK", 162 | "examples": { 163 | "application/json": { 164 | "_id": "5a4c004b0eed73835833cc9a", 165 | "itemId": "1", 166 | "itemName": "Sports Watch", 167 | "price": 100, 168 | "currency": "EUR", 169 | "__v": 0, 170 | "categories": [ 171 | "Watches", 172 | "Sports Watches" 173 | ] 174 | } 175 | } 176 | }, 177 | "404": { 178 | "description": "404 Not Found" 179 | }, 180 | "500": { 181 | "description": "500 Internal Server Error" 182 | } 183 | } 184 | }, 185 | "post": { 186 | "404": { 187 | "description": "404 Not Found" 188 | }, 189 | "500": { 190 | "description": "500 Internal Server Error" 191 | }, 192 | "operationId": "postItemV2", 193 | "summary": "Creates new or updates existing item", 194 | "produces": [ 195 | "application/json" 196 | ], 197 | "responses": { 198 | "200": { 199 | "itemId": 19, 200 | "itemName": "Sports Watch 19", 201 | "price": 100, 202 | "currency": "USD", 203 | "__v": 0, 204 | "categories": [ 205 | "Watches", 206 | "Sports Watches" 207 | ] 208 | }, 209 | "201": { 210 | "itemId": 19, 211 | "itemName": "Sports Watch 19", 212 | "price": 100, 213 | "currency": "USD", 214 | "__v": 0, 215 | "categories": [ 216 | "Watches", 217 | "Sports Watches" 218 | ] 219 | }, 220 | "500": "text/html" 221 | } 222 | }, 223 | "put": { 224 | "404": { 225 | "description": "404 Not Found" 226 | }, 227 | "500": { 228 | "description": "500 Internal Server Error" 229 | }, 230 | "operationId": "putItemV2", 231 | "summary": "Creates new or updates existing item", 232 | "produces": [ 233 | "application/json" 234 | ], 235 | "responses": { 236 | "200": { 237 | "itemId": 19, 238 | "itemName": "Sports Watch 19", 239 | "price": 100, 240 | "currency": "USD", 241 | "__v": 0, 242 | "categories": [ 243 | "Watches", 244 | "Sports Watches" 245 | ] 246 | }, 247 | "201": { 248 | "itemId": 19, 249 | "itemName": "Sports Watch 19", 250 | "price": 100, 251 | "currency": "USD", 252 | "__v": 0, 253 | "categories": [ 254 | "Watches", 255 | "Sports Watches" 256 | ] 257 | }, 258 | "500": "text/html" 259 | } 260 | }, 261 | "delete": { 262 | "404": { 263 | "description": "404 Not Found" 264 | }, 265 | "500": { 266 | "description": "500 Internal Server Error" 267 | }, 268 | "operationId": "deleteItemV2", 269 | "summary": "Deletes an existing item", 270 | "produces": [ 271 | "application/json" 272 | ], 273 | "responses": { 274 | "200": { 275 | "deleted": true 276 | }, 277 | "500": "text/html" 278 | } 279 | } 280 | }, 281 | "/catalog/v1/item/{id}": { 282 | "get": { 283 | "operationId": "getItemV1", 284 | "summary": "Get an existing item", 285 | "produces": [ 286 | "application/json" 287 | ], 288 | "responses": { 289 | "200": { 290 | "description": "200 OK", 291 | "examples": { 292 | "application/json": { 293 | "_id": "5a4c004b0eed73835833cc9a", 294 | "itemId": "1", 295 | "itemName": "Sports Watch", 296 | "price": 100, 297 | "currency": "EUR", 298 | "__v": 0, 299 | "categories": [ 300 | "Watches", 301 | "Sports Watches" 302 | ] 303 | } 304 | } 305 | }, 306 | "404": { 307 | "description": "404 Not Found" 308 | }, 309 | "500": { 310 | "description": "500 Internal Server Error" 311 | } 312 | } 313 | }, 314 | "post": { 315 | "404": { 316 | "description": "404 Not Found" 317 | }, 318 | "500": { 319 | "description": "500 Internal Server Error" 320 | }, 321 | "operationId": "postItemV1", 322 | "summary": "Creates new or updates existing item", 323 | "produces": [ 324 | "application/json" 325 | ], 326 | "responses": { 327 | "200": { 328 | "itemId": 19, 329 | "itemName": "Sports Watch 19", 330 | "price": 100, 331 | "currency": "USD", 332 | "__v": 0, 333 | "categories": [ 334 | "Watches", 335 | "Sports Watches" 336 | ] 337 | }, 338 | "201": { 339 | "itemId": 19, 340 | "itemName": "Sports Watch 19", 341 | "price": 100, 342 | "currency": "USD", 343 | "__v": 0, 344 | "categories": [ 345 | "Watches", 346 | "Sports Watches" 347 | ] 348 | }, 349 | "500": "text/html" 350 | } 351 | }, 352 | "put": { 353 | "404": { 354 | "description": "404 Not Found" 355 | }, 356 | "500": { 357 | "description": "500 Internal Server Error" 358 | }, 359 | "operationId": "putItemV1", 360 | "summary": "Creates new or updates existing item", 361 | "produces": [ 362 | "application/json" 363 | ], 364 | "responses": { 365 | "200": { 366 | "itemId": 19, 367 | "itemName": "Sports Watch 19", 368 | "price": 100, 369 | "currency": "USD", 370 | "__v": 0, 371 | "categories": [ 372 | "Watches", 373 | "Sports Watches" 374 | ] 375 | }, 376 | "201": { 377 | "itemId": 19, 378 | "itemName": "Sports Watch 19", 379 | "price": 100, 380 | "currency": "USD", 381 | "__v": 0, 382 | "categories": [ 383 | "Watches", 384 | "Sports Watches" 385 | ] 386 | }, 387 | "500": "text/html" 388 | } 389 | }, 390 | "delete": { 391 | "404": { 392 | "description": "404 Not Found" 393 | }, 394 | "500": { 395 | "description": "500 Internal Server Error" 396 | }, 397 | "operationId": "deleteItemV1", 398 | "summary": "Deletes an existing item", 399 | "produces": [ 400 | "application/json" 401 | ], 402 | "responses": { 403 | "200": { 404 | "deleted": true 405 | }, 406 | "500": "text/html" 407 | } 408 | } 409 | }, 410 | "/catalog/v2/item/{id}": { 411 | "get": { 412 | "operationId": "getItemV2", 413 | "summary": "Get an existing item", 414 | "produces": [ 415 | "application/json" 416 | ], 417 | "responses": { 418 | "200": { 419 | "description": "200 OK", 420 | "examples": { 421 | "application/json": 422 | { 423 | "_id": "5a4c004b0eed73835833cc9a", 424 | "itemId": "1", 425 | "itemName": "Sports Watch", 426 | "price": 100, 427 | "currency": "EUR", 428 | "__v": 0, 429 | "categories": [ 430 | "Watches", 431 | "Sports Watches" 432 | ] 433 | } 434 | } 435 | }, 436 | "404": { 437 | "description": "404 Not Found" 438 | }, 439 | "500": { 440 | "description": "500 Internal Server Error" 441 | } 442 | } 443 | }, 444 | "post": { 445 | "operationId": "postItemImageV2", 446 | "summary": "Provide image for existing item", 447 | "produces": [ 448 | "image/jpeg" 449 | ], 450 | "responses": { 451 | "200": { 452 | "404": { 453 | "description": "404 Not Found" 454 | }, 455 | "500": { 456 | "description": "500 Internal Server Error" 457 | }, 458 | "description": "200 OK", 459 | "examples": { 460 | "image/jpeg": "image" 461 | } 462 | } 463 | } 464 | }, 465 | "put": { 466 | "404": { 467 | "description": "404 Not Found" 468 | }, 469 | "500": { 470 | "description": "500 Internal Server Error" 471 | }, 472 | "operationId": "putItemV2", 473 | "summary": "Creates new or updates existing item", 474 | "produces": [ 475 | "application/json" 476 | ], 477 | "responses": { 478 | "200": { 479 | "itemId": 19, 480 | "itemName": "Sports Watch 19", 481 | "price": 100, 482 | "currency": "USD", 483 | "__v": 0, 484 | "categories": [ 485 | "Watches", 486 | "Sports Watches" 487 | ] 488 | }, 489 | "201": { 490 | "itemId": 19, 491 | "itemName": "Sports Watch 19", 492 | "price": 100, 493 | "currency": "USD", 494 | "__v": 0, 495 | "categories": [ 496 | "Watches", 497 | "Sports Watches" 498 | ] 499 | }, 500 | "500": "text/html" 501 | } 502 | }, 503 | "delete": { 504 | "404": { 505 | "description": "404 Not Found" 506 | }, 507 | "500": { 508 | "description": "500 Internal Server Error" 509 | }, 510 | "operationId": "deleteItemV2", 511 | "summary": "Deletes an existing item", 512 | "produces": [ 513 | "application/json" 514 | ], 515 | "responses": { 516 | "200": { 517 | "deleted": true 518 | }, 519 | "500": "text/html" 520 | } 521 | } 522 | }, 523 | "/catalog/v2/item/{id}/image": { 524 | "get": { 525 | "404": { 526 | "description": "404 Not Found" 527 | }, 528 | "500": { 529 | "description": "500 Internal Server Error" 530 | }, 531 | "operationId": "getItemImageV2", 532 | "summary": "Get an image bound to an existing item", 533 | "produces": [ 534 | "application/json" 535 | ], 536 | "responses": { 537 | "200": "application/jpeg" 538 | } 539 | }, 540 | "post": { 541 | "404": { 542 | "description": "404 Not Found" 543 | }, 544 | "500": { 545 | "description": "500 Internal Server Error" 546 | }, 547 | "operationId": "postItemImageV2", 548 | "summary": "Creates or updates an image bound to an existing item", 549 | "produces": [ 550 | "application/json" 551 | ], 552 | "responses": { 553 | "200": "application/jpeg" 554 | } 555 | }, 556 | "put": { 557 | "404": { 558 | "description": "404 Not Found" 559 | }, 560 | "500": { 561 | "description": "500 Internal Server Error" 562 | }, 563 | "operationId": "putItemV2", 564 | "summary": "Creates or updates an image bound to an existing item", 565 | "produces": [ 566 | "application/json" 567 | ], 568 | "responses": { 569 | "200": "application/jpeg" 570 | } 571 | }, 572 | "delete": { 573 | "404": { 574 | "description": "404 Not Found" 575 | }, 576 | "500": { 577 | "description": "500 Internal Server Error" 578 | }, 579 | "operationId": "deleteItemV2", 580 | "summary": "Creates or updates an image bound to an existing item", 581 | "produces": [ 582 | "application/json" 583 | ], 584 | "responses": { 585 | "200": "application/jpeg" 586 | } 587 | } 588 | }, 589 | "/catalog/item/": { 590 | "post": { 591 | "404": { 592 | "description": "404 Not Found" 593 | }, 594 | "500": { 595 | "description": "500 Internal Server Error" 596 | }, 597 | "operationId": "postItemV21", 598 | "summary": "Creates new or updates existing item", 599 | "produces": [ 600 | "application/json" 601 | ], 602 | "responses": { 603 | "200": { 604 | "itemId": 19, 605 | "itemName": "Sports Watch 19", 606 | "price": 100, 607 | "currency": "USD", 608 | "__v": 0, 609 | "categories": [ 610 | "Watches", 611 | "Sports Watches" 612 | ] 613 | }, 614 | "201": { 615 | "itemId": 19, 616 | "itemName": "Sports Watch 19", 617 | "price": 100, 618 | "currency": "USD", 619 | "__v": 0, 620 | "categories": [ 621 | "Watches", 622 | "Sports Watches" 623 | ] 624 | }, 625 | "500": "text/html" 626 | } 627 | } 628 | }, 629 | "/catalog/v1/item/": { 630 | "post": { 631 | "operationId": "postItemV1", 632 | "summary": "Create new or update existing item", 633 | "produces": [ 634 | "image/jpeg" 635 | ], 636 | "responses": { 637 | "200": { 638 | "description": "200 OK", 639 | "examples": { 640 | "application/json": 641 | { 642 | "_id": "5a4c004b0eed73835833cc9a", 643 | "itemId": "1", 644 | "itemName": "Sports Watch", 645 | "price": 100, 646 | "currency": "EUR", 647 | "__v": 0, 648 | "categories": [ 649 | "Watches", 650 | "Sports Watches" 651 | ] 652 | } 653 | } 654 | }, 655 | "404": { 656 | "description": "404 Not Found" 657 | }, 658 | "500": { 659 | "description": "500 Internal Server Error" 660 | } 661 | } 662 | } 663 | }, 664 | "/catalog/v2/item/": { 665 | "post": { 666 | "operationId": "postItemV2", 667 | "summary": "Create new or update existing item", 668 | "produces": [ 669 | "image/jpeg" 670 | ], 671 | "responses": { 672 | "200": { 673 | "description": "200 OK", 674 | "examples": { 675 | "application/json": 676 | { 677 | "_id": "5a4c004b0eed73835833cc9a", 678 | "itemId": "1", 679 | "itemName": "Sports Watch", 680 | "price": 100, 681 | "currency": "EUR", 682 | "__v": 0, 683 | "categories": [ 684 | "Watches", 685 | "Sports Watches" 686 | ] 687 | } 688 | } 689 | }, 690 | "404": { 691 | "description": "404 Not Found" 692 | }, 693 | "500": { 694 | "description": "500 Internal Server Error" 695 | } 696 | } 697 | } 698 | } 699 | }, 700 | "consumes": [ 701 | "application/json" 702 | ] 703 | } 704 | -------------------------------------------------------------------------------- /Chapter09/model/item.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var mongoosePaginate = require('mongoose-paginate'); 3 | var Schema = mongoose.Schema; 4 | 5 | mongoose.connect('mongodb://localhost/catalog'); 6 | 7 | var itemSchema = new Schema ({ 8 | "itemId" : {type: String, index: {unique: true}}, 9 | "itemName": String, 10 | "price": Number, 11 | "currency" : String, 12 | "categories": [String] 13 | }); 14 | itemSchema.plugin(mongoosePaginate); 15 | var CatalogItem = mongoose.model('Item', itemSchema); 16 | 17 | module.exports = {CatalogItem : CatalogItem, connection : mongoose.connection}; 18 | -------------------------------------------------------------------------------- /Chapter09/modules/catalogV1.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (itemId, response) { 26 | CatalogItem.findOne({itemId: itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | if (response != null){ 41 | response.setHeader('Content-Type', 'application/json'); 42 | response.send(result); 43 | } 44 | console.log(result); 45 | } 46 | }); 47 | } 48 | 49 | exports.findItemsByCategory = function (category, response) { 50 | CatalogItem.find({categories: category}, function(error, result) { 51 | if (error) { 52 | console.error(error); 53 | response.writeHead(500, contentTypePlainText); 54 | return; 55 | } else { 56 | if (!result) { 57 | if (response != null) { 58 | response.writeHead(404, contentTypePlainText); 59 | response.end('Not Found'); 60 | } 61 | return; 62 | } 63 | 64 | if (response != null){ 65 | response.setHeader('Content-Type', 'application/json'); 66 | response.send(result); 67 | } 68 | console.log(result); 69 | } 70 | }); 71 | } 72 | 73 | exports.saveItem = function(request, response) 74 | { 75 | var item = toItem(request.body); 76 | item.save((error) => { 77 | if (!error) { 78 | item.save(); 79 | response.setHeader('Location', request.get('host') + '/item/' + item.itemId); 80 | response.writeHead(201, contentTypeJson); 81 | response.end(JSON.stringify(request.body)); 82 | } else { 83 | console.log(error); 84 | CatalogItem.findOne({itemId : item.itemId }, 85 | (error, result) => { 86 | console.log('Check if such an item exists'); 87 | if (error) { 88 | console.log(error); 89 | response.writeHead(500, contentTypePlainText); 90 | response.end('Internal Server Error'); 91 | } else { 92 | if (!result) { 93 | console.log('Item does not exist. Creating a new one'); 94 | item.save(); 95 | response.writeHead(201, contentTypeJson); 96 | response.end(JSON.stringify(request.body)); 97 | } else { 98 | console.log('Updating existing item'); 99 | result.itemId = item.itemId; 100 | result.itemName = item.itemName; 101 | result.price = item.price; 102 | result.currency = item.currency; 103 | result.categories = item.categories; 104 | result.save(); 105 | response.json(JSON.stringify(result)); 106 | } 107 | } 108 | }); 109 | } 110 | }); 111 | }; 112 | 113 | exports.remove = function (request, response) { 114 | console.log('Deleting item with id: ' + request.body.itemId); 115 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 116 | if (error) { 117 | console.log(error); 118 | if (response != null) { 119 | response.writeHead(500, contentTypePlainText); 120 | response.end('Internal server error'); 121 | } 122 | return; 123 | } else { 124 | if (!data) { 125 | console.log('Item not found'); 126 | if (response != null) { 127 | response.writeHead(404, contentTypePlainText); 128 | response.end('Not Found'); 129 | } 130 | return; 131 | } else { 132 | data.remove(function(error){ 133 | if (!error) { 134 | data.remove(); 135 | response.json({'Status': 'Successfully deleted'}); 136 | } 137 | else { 138 | console.log(error); 139 | response.writeHead(500, contentTypePlainText); 140 | response.end('Internal Server Error'); 141 | } 142 | }); 143 | } 144 | } 145 | }); 146 | } 147 | 148 | function toItem(body) { 149 | return new CatalogItem({ 150 | itemId: body.itemId, 151 | itemName: body.itemName, 152 | price: body.price, 153 | currency: body.currency, 154 | categories: body.categories 155 | }); 156 | } 157 | -------------------------------------------------------------------------------- /Chapter09/modules/catalogV2.js.txt: -------------------------------------------------------------------------------- 1 | const model = require('../model/item.js'); 2 | const CatalogItem = model.CatalogItem; 3 | const contentTypeJson = { 4 | 'Content-Type' : 'application/json' 5 | }; 6 | const contentTypePlainText = { 7 | 'Content-Type' : 'text/plain' 8 | }; 9 | 10 | exports.findAllItems = function (response) { 11 | CatalogItem.find({}, (error, result) => { 12 | if (error) { 13 | console.error(error); 14 | return null; 15 | } 16 | if (result != null) { 17 | response.json(result); 18 | } else { 19 | response.json({}); 20 | } 21 | }); 22 | }; 23 | 24 | 25 | exports.findItemById = function (gfs, request, response) { 26 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, result) { 27 | if (error) { 28 | console.error(error); 29 | response.writeHead(500, contentTypePlainText); 30 | return; 31 | } else { 32 | if (!result) { 33 | if (response != null) { 34 | response.writeHead(404, contentTypePlainText); 35 | response.end('Not Found'); 36 | } 37 | return; 38 | } 39 | 40 | var options = { 41 | filename : result.itemId, 42 | }; 43 | gfs.exist(options, function(error, found) { 44 | if (found) { 45 | response.setHeader('Content-Type', 'application/json'); 46 | var imageUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + '/image'; 47 | response.setHeader('Image-Url', imageUrl); 48 | response.send(result); 49 | } else { 50 | response.json(result); 51 | } 52 | }); 53 | } 54 | }); 55 | } 56 | 57 | exports.findItemsByAttribute = function (key, value, response) { 58 | var filter = {}; 59 | filter[key] = value; 60 | CatalogItem.find(filter, function(error, result) { 61 | if (error) { 62 | console.error(error); 63 | response.writeHead(500, contentTypePlainText); 64 | response.end('Internal server error'); 65 | return; 66 | } else { 67 | if (!result) { 68 | if (response != null) { 69 | response.writeHead(200, contentTypeJson); 70 | response.end({}); 71 | } 72 | return; 73 | } 74 | if (response != null){ 75 | response.setHeader('Content-Type', 76 | 'application/json'); 77 | response.send(result); 78 | } 79 | } 80 | }); 81 | } 82 | 83 | exports.findItemsByCategory = function (category, response) { 84 | CatalogItem.find({categories: category}, function(error, result) { 85 | if (error) { 86 | console.error(error); 87 | response.writeHead(500, contentTypePlainText); 88 | return; 89 | } else { 90 | if (!result) { 91 | if (response != null) { 92 | response.writeHead(404, contentTypePlainText); 93 | response.end('Not Found'); 94 | } 95 | return; 96 | } 97 | 98 | if (response != null){ 99 | response.setHeader('Content-Type', 'application/json'); 100 | response.send(result); 101 | } 102 | console.log(result); 103 | } 104 | }); 105 | } 106 | 107 | exports.saveItem = function(request, response) { 108 | var item = toItem(request.body); 109 | console.log(item); 110 | item.save((error) => { 111 | if (!error) { 112 | item.save(); 113 | var locationUrl; 114 | if (!request.path.endsWith('/')) { 115 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + '/items/' + item.itemId; 116 | } else { 117 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + 'item/' + item.itemId; 118 | } 119 | response.setHeader('Location', locationUrl); 120 | response.writeHead(201, contentTypeJson); 121 | response.end(JSON.stringify(request.body)); 122 | } else { 123 | console.log(error); 124 | CatalogItem.findOne({itemId : item.itemId }, 125 | (error, result) => { 126 | console.log('Check if such an item exists'); 127 | if (error) { 128 | console.log(error); 129 | response.writeHead(500, contentTypePlainText); 130 | response.end('Internal Server Error'); 131 | } else { 132 | if (!result) { 133 | console.log('Item does not exist. Creating a new one'); 134 | item.save(); 135 | if (!request.path.endsWith('/')) { 136 | locationUrl = request.protocol + '://' + request.get('host') + request.baseUrl+ request.path + '/' + item.itemId; 137 | } else { 138 | locationUrl = itemImageUrl = request.protocol + '://' + request.get('host') + request.baseUrl + request.path + item.itemId; 139 | } 140 | response.setHeader('Location', locationUrl); 141 | response.end(JSON.stringify(request.body)); 142 | } else { 143 | console.log('Updating existing item'); 144 | result.itemId = item.itemId; 145 | result.itemName = item.itemName; 146 | result.price = item.price; 147 | result.currency = item.currency; 148 | result.categories = item.categories; 149 | result.save(); 150 | response.json(JSON.stringify(result)); 151 | } 152 | } 153 | }); 154 | } 155 | }); 156 | } 157 | 158 | 159 | 160 | 161 | exports.remove = function (request, response) { 162 | console.log('Deleting item with id: ' + request.body.itemId); 163 | CatalogItem.findOne({itemId: request.params.itemId}, function(error, data) { 164 | if (error) { 165 | console.log(error); 166 | if (response != null) { 167 | response.writeHead(500, contentTypePlainText); 168 | response.end('Internal server error'); 169 | } 170 | return; 171 | } else { 172 | if (!data) { 173 | console.log('Item not found'); 174 | if (response != null) { 175 | response.writeHead(404, contentTypePlainText); 176 | response.end('Not Found'); 177 | } 178 | return; 179 | } else { 180 | data.remove(function(error){ 181 | if (!error) { 182 | data.remove(); 183 | response.json({'Status': 'Successfully deleted'}); 184 | } 185 | else { 186 | console.log(error); 187 | response.writeHead(500, contentTypePlainText); 188 | response.end('Internal Server Error'); 189 | } 190 | }); 191 | } 192 | } 193 | }); 194 | } 195 | 196 | exports.paginate = function(model, request, response) { 197 | var pageSize = request.query.limit; 198 | var page = request.query.page; 199 | if (pageSize === undefined) { 200 | pageSize = 100; 201 | } 202 | if (page === undefined) { 203 | page = 1; 204 | } 205 | 206 | model.paginate({}, {page:page, limit:pageSize}, 207 | function (error, result){ 208 | if(error) { 209 | console.log(error); 210 | response.writeHead('500', 211 | {'Content-Type' : 'text/plain'}); 212 | response.end('Internal Server Error'); 213 | } 214 | else { 215 | response.json(result); 216 | } 217 | }); 218 | } 219 | 220 | exports.deleteImage = function(gfs, mongodb, itemId, response) { 221 | console.log('Deleting image for itemId:' + itemId); 222 | 223 | var options = { 224 | filename : itemId, 225 | }; 226 | 227 | var chunks = mongodb.collection('fs.files.chunks'); 228 | chunks.remove(options, function (error, image) { 229 | if (error) { 230 | console.log(error); 231 | response.send('500', 'Internal Server Error'); 232 | return; 233 | } else { 234 | console.log('Successfully deleted image for primary contact number: ' + itemId); 235 | } 236 | }); 237 | 238 | 239 | var files = mongodb.collection('fs.files'); 240 | 241 | files.remove(options, function (error, image) { 242 | if (error) { 243 | console.log(error); 244 | response.send('500', 'Internal Server Error'); 245 | return; 246 | } 247 | 248 | if (image === null) { 249 | response.send('404', 'Not found'); 250 | return; 251 | } 252 | else { 253 | console.log('Successfully deleted image for primary contact number: ' + itemId); 254 | response.json({'deleted': true}); 255 | } 256 | }); 257 | } 258 | 259 | 260 | exports.getImage = function(gfs, request, response) { 261 | readImage(gfs, request, response); 262 | }; 263 | 264 | exports.saveImage = function(gfs, request, response) { 265 | 266 | var writeStream = gfs.createWriteStream({ 267 | filename : request.params.itemId, 268 | mode : 'w' 269 | }); 270 | 271 | writeStream.on('error', function(error) { 272 | response.send('500', 'Internal Server Error'); 273 | console.log(error); 274 | return; 275 | }) 276 | 277 | writeStream.on('close', function() { 278 | readImage(gfs, request, response); 279 | }); 280 | 281 | request.pipe(writeStream); 282 | } 283 | 284 | function readImage(gfs, request, response) { 285 | 286 | var imageStream = gfs.createReadStream({ 287 | filename : request.params.itemId, 288 | mode : 'r' 289 | }); 290 | 291 | imageStream.on('error', function(error) { 292 | console.log(error); 293 | response.send('404', 'Not found'); 294 | return; 295 | }); 296 | 297 | var itemImageUrl = request.protocol + '://' + request.get('host') + request.baseUrl+ request.path; 298 | var itemUrl = itemImageUrl.substring(0, itemImageUrl.indexOf('/image')); 299 | response.setHeader('Content-Type', 'image/jpeg'); 300 | response.setHeader('Item-Url', itemUrl); 301 | 302 | 303 | imageStream.pipe(response); 304 | } 305 | 306 | 307 | function toItem(body) { 308 | return new CatalogItem({ 309 | itemId: body.itemId, 310 | itemName: body.itemName, 311 | price: body.price, 312 | currency: body.currency, 313 | categories: body.categories 314 | }); 315 | } 316 | -------------------------------------------------------------------------------- /Chapter09/static/catalog.wadl.txt: -------------------------------------------------------------------------------- 1 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | itemId 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | -------------------------------------------------------------------------------- /Chapter09/test/model/model-test.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var should = require('should'); 3 | var prepare = require('./prepare'); 4 | 5 | 6 | 7 | const model = require('../model/item.js'); 8 | const CatalogItem = model.CatalogItem; 9 | 10 | mongoose.createConnection('mongodb://localhost/catalog'); 11 | 12 | 13 | describe('CatalogItem: models', function () { 14 | describe('#create()', function () { 15 | it('Should create a new CatalogItem', function (done) { 16 | 17 | var item = { 18 | "itemId": "1", 19 | "itemName": "Sports Watch", 20 | "price": 100, 21 | "currency": "EUR", 22 | "categories": [ 23 | "Watches", 24 | "Sports Watches" 25 | ] 26 | 27 | }; 28 | 29 | CatalogItem.create(item, function (err, createdItem) { 30 | // Check that no error occured 31 | should.not.exist(err); 32 | // Assert that the returned item has is what we expect 33 | 34 | createdItem.itemId.should.equal('1'); 35 | createdItem.itemName.should.equal('Sports Watch'); 36 | createdItem.price.should.equal(100); 37 | createdItem.currency.should.equal('EUR'); 38 | createdItem.categories[0].should.equal('Watches'); 39 | createdItem.categories[1].should.equal('Sports Watches'); 40 | //Notify mocha that the test has completed 41 | done(); 42 | }); 43 | }); 44 | }); 45 | 46 | 47 | }); 48 | -------------------------------------------------------------------------------- /Chapter09/test/model/prepare.js.txt: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | 3 | beforeEach(function (done) { 4 | function clearDatabase() { 5 | for (var i in mongoose.connection.collections) { 6 | mongoose.connection.collections[i].remove(function() {}); 7 | } 8 | return done(); 9 | } 10 | 11 | if (mongoose.connection.readyState === 0) { 12 | mongoose.connect(config.db.test, function (err) { 13 | if (err) { 14 | throw err; 15 | } 16 | return clearDatabase(); 17 | }); 18 | } else { 19 | return clearDatabase(); 20 | } 21 | }); 22 | 23 | afterEach(function (done) { 24 | mongoose.disconnect(); 25 | return done(); 26 | }); 27 | -------------------------------------------------------------------------------- /Chapter09/test/routes/routes-test.js.txt: -------------------------------------------------------------------------------- 1 | var expressApp = require('../../app'); 2 | var chai = require('chai'); 3 | var chaiHttp = require('chai-http'); 4 | var mongoose = require('mongoose'); 5 | var should = chai.should(); 6 | 7 | 8 | mongoose.createConnection('mongodb://localhost/catalog-test'); 9 | 10 | chai.use(chaiHttp); 11 | 12 | 13 | describe('/get', function() { 14 | it('get test', function(done) { 15 | chai.request(expressApp) 16 | .get('/catalog/v2') 17 | .end(function(error, response) { 18 | should.equal(200 , response.status); 19 | done(); 20 | }); 21 | }); 22 | }); 23 | 24 | describe('/post', function() { 25 | it('post test', function(done) { 26 | var item ={ 27 | "itemId":19, 28 | "itemName": "Sports Watch 10", 29 | "price": 100, 30 | "currency": "USD", 31 | "__v": 0, 32 | "categories": [ 33 | "Watches", 34 | "Sports Watches" 35 | ] 36 | }; 37 | chai.request(expressApp) 38 | .post('/catalog/v2') 39 | .send(item ) 40 | .end(function(err, response){ 41 | should.equal(201, response.status) 42 | done(); 43 | }); 44 | }); 45 | }); 46 | 47 | describe('/delete', function() { 48 | it('delete test', function(done) { 49 | var item ={ 50 | "itemId":19, 51 | "itemName": "Sports Watch 10", 52 | "price": 100, 53 | "currency": "USD", 54 | "__v": 0, 55 | "categories": [ 56 | "Watches", 57 | "Sports Watches" 58 | ] 59 | }; 60 | chai.request(expressApp) 61 | .delete('/catalog/v2/item/19') 62 | .send(item ) 63 | .end(function(err, response){ 64 | should.equal(200, response.status) 65 | done(); 66 | }); 67 | }); 68 | }); 69 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Packt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | # RESTful Web API Design with Node.js 10 - Third Edition 5 | This is the code repository for [RESTful Web API Design with Node.js 10 - Third Edition](https://www.packtpub.com/web-development/restful-web-api-design-nodejs-10-third-edition?utm_source=github&utm_medium=repository&utm_campaign=9781788623322), published by [Packt](https://www.packtpub.com/?utm_source=github). It contains all the supporting project files necessary to work through the book from start to finish. 6 | ## About the Book 7 | This book targets developers who want to enrich their development skills by learning how 8 | to develop scalable, server-side, RESTful applications based on the Node.js platform. You 9 | also need to be aware of HTTP communication concepts and should have a working 10 | knowledge of the JavaScript language. Keep in mind that this is not a book that will teach 11 | you how to program in JavaScript. Knowledge of REST will be an added advantage but is 12 | definitely not a necessity. 13 | ## Instructions and Navigation 14 | All of the code is organized into folders. Each folder starts with a number followed by the application name. For example, Chapter02. 15 | 16 | 17 | 18 | The code will look like the following: 19 | ``` 20 | router.get('/v1/item/:itemId', function(request, response, next) { 21 | console.log(request.url + ' : querying for ' + request.params.itemId); 22 | catalogV1.findItemById(request.params.itemId, response); 23 | }); 24 | router.get('/v1/:categoryId', function(request, response, next) { 25 | console.log(request.url + ' : querying for ' + 26 | request.params.categoryId); 27 | catalogV1.findItemsByCategory(request.params.categoryId, response); 28 | }); 29 | ``` 30 | 31 | ## Related Products 32 | * [Mastering Node.js - Second Edition](https://www.packtpub.com/web-development/mastering-nodejs-second-edition?utm_source=github&utm_medium=repository&utm_campaign=9781785888960) 33 | 34 | * [Learning Node.js Development](https://www.packtpub.com/web-development/learning-nodejs-development?utm_source=github&utm_medium=repository&utm_campaign=9781788395540) 35 | ### Download a free PDF 36 | 37 | If you have already purchased a print or Kindle version of this book, you can get a DRM-free PDF version at no cost.
Simply click on the link to claim your free PDF.
38 |

https://packt.link/free-ebook/9781788623322

--------------------------------------------------------------------------------