From d9fd3f687d8483832542bd87aa205388e276f482 Mon Sep 17 00:00:00 2001 From: Dan Stroot Date: Thu, 24 Jan 2013 20:04:08 -0800 Subject: [PATCH 1/3] Much more robust API 0) Returns proper HTML response codes 1) Much faster! (because it returns proper response codes browser doesn't wait!) 2) Can't crash the app by sending bad data (checks what you send it) 3) Makes sure the browser won't cache the data (problem solved) 4) If it can't update or delete an item you now will be notified properly --- routes/wines.js | 175 ++++++++++++++++++++++++++++++++++++++---------- 1 file changed, 139 insertions(+), 36 deletions(-) diff --git a/routes/wines.js b/routes/wines.js index 1626db8..469ae2d 100644 --- a/routes/wines.js +++ b/routes/wines.js @@ -19,74 +19,171 @@ db.open(function(err, db) { } }); +// GET /wines/:id exports.findById = function(req, res) { + + // At some point we may want to lock this api down! + // If this is on the Internet someone could easily + // steal, modify or delete your data! Need something like + // this on our api (assuming we have people authenticate): + // if (req.session.userAuthenticated) { + + // We don't want the browser to cache the results + res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); + var id = req.params.id; - console.log('Retrieving wine: ' + id); - db.collection('wines', function(err, collection) { - collection.findOne({'_id':new BSON.ObjectID(id)}, function(err, item) { - res.send(item); + // need to validate the id is in a valid format (24 hex) + var regexObj = /^[A-Fa-f0-9]{24}$/; + if (regexObj.test(id)) { + db.collection('wines', function(err, collection) { + // We will search for all docs with that ID + collection.find({'_id':new BSON.ObjectID(id)}).toArray(function(err, items) { + if (err) { + //console.log('Error getting wine by ID: ' + id + ' Error: ' + err); + res.json(500, {'error':'An error has occurred!'}); + } else if (items.length != 1) { + //console.log('Wine not found. ID: ' + id); + res.json(404, {'message':'item not found'}); + } else { + //console.log('Success getting wine by ID: ' + id); + res.json(200, items[0]); + } + }); }); - }); + } else { + //console.log('Error invalid ID format: ' + id); + res.json(500, {'error':'invalid ID format'}); + } }; +// GET /wines exports.findAll = function(req, res) { + // We don't want the browser to cache the results + res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); + db.collection('wines', function(err, collection) { collection.find().toArray(function(err, items) { - res.send(items); + // We don't want the browser to cache the results + res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); + if (err) { + //console.log('Error getting all wines: ' + err); + res.json(500, {'error':'An error has occurred!'}); + } else { + //console.log('Success getting all wines.'); + res.json(200, items); + } }); }); }; +// POST /wines exports.addWine = function(req, res) { + + // We don't want the browser to cache the results + res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); + var wine = req.body; - console.log('Adding wine: ' + JSON.stringify(wine)); db.collection('wines', function(err, collection) { collection.insert(wine, {safe:true}, function(err, result) { if (err) { - res.send({'error':'An error has occurred'}); + //console.log('Error adding wine: ' + err); + res.json(500, {'error':'An error has occurred'}); } else { - console.log('Success: ' + JSON.stringify(result[0])); - res.send(result[0]); + //console.log('Success adding wine: ' + JSON.stringify(result[0])); + res.json(201, result[0]); } }); }); } +// PUT /wines/:id exports.updateWine = function(req, res) { + + // We don't want the browser to cache the results + res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); + var id = req.params.id; var wine = req.body; - delete wine._id; - console.log('Updating wine: ' + id); - console.log(JSON.stringify(wine)); - db.collection('wines', function(err, collection) { - collection.update({'_id':new BSON.ObjectID(id)}, wine, {safe:true}, function(err, result) { - if (err) { - console.log('Error updating wine: ' + err); - res.send({'error':'An error has occurred'}); - } else { - console.log('' + result + ' document(s) updated'); - res.send(wine); - } + delete wine._id; // remove the ID field + + // need to validate the id is in a valid format (24 hex) + var regexObj = /^[A-Fa-f0-9]{24}$/; + if (regexObj.test(id)) { + db.collection('wines', function(err, collection) { + // Let's see if we find the item before we try to update it + collection.find({'_id':new BSON.ObjectID(id)}).toArray(function(err, items) { + if (err) { + // handle error (500) + //console.log('Error updating wine: ' + err); + res.send(500, {'error':'An error has occurred'}); + } else if (items.length != 1) { + // item doesn't exist (or we have bigger issues) + //console.log('Wine to update not found: ' + id); + res.send(404, {'message':'Wine to update not found: ' + id}); + } else { + // Update item + collection.update({'_id':new BSON.ObjectID(id)}, wine, {safe:true}, function(err, result) { + if (err) { + //console.log('Error updating wine. ID: ' + id + ' Error: ' + err); + res.json(500, {'error':'An error has occurred!'}); + } else { + //console.log('Success updating wine: ' + result + ' document(s) updated'); + res.json(200, wine); + } + }); + } + }); }); - }); + } else { + //console.log('Error invalid ID format: ' + id); + res.json(500, {'error':'invalid ID format'}); + } } +// DELETE /wines/:id exports.deleteWine = function(req, res) { + + // We don't want the browser to cache the results + res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); + var id = req.params.id; - console.log('Deleting wine: ' + id); - db.collection('wines', function(err, collection) { - collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err, result) { - if (err) { - res.send({'error':'An error has occurred - ' + err}); - } else { - console.log('' + result + ' document(s) deleted'); - res.send(req.body); - } + // need to validate the id is in a valid format (24 hex) + var regexObj = /^[A-Fa-f0-9]{24}$/; + + if (regexObj.test(id)) { + db.collection('wines', function(err, collection) { + // Let's see if we find the item before we try to delete it + collection.find({'_id':new BSON.ObjectID(id)}).toArray(function(err, items) { + if (err) { + // handle error (500) + console.log('Error deleting wine: ' + err); + res.send(500, {'error':'An error has occurred'}); + } else if (items.length != 1) { + // item doesn't exist (or we have bigger issues) + console.log('Cannot delete, wine not found: ' + id); + res.send(404, {'message':'Cannot delete, wine not found. ID: ' + id}); + } else { + // Remove item + collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err, result) { + if (err) { + //console.log('Error deleting wine. ID: ' + id + ' Error: ' + err); + res.json(500, {'error':'An error has occurred - ' + err}); + } else { + //console.log('Success deleting wine: ' + result + ' document(s) deleted'); + // HTTP 204 No Content: The server successfully processed the request, but is not returning any content + res.json(204); + } + }); + } + }); }); - }); + } else { + //console.log('Error invalid ID format: ' + id); + res.json(500, {'error':'invalid ID format'}); + } } -/*--------------------------------------------------------------------------------------------------------------------*/ +/*-----------------------------------------------------------------------------------------------*/ // Populate database with sample data -- Only used once: the first time the application is started. // You'd typically not find this code in a real-life app, since the database would already exist. var populateDB = function() { @@ -260,7 +357,7 @@ var populateDB = function() { grapes: "Zinfandel", country: "USA", region: "California", - description: "o yourself a favor and have a bottle (or two) of this fine zinfandel on hand for your next romantic outing. The only thing that can make this fine choice better is the company you share it with.", + description: "Do yourself a favor and have a bottle (or two) of this fine zinfandel on hand for your next romantic outing. The only thing that can make this fine choice better is the company you share it with.", picture: "fourvines.jpg" }, { @@ -310,7 +407,13 @@ var populateDB = function() { }]; db.collection('wines', function(err, collection) { - collection.insert(wines, {safe:true}, function(err, result) {}); + collection.insert(wines, {safe:true}, function(err, result) { + if (err) { + console.log('Error adding sample wines: ' + err); + } else { + console.log('Success adding sample wines'); + } + }); }); }; \ No newline at end of file From efffd874aa9a5b850e859967bf9756d6ed131cae Mon Sep 17 00:00:00 2001 From: Dan Stroot Date: Thu, 24 Jan 2013 20:24:10 -0800 Subject: [PATCH 2/3] More Robust API Christophe: LOVE your tutorials! I have learned quite a bit! I wanted to give back by improving your wines api a bit - 0) Returns proper HTML response codes 1) Much faster! (because it returns proper response codes browser doesn't wait...) 2) Can't crash the app by sending bad data (checks what you send it) 3) Makes sure the browser won't cache the data (problem solved) 4) If it can't update or delete an item you now will be notified properly 5) res.send changed to res.json --- routes/wines.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/routes/wines.js b/routes/wines.js index 469ae2d..9a6c53b 100644 --- a/routes/wines.js +++ b/routes/wines.js @@ -63,8 +63,6 @@ exports.findAll = function(req, res) { db.collection('wines', function(err, collection) { collection.find().toArray(function(err, items) { - // We don't want the browser to cache the results - res.header('Cache-Control', 'no-cache, private, no-store, must-revalidate, max-stale=0, post-check=0, pre-check=0'); if (err) { //console.log('Error getting all wines: ' + err); res.json(500, {'error':'An error has occurred!'}); From d1c8fb1fc00448734855739077775d7df2b3d122 Mon Sep 17 00:00:00 2001 From: Dan Stroot Date: Fri, 25 Jan 2013 07:00:59 -0800 Subject: [PATCH 3/3] Fixed dashboard so it doesn't count itself --- public/dashboard.html | 2 +- routes/wines.js | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/public/dashboard.html b/public/dashboard.html index 4c77919..992e278 100644 --- a/public/dashboard.html +++ b/public/dashboard.html @@ -106,7 +106,7 @@
active visitors
console.log('Socket connected'); socket.on('pageview', function (msg) { - $('#connections').html(msg.connections); + $('#connections').html(msg.connections - 1); // less one since we don't count our own dashboard connection if (msg.url) { if ($('#visits tr').length > 10) { $('#visits tr:last').remove(); diff --git a/routes/wines.js b/routes/wines.js index 9a6c53b..4dc4878 100644 --- a/routes/wines.js +++ b/routes/wines.js @@ -113,11 +113,11 @@ exports.updateWine = function(req, res) { if (err) { // handle error (500) //console.log('Error updating wine: ' + err); - res.send(500, {'error':'An error has occurred'}); + res.json(500, {'error':'An error has occurred'}); } else if (items.length != 1) { // item doesn't exist (or we have bigger issues) //console.log('Wine to update not found: ' + id); - res.send(404, {'message':'Wine to update not found: ' + id}); + res.json(404, {'message':'Wine to update not found: ' + id}); } else { // Update item collection.update({'_id':new BSON.ObjectID(id)}, wine, {safe:true}, function(err, result) { @@ -155,11 +155,11 @@ exports.deleteWine = function(req, res) { if (err) { // handle error (500) console.log('Error deleting wine: ' + err); - res.send(500, {'error':'An error has occurred'}); + res.json(500, {'error':'An error has occurred'}); } else if (items.length != 1) { // item doesn't exist (or we have bigger issues) console.log('Cannot delete, wine not found: ' + id); - res.send(404, {'message':'Cannot delete, wine not found. ID: ' + id}); + res.json(404, {'message':'Cannot delete, wine not found. ID: ' + id}); } else { // Remove item collection.remove({'_id':new BSON.ObjectID(id)}, {safe:true}, function(err, result) {