├── 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 |
50 |
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 |
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
--------------------------------------------------------------------------------