From 53ef68d87f67c7c05cea4e6cfa12ae5c4785861f Mon Sep 17 00:00:00 2001 From: Jason Dobry Date: Fri, 5 Feb 2016 13:03:44 -0800 Subject: [PATCH] Fixes #38 so errors will be logged properly. Cleaned up some code style and added some comments. --- 2-structured-data/app.js | 16 ++-- 2-structured-data/books/api.js | 96 ++++++++++++-------- 2-structured-data/books/crud.js | 127 ++++++++++++++++----------- 3-binary-data/app.js | 17 ++-- 3-binary-data/books/api.js | 96 ++++++++++++-------- 3-binary-data/books/crud.js | 131 ++++++++++++++++++---------- 4-auth/app.js | 20 ++--- 4-auth/books/api.js | 96 ++++++++++++-------- 4-auth/books/crud.js | 145 ++++++++++++++++++------------ 5-logging/app.js | 18 ++-- 5-logging/books/api.js | 96 ++++++++++++-------- 5-logging/books/crud.js | 150 ++++++++++++++++++++------------ 6-pubsub/app.js | 23 ++--- 6-pubsub/books/api.js | 96 ++++++++++++-------- 6-pubsub/books/crud.js | 150 ++++++++++++++++++++------------ 7-gce/app.js | 27 +++--- 7-gce/books/api.js | 96 ++++++++++++-------- 7-gce/books/crud.js | 150 ++++++++++++++++++++------------ 18 files changed, 957 insertions(+), 593 deletions(-) diff --git a/2-structured-data/app.js b/2-structured-data/app.js index dc8394b456..638ad8aabb 100644 --- a/2-structured-data/app.js +++ b/2-structured-data/app.js @@ -24,24 +24,28 @@ app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.set('trust proxy', true); - // Books var model = require('./books/model-' + config.dataBackend)(config); app.use('/books', require('./books/crud')(model)); app.use('/api/books', require('./books/api')(model)); - // Redirect root to /books -app.get('/', function(req, res) { +app.get('/', function (req, res) { res.redirect('/books'); }); +// Basic 404 handler +app.use(function (req, res) { + res.status(404).send('Not Found'); +}); // Basic error handler -app.use(function(err, req, res, next) { +app.use(function (err, req, res, next) { /* jshint unused:false */ - console.error(err.stack); - res.status(500).send('Something broke!'); + console.error(err); + // If our routes specified a specific response, then send that. Otherwise, + // send a generic message so as not to leak anything. + res.status(500).send(err.response || 'Something broke!'); }); if (module === require.main) { diff --git a/2-structured-data/books/api.js b/2-structured-data/books/api.js index 0d195c2391..0322929233 100644 --- a/2-structured-data/books/api.js +++ b/2-structured-data/books/api.js @@ -16,66 +16,88 @@ var express = require('express'); var bodyParser = require('body-parser'); - -module.exports = function(model) { +module.exports = function (model) { var router = express.Router(); + // Automatically parse request body as JSON router.use(bodyParser.json()); - - function handleRpcError(err, res) { - if (err.code === 404) { return res.status(404); } - res.status(500).json({ - message: err.message, - internalCode: err.code - }); - } - - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.json({ - items: entities, - nextPageToken: cursor - }); + /** + * GET /api/books + * + * Retrieve a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.json({ + items: entities, + nextPageToken: cursor }); + }); }); - - router.post('/', function insert(req, res) { - model.create(req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * POST /api/books + * + * Create a new book. + */ + router.post('/', function insert(req, res, next) { + model.create(req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.get('/:book(\\d+)', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /api/books/:id + * + * Retrieve a book. + */ + router.get('/:book(\\d+)', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.put('/:book(\\d+)', function update(req, res) { - model.update(req.params.book, req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * PUT /api/books/:id + * + * Update a book. + */ + router.put('/:book(\\d+)', function update(req, res, next) { + model.update(req.params.book, req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.delete('/:book(\\d+)', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * DELETE /api/books/:id + * + * Delete a book. + */ + router.delete('/:book(\\d+)', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.status(200).send('OK'); }); }); - return router; + /** + * Errors on "/api/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = { + message: err.message, + internalCode: err.code + }; + next(err); + }); + return router; }; diff --git a/2-structured-data/books/crud.js b/2-structured-data/books/crud.js index 644322343d..d55d32c969 100644 --- a/2-structured-data/books/crud.js +++ b/2-structured-data/books/crud.js @@ -16,64 +16,73 @@ var express = require('express'); var bodyParser = require('body-parser'); - -module.exports = function(model) { +module.exports = function (model) { var router = express.Router(); - router.use(bodyParser.urlencoded({extended: false})); - - - function handleRpcError(err, res) { - res.status(err.code || 500).send(err.message); - } + // Automatically parse request body as form data + router.use(bodyParser.urlencoded({ extended: false })); - - router.use(function(req, res, next){ + // Set Content-Type for all responses for these routes + router.use(function (req, res, next){ res.set('Content-Type', 'text/html'); next(); }); - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.render('books/list.jade', { - books: entities, - nextPageToken: cursor - }); - } - ); + /** + * GET /books/add + * + * Display a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.render('books/list.jade', { + books: entities, + nextPageToken: cursor + }); + }); }); - -// [START add_get] + /** + * GET /books/add + * + * Display a form for creating a book. + */ + // [START add_get] router.get('/add', function addForm(req, res) { res.render('books/form.jade', { book: {}, action: 'Add' }); }); -// [END add_get] - - -// [START add_post] - router.post('/add', function insert(req, res) { + // [END add_get] + + /** + * POST /books/add + * + * Create a book. + */ + // [START add_post] + router.post('/add', function insert(req, res, next) { var data = req.body; // Save the data to the database. - model.create(data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.create(data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); }); -// [END add_post] - - - router.get('/:book/edit', function editForm(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + // [END add_post] + + /** + * GET /books/:id/edit + * + * Display a book for editing. + */ + router.get('/:book/edit', function editForm(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/form.jade', { book: entity, action: 'Edit' @@ -81,35 +90,55 @@ module.exports = function(model) { }); }); - - router.post('/:book/edit', function update(req, res) { + /** + * POST /books/:id/edit + * + * Update a book. + */ + router.post('/:book/edit', function update(req, res, next) { var data = req.body; - model.update(req.params.book, data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.update(req.params.book, data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); }); - - router.get('/:book', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id + * + * Display a book. + */ + router.get('/:book', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/view.jade', { book: entity }); }); }); - - router.get('/:book/delete', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id/delete + * + * Delete a book. + */ + router.get('/:book/delete', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.redirect(req.baseUrl); }); }); + /** + * Errors on "/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = err.message; + next(err); + }); return router; - }; diff --git a/3-binary-data/app.js b/3-binary-data/app.js index 13a306adda..2a7c7a6e3d 100644 --- a/3-binary-data/app.js +++ b/3-binary-data/app.js @@ -24,7 +24,6 @@ app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.set('trust proxy', true); - // Setup modules and dependencies var images = require('./lib/images')(config.gcloud, config.cloudStorageBucket); var model = require('./books/model-' + config.dataBackend)(config); @@ -33,21 +32,25 @@ var model = require('./books/model-' + config.dataBackend)(config); app.use('/books', require('./books/crud')(model, images)); app.use('/api/books', require('./books/api')(model)); - // Redirect root to /books -app.get('/', function(req, res) { +app.get('/', function (req, res) { res.redirect('/books'); }); +// Basic 404 handler +app.use(function (req, res) { + res.status(404).send('Not Found'); +}); // Basic error handler -app.use(function(err, req, res, next) { +app.use(function (err, req, res, next) { /* jshint unused:false */ - console.error(err.stack); - res.status(500).send('Something broke!'); + console.error(err); + // If our routes specified a specific response, then send that. Otherwise, + // send a generic message so as not to leak anything. + res.status(500).send(err.response || 'Something broke!'); }); - if (module === require.main) { // Start the server var server = app.listen(config.port, function () { diff --git a/3-binary-data/books/api.js b/3-binary-data/books/api.js index 0d195c2391..0322929233 100644 --- a/3-binary-data/books/api.js +++ b/3-binary-data/books/api.js @@ -16,66 +16,88 @@ var express = require('express'); var bodyParser = require('body-parser'); - -module.exports = function(model) { +module.exports = function (model) { var router = express.Router(); + // Automatically parse request body as JSON router.use(bodyParser.json()); - - function handleRpcError(err, res) { - if (err.code === 404) { return res.status(404); } - res.status(500).json({ - message: err.message, - internalCode: err.code - }); - } - - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.json({ - items: entities, - nextPageToken: cursor - }); + /** + * GET /api/books + * + * Retrieve a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.json({ + items: entities, + nextPageToken: cursor }); + }); }); - - router.post('/', function insert(req, res) { - model.create(req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * POST /api/books + * + * Create a new book. + */ + router.post('/', function insert(req, res, next) { + model.create(req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.get('/:book(\\d+)', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /api/books/:id + * + * Retrieve a book. + */ + router.get('/:book(\\d+)', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.put('/:book(\\d+)', function update(req, res) { - model.update(req.params.book, req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * PUT /api/books/:id + * + * Update a book. + */ + router.put('/:book(\\d+)', function update(req, res, next) { + model.update(req.params.book, req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.delete('/:book(\\d+)', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * DELETE /api/books/:id + * + * Delete a book. + */ + router.delete('/:book(\\d+)', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.status(200).send('OK'); }); }); - return router; + /** + * Errors on "/api/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = { + message: err.message, + internalCode: err.code + }; + next(err); + }); + return router; }; diff --git a/3-binary-data/books/crud.js b/3-binary-data/books/crud.js index e51c6e6e8b..80dcec4084 100644 --- a/3-binary-data/books/crud.js +++ b/3-binary-data/books/crud.js @@ -15,36 +15,36 @@ var express = require('express'); - -module.exports = function(model, images) { +module.exports = function (model, images) { var router = express.Router(); - - function handleRpcError(err, res) { - res.status(err.code || 500).send(err.message); - } - - - router.use(function(req, res, next) { + // Set Content-Type for all responses for these routes + router.use(function (req, res, next) { res.set('Content-Type', 'text/html'); next(); }); - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.render('books/list.jade', { - books: entities, - nextPageToken: cursor - }); - } - ); + /** + * GET /books/add + * + * Display a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.render('books/list.jade', { + books: entities, + nextPageToken: cursor + }); + }); }); - + /** + * GET /books/add + * + * Display a form for creating a book. + */ router.get('/add', function addForm(req, res) { res.render('books/form.jade', { book: {}, @@ -52,10 +52,17 @@ module.exports = function(model, images) { }); }); - + /** + * POST /books/add + * + * Create a book. + */ // [START add] - router.post('/add', images.multer.single('image'), images.sendUploadToGCS, - function insert(req, res) { + router.post( + '/add', + images.multer.single('image'), + images.sendUploadToGCS, + function insert(req, res, next) { var data = req.body; // Was an image uploaded? If so, we'll use its public URL @@ -65,18 +72,22 @@ module.exports = function(model, images) { } // Save the data to the database. - model.create(data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.create(data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); + } + ); // [END add] - - - router.get('/:book/edit', function editForm(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id/edit + * + * Display a book for editing. + */ + router.get('/:book/edit', function editForm(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/form.jade', { book: entity, action: 'Edit' @@ -84,9 +95,16 @@ module.exports = function(model, images) { }); }); - - router.post('/:book/edit', images.multer.single('image'), - images.sendUploadToGCS, function update(req, res) { + /** + * POST /books/:id/edit + * + * Update a book. + */ + router.post( + '/:book/edit', + images.multer.single('image'), + images.sendUploadToGCS, + function update(req, res, next) { var data = req.body; // Was an image uploaded? If so, we'll use its public URL @@ -95,31 +113,48 @@ module.exports = function(model, images) { req.body.imageUrl = req.file.cloudStoragePublicUrl; } - model.update(req.params.book, data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.update(req.params.book, data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); - - - router.get('/:book', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + } + ); + + /** + * GET /books/:id + * + * Display a book. + */ + router.get('/:book', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/view.jade', { book: entity }); }); }); - - router.get('/:book/delete', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id/delete + * + * Delete a book. + */ + router.get('/:book/delete', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.redirect(req.baseUrl); }); }); + /** + * Errors on "/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = err.message; + next(err); + }); return router; - }; diff --git a/4-auth/app.js b/4-auth/app.js index 4d4f5883dd..30cff16662 100644 --- a/4-auth/app.js +++ b/4-auth/app.js @@ -25,7 +25,6 @@ app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.set('trust proxy', true); - // Configure the session and session storage. // MemoryStore isn't viable in a multi-server configuration, so we // use encrypted cookies. Redis or Memcache is a great option for @@ -37,36 +36,37 @@ app.use(session({ })); // [END session] - // OAuth2 var oauth2 = require('./lib/oauth2')(config.oauth2); app.use(oauth2.router); - // Setup modules and dependencies var images = require('./lib/images')(config.gcloud, config.cloudStorageBucket); var model = require('./books/model-' + config.dataBackend)(config); - // Books app.use('/books', require('./books/crud')(model, images, oauth2)); app.use('/api/books', require('./books/api')(model)); - // Redirect root to /books -app.get('/', function(req, res) { +app.get('/', function (req, res) { res.redirect('/books'); }); +// Basic 404 handler +app.use(function (req, res) { + res.status(404).send('Not Found'); +}); // Basic error handler -app.use(function(err, req, res, next) { +app.use(function (err, req, res, next) { /* jshint unused:false */ - console.error(err.stack); - res.status(500).send('Something broke!'); + console.error(err); + // If our routes specified a specific response, then send that. Otherwise, + // send a generic message so as not to leak anything. + res.status(500).send(err.response || 'Something broke!'); }); - if (module === require.main) { // Start the server var server = app.listen(config.port, function () { diff --git a/4-auth/books/api.js b/4-auth/books/api.js index 0d195c2391..0322929233 100644 --- a/4-auth/books/api.js +++ b/4-auth/books/api.js @@ -16,66 +16,88 @@ var express = require('express'); var bodyParser = require('body-parser'); - -module.exports = function(model) { +module.exports = function (model) { var router = express.Router(); + // Automatically parse request body as JSON router.use(bodyParser.json()); - - function handleRpcError(err, res) { - if (err.code === 404) { return res.status(404); } - res.status(500).json({ - message: err.message, - internalCode: err.code - }); - } - - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.json({ - items: entities, - nextPageToken: cursor - }); + /** + * GET /api/books + * + * Retrieve a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.json({ + items: entities, + nextPageToken: cursor }); + }); }); - - router.post('/', function insert(req, res) { - model.create(req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * POST /api/books + * + * Create a new book. + */ + router.post('/', function insert(req, res, next) { + model.create(req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.get('/:book(\\d+)', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /api/books/:id + * + * Retrieve a book. + */ + router.get('/:book(\\d+)', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.put('/:book(\\d+)', function update(req, res) { - model.update(req.params.book, req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * PUT /api/books/:id + * + * Update a book. + */ + router.put('/:book(\\d+)', function update(req, res, next) { + model.update(req.params.book, req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.delete('/:book(\\d+)', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * DELETE /api/books/:id + * + * Delete a book. + */ + router.delete('/:book(\\d+)', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.status(200).send('OK'); }); }); - return router; + /** + * Errors on "/api/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = { + message: err.message, + internalCode: err.code + }; + next(err); + }); + return router; }; diff --git a/4-auth/books/crud.js b/4-auth/books/crud.js index 9c93d99495..5f1961250c 100644 --- a/4-auth/books/crud.js +++ b/4-auth/books/crud.js @@ -15,49 +15,47 @@ var express = require('express'); - module.exports = function(model, images, oauth2) { var router = express.Router(); - // Use the oauth middleware to automatically get the user's profile // information and expose login/logout URLs to templates. router.use(oauth2.aware); router.use(oauth2.template); - - function handleRpcError(err, res) { - res.status(err.code || 500).send(err.message); - } - - - router.use(function(req, res, next) { + // Set Content-Type for all responses for these routes + router.use(function (req, res, next) { res.set('Content-Type', 'text/html'); next(); }); - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.render('books/list.jade', { - books: entities, - nextPageToken: cursor - }); - } - ); + /** + * GET /books/add + * + * Display a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.render('books/list.jade', { + books: entities, + nextPageToken: cursor + }); + }); }); - // [START mine] // Use the oauth2.required middleware to ensure that only logged-in users // can access this handler. - router.get('/mine', oauth2.required, function list(req, res) { - model.listBy(req.session.profile.id, 10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } + router.get('/mine', oauth2.required, function list(req, res, next) { + model.listBy( + req.session.profile.id, + 10, + req.query.pageToken, + function (err, entities, cursor, apiResponse) { + console.log(err, apiResponse); + if (err) { return next(err); } res.render('books/list.jade', { books: entities, nextPageToken: cursor @@ -67,7 +65,11 @@ module.exports = function(model, images, oauth2) { }); // [END mine] - + /** + * GET /books/add + * + * Display a form for creating a book. + */ router.get('/add', function addForm(req, res) { res.render('books/form.jade', { book: {}, @@ -75,10 +77,17 @@ module.exports = function(model, images, oauth2) { }); }); - + /** + * POST /books/add + * + * Create a book. + */ // [START add] - router.post('/add', images.multer.single('image'), images.sendUploadToGCS, - function insert(req, res) { + router.post( + '/add', + images.multer.single('image'), + images.sendUploadToGCS, + function insert(req, res, next) { var data = req.body; // If the user is logged in, set them as the creator of the book. @@ -96,18 +105,22 @@ module.exports = function(model, images, oauth2) { } // Save the data to the database. - model.create(data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.create(data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); + } + ); // [END add] - - - router.get('/:book/edit', function editForm(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id/edit + * + * Display a book for editing. + */ + router.get('/:book/edit', function editForm(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/form.jade', { book: entity, action: 'Edit' @@ -115,9 +128,16 @@ module.exports = function(model, images, oauth2) { }); }); - - router.post('/:book/edit', images.multer.single('image'), - images.sendUploadToGCS, function update(req, res) { + /** + * POST /books/:id/edit + * + * Update a book. + */ + router.post( + '/:book/edit', + images.multer.single('image'), + images.sendUploadToGCS, + function update(req, res, next) { var data = req.body; // Was an image uploaded? If so, we'll use its public URL @@ -126,31 +146,48 @@ module.exports = function(model, images, oauth2) { req.body.imageUrl = req.file.cloudStoragePublicUrl; } - model.update(req.params.book, data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.update(req.params.book, data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); - - - router.get('/:book', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + } + ); + + /** + * GET /books/:id + * + * Display a book. + */ + router.get('/:book', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/view.jade', { book: entity }); }); }); - - router.get('/:book/delete', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id/delete + * + * Delete a book. + */ + router.get('/:book/delete', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.redirect(req.baseUrl); }); }); + /** + * Errors on "/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = err.message; + next(err); + }); return router; - -}; +}; \ No newline at end of file diff --git a/5-logging/app.js b/5-logging/app.js index e64eef0b32..a6b9a1b4a8 100644 --- a/5-logging/app.js +++ b/5-logging/app.js @@ -26,14 +26,12 @@ app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.set('trust proxy', true); - // Add the request logger before anything else so that it can // accurately log requests. // [START requests] app.use(logging.requestLogger); // [END requests] - // Configure the session and session storage. // MemoryStore isn't viable in a multi-server configuration, so we // use encrypted cookies. Redis or Memcache is a great option for @@ -47,19 +45,16 @@ app.use(session({ var oauth2 = require('./lib/oauth2')(config.oauth2); app.use(oauth2.router); - // Setup modules and dependencies var images = require('./lib/images')(config.gcloud, config.cloudStorageBucket); var model = require('./books/model-' + config.dataBackend)(config); - // Books app.use('/books', require('./books/crud')(model, images, oauth2)); app.use('/api/books', require('./books/api')(model)); - // Redirect root to /books -app.get('/', function(req, res) { +app.get('/', function (req, res) { res.redirect('/books'); }); @@ -69,15 +64,20 @@ app.get('/', function(req, res) { // [START errors] app.use(logging.errorLogger); +// Basic 404 handler +app.use(function (req, res) { + res.status(404).send('Not Found'); +}); // Basic error handler -app.use(function(err, req, res, next) { +app.use(function (err, req, res, next) { /* jshint unused:false */ - res.status(500).send('Something broke!'); + // If our routes specified a specific response, then send that. Otherwise, + // send a generic message so as not to leak anything. + res.status(500).send(err.response || 'Something broke!'); }); // [END errors] - if (module === require.main) { // Start the server var server = app.listen(config.port, function () { diff --git a/5-logging/books/api.js b/5-logging/books/api.js index 0d195c2391..0322929233 100644 --- a/5-logging/books/api.js +++ b/5-logging/books/api.js @@ -16,66 +16,88 @@ var express = require('express'); var bodyParser = require('body-parser'); - -module.exports = function(model) { +module.exports = function (model) { var router = express.Router(); + // Automatically parse request body as JSON router.use(bodyParser.json()); - - function handleRpcError(err, res) { - if (err.code === 404) { return res.status(404); } - res.status(500).json({ - message: err.message, - internalCode: err.code - }); - } - - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.json({ - items: entities, - nextPageToken: cursor - }); + /** + * GET /api/books + * + * Retrieve a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.json({ + items: entities, + nextPageToken: cursor }); + }); }); - - router.post('/', function insert(req, res) { - model.create(req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * POST /api/books + * + * Create a new book. + */ + router.post('/', function insert(req, res, next) { + model.create(req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.get('/:book(\\d+)', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /api/books/:id + * + * Retrieve a book. + */ + router.get('/:book(\\d+)', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.put('/:book(\\d+)', function update(req, res) { - model.update(req.params.book, req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * PUT /api/books/:id + * + * Update a book. + */ + router.put('/:book(\\d+)', function update(req, res, next) { + model.update(req.params.book, req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.delete('/:book(\\d+)', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * DELETE /api/books/:id + * + * Delete a book. + */ + router.delete('/:book(\\d+)', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.status(200).send('OK'); }); }); - return router; + /** + * Errors on "/api/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = { + message: err.message, + internalCode: err.code + }; + next(err); + }); + return router; }; diff --git a/5-logging/books/crud.js b/5-logging/books/crud.js index 8fc7865b41..f5c4e271ea 100644 --- a/5-logging/books/crud.js +++ b/5-logging/books/crud.js @@ -15,48 +15,46 @@ var express = require('express'); - -module.exports = function(model, images, oauth2) { +module.exports = function (model, images, oauth2) { var router = express.Router(); - // Use the oauth middleware to automatically get the user's profile // information and expose login/logout URLs to templates. router.use(oauth2.aware); router.use(oauth2.template); - - function handleRpcError(err, res) { - res.status(err.code || 500).send(err.message); - } - - - router.use(function(req, res, next) { + // Set Content-Type for all responses for these routes + router.use(function (req, res, next) { res.set('Content-Type', 'text/html'); next(); }); - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.render('books/list.jade', { - books: entities, - nextPageToken: cursor - }); - } - ); + /** + * GET /books/add + * + * Display a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.render('books/list.jade', { + books: entities, + nextPageToken: cursor + }); + }); }); - // Use the oauth2.required middleware to ensure that only logged-in users // can access this handler. - router.get('/mine', oauth2.required, function list(req, res) { - model.listBy(req.session.profile.id, 10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } + router.get('/mine', oauth2.required, function list(req, res, next) { + model.listBy( + req.session.profile.id, + 10, + req.query.pageToken, + function (err, entities, cursor, apiResponse) { + console.log(err, apiResponse); + if (err) { return next(err); } res.render('books/list.jade', { books: entities, nextPageToken: cursor @@ -65,7 +63,11 @@ module.exports = function(model, images, oauth2) { ); }); - + /** + * GET /books/add + * + * Display a form for creating a book. + */ router.get('/add', function addForm(req, res) { res.render('books/form.jade', { book: {}, @@ -73,9 +75,17 @@ module.exports = function(model, images, oauth2) { }); }); - - router.post('/add', images.multer.single('image'), images.sendUploadToGCS, - function insert(req, res) { + /** + * POST /books/add + * + * Create a book. + */ + // [START add] + router.post( + '/add', + images.multer.single('image'), + images.sendUploadToGCS, + function insert(req, res, next) { var data = req.body; // If the user is logged in, set them as the creator of the book. @@ -93,16 +103,22 @@ module.exports = function(model, images, oauth2) { } // Save the data to the database. - model.create(data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.create(data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); - - - router.get('/:book/edit', function editForm(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + } + ); + // [END add] + + /** + * GET /books/:id/edit + * + * Display a book for editing. + */ + router.get('/:book/edit', function editForm(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/form.jade', { book: entity, action: 'Edit' @@ -110,9 +126,16 @@ module.exports = function(model, images, oauth2) { }); }); - - router.post('/:book/edit', images.multer.single('image'), - images.sendUploadToGCS, function update(req, res) { + /** + * POST /books/:id/edit + * + * Update a book. + */ + router.post( + '/:book/edit', + images.multer.single('image'), + images.sendUploadToGCS, + function update(req, res, next) { var data = req.body; // Was an image uploaded? If so, we'll use its public URL @@ -121,31 +144,48 @@ module.exports = function(model, images, oauth2) { req.body.imageUrl = req.file.cloudStoragePublicUrl; } - model.update(req.params.book, data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.update(req.params.book, data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); - - - router.get('/:book', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + } + ); + + /** + * GET /books/:id + * + * Display a book. + */ + router.get('/:book', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/view.jade', { book: entity }); }); }); - - router.get('/:book/delete', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id/delete + * + * Delete a book. + */ + router.get('/:book/delete', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.redirect(req.baseUrl); }); }); + /** + * Errors on "/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = err.message; + next(err); + }); return router; - -}; +}; \ No newline at end of file diff --git a/6-pubsub/app.js b/6-pubsub/app.js index 6bab05f31c..0865849422 100644 --- a/6-pubsub/app.js +++ b/6-pubsub/app.js @@ -26,12 +26,10 @@ app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.set('trust proxy', true); - // Add the request logger before anything else so that it can // accurately log requests. app.use(logging.requestLogger); - // Configure the session and session storage. // MemoryStore isn't viable in a multi-server configuration, so we // use encrypted cookies. Redis or Memcache is a great option for @@ -45,21 +43,21 @@ app.use(session({ var oauth2 = require('./lib/oauth2')(config.oauth2); app.use(oauth2.router); - // Setup modules and dependencies var background = require('./lib/background')(config.gcloud, logging); var images = require('./lib/images')( - config.gcloud, config.cloudStorageBucket, logging); + config.gcloud, + config.cloudStorageBucket, + logging +); var model = require('./books/model-' + config.dataBackend)(config, background); - // Books app.use('/books', require('./books/crud')(model, images, oauth2)); app.use('/api/books', require('./books/api')(model)); - // Redirect root to /books -app.get('/', function(req, res) { +app.get('/', function (req, res) { res.redirect('/books'); }); @@ -68,14 +66,19 @@ app.get('/', function(req, res) { // handlers should go after this. app.use(logging.errorLogger); +// Basic 404 handler +app.use(function (req, res) { + res.status(404).send('Not Found'); +}); // Basic error handler -app.use(function(err, req, res, next) { +app.use(function (err, req, res, next) { /* jshint unused:false */ - res.status(500).send('Something broke!'); + // If our routes specified a specific response, then send that. Otherwise, + // send a generic message so as not to leak anything. + res.status(500).send(err.response || 'Something broke!'); }); - if (module === require.main) { // Start the server var server = app.listen(config.port, function () { diff --git a/6-pubsub/books/api.js b/6-pubsub/books/api.js index 0d195c2391..0322929233 100644 --- a/6-pubsub/books/api.js +++ b/6-pubsub/books/api.js @@ -16,66 +16,88 @@ var express = require('express'); var bodyParser = require('body-parser'); - -module.exports = function(model) { +module.exports = function (model) { var router = express.Router(); + // Automatically parse request body as JSON router.use(bodyParser.json()); - - function handleRpcError(err, res) { - if (err.code === 404) { return res.status(404); } - res.status(500).json({ - message: err.message, - internalCode: err.code - }); - } - - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.json({ - items: entities, - nextPageToken: cursor - }); + /** + * GET /api/books + * + * Retrieve a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.json({ + items: entities, + nextPageToken: cursor }); + }); }); - - router.post('/', function insert(req, res) { - model.create(req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * POST /api/books + * + * Create a new book. + */ + router.post('/', function insert(req, res, next) { + model.create(req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.get('/:book(\\d+)', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /api/books/:id + * + * Retrieve a book. + */ + router.get('/:book(\\d+)', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.put('/:book(\\d+)', function update(req, res) { - model.update(req.params.book, req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * PUT /api/books/:id + * + * Update a book. + */ + router.put('/:book(\\d+)', function update(req, res, next) { + model.update(req.params.book, req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.delete('/:book(\\d+)', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * DELETE /api/books/:id + * + * Delete a book. + */ + router.delete('/:book(\\d+)', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.status(200).send('OK'); }); }); - return router; + /** + * Errors on "/api/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = { + message: err.message, + internalCode: err.code + }; + next(err); + }); + return router; }; diff --git a/6-pubsub/books/crud.js b/6-pubsub/books/crud.js index 8fc7865b41..f5c4e271ea 100644 --- a/6-pubsub/books/crud.js +++ b/6-pubsub/books/crud.js @@ -15,48 +15,46 @@ var express = require('express'); - -module.exports = function(model, images, oauth2) { +module.exports = function (model, images, oauth2) { var router = express.Router(); - // Use the oauth middleware to automatically get the user's profile // information and expose login/logout URLs to templates. router.use(oauth2.aware); router.use(oauth2.template); - - function handleRpcError(err, res) { - res.status(err.code || 500).send(err.message); - } - - - router.use(function(req, res, next) { + // Set Content-Type for all responses for these routes + router.use(function (req, res, next) { res.set('Content-Type', 'text/html'); next(); }); - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.render('books/list.jade', { - books: entities, - nextPageToken: cursor - }); - } - ); + /** + * GET /books/add + * + * Display a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.render('books/list.jade', { + books: entities, + nextPageToken: cursor + }); + }); }); - // Use the oauth2.required middleware to ensure that only logged-in users // can access this handler. - router.get('/mine', oauth2.required, function list(req, res) { - model.listBy(req.session.profile.id, 10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } + router.get('/mine', oauth2.required, function list(req, res, next) { + model.listBy( + req.session.profile.id, + 10, + req.query.pageToken, + function (err, entities, cursor, apiResponse) { + console.log(err, apiResponse); + if (err) { return next(err); } res.render('books/list.jade', { books: entities, nextPageToken: cursor @@ -65,7 +63,11 @@ module.exports = function(model, images, oauth2) { ); }); - + /** + * GET /books/add + * + * Display a form for creating a book. + */ router.get('/add', function addForm(req, res) { res.render('books/form.jade', { book: {}, @@ -73,9 +75,17 @@ module.exports = function(model, images, oauth2) { }); }); - - router.post('/add', images.multer.single('image'), images.sendUploadToGCS, - function insert(req, res) { + /** + * POST /books/add + * + * Create a book. + */ + // [START add] + router.post( + '/add', + images.multer.single('image'), + images.sendUploadToGCS, + function insert(req, res, next) { var data = req.body; // If the user is logged in, set them as the creator of the book. @@ -93,16 +103,22 @@ module.exports = function(model, images, oauth2) { } // Save the data to the database. - model.create(data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.create(data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); - - - router.get('/:book/edit', function editForm(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + } + ); + // [END add] + + /** + * GET /books/:id/edit + * + * Display a book for editing. + */ + router.get('/:book/edit', function editForm(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/form.jade', { book: entity, action: 'Edit' @@ -110,9 +126,16 @@ module.exports = function(model, images, oauth2) { }); }); - - router.post('/:book/edit', images.multer.single('image'), - images.sendUploadToGCS, function update(req, res) { + /** + * POST /books/:id/edit + * + * Update a book. + */ + router.post( + '/:book/edit', + images.multer.single('image'), + images.sendUploadToGCS, + function update(req, res, next) { var data = req.body; // Was an image uploaded? If so, we'll use its public URL @@ -121,31 +144,48 @@ module.exports = function(model, images, oauth2) { req.body.imageUrl = req.file.cloudStoragePublicUrl; } - model.update(req.params.book, data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.update(req.params.book, data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); - - - router.get('/:book', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + } + ); + + /** + * GET /books/:id + * + * Display a book. + */ + router.get('/:book', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/view.jade', { book: entity }); }); }); - - router.get('/:book/delete', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id/delete + * + * Delete a book. + */ + router.get('/:book/delete', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.redirect(req.baseUrl); }); }); + /** + * Errors on "/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = err.message; + next(err); + }); return router; - -}; +}; \ No newline at end of file diff --git a/7-gce/app.js b/7-gce/app.js index 5684f2d324..677c6ec462 100644 --- a/7-gce/app.js +++ b/7-gce/app.js @@ -26,12 +26,10 @@ app.set('views', path.join(__dirname, 'views')); app.set('view engine', 'jade'); app.set('trust proxy', true); - // Add the request logger before anything else so that it can // accurately log requests. app.use(logging.requestLogger); - // Configure the session and session storage. // MemoryStore isn't viable in a multi-server configuration, so we // use encrypted cookies. Redis or Memcache is a great option for @@ -45,45 +43,48 @@ app.use(session({ var oauth2 = require('./lib/oauth2')(config.oauth2); app.use(oauth2.router); - // Setup modules and dependencies var background = require('./lib/background')(config.gcloud, logging); var images = require('./lib/images')( - config.gcloud, config.cloudStorageBucket, logging); + config.gcloud, + config.cloudStorageBucket, + logging +); var model = require('./books/model-' + config.dataBackend)(config, background); - // Books app.use('/books', require('./books/crud')(model, images, oauth2)); app.use('/api/books', require('./books/api')(model)); - // Redirect root to /books -app.get('/', function(req, res) { +app.get('/', function (req, res) { res.redirect('/books'); }); - // Our application will need to respond to health checks when running on // Compute Engine with Managed Instance Groups. -app.get('/_ah/health', function(req, res) { +app.get('/_ah/health', function (req, res) { res.status(200).send('ok'); }); - // Add the error logger after all middleware and routes so that // it can log errors from the whole application. Any custom error // handlers should go after this. app.use(logging.errorLogger); +// Basic 404 handler +app.use(function (req, res) { + res.status(404).send('Not Found'); +}); // Basic error handler -app.use(function(err, req, res, next) { +app.use(function (err, req, res, next) { /* jshint unused:false */ - res.status(500).send('Something broke!'); + // If our routes specified a specific response, then send that. Otherwise, + // send a generic message so as not to leak anything. + res.status(500).send(err.response || 'Something broke!'); }); - if (module === require.main) { // Start the server var server = app.listen(config.port, function () { diff --git a/7-gce/books/api.js b/7-gce/books/api.js index 0d195c2391..0322929233 100644 --- a/7-gce/books/api.js +++ b/7-gce/books/api.js @@ -16,66 +16,88 @@ var express = require('express'); var bodyParser = require('body-parser'); - -module.exports = function(model) { +module.exports = function (model) { var router = express.Router(); + // Automatically parse request body as JSON router.use(bodyParser.json()); - - function handleRpcError(err, res) { - if (err.code === 404) { return res.status(404); } - res.status(500).json({ - message: err.message, - internalCode: err.code - }); - } - - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.json({ - items: entities, - nextPageToken: cursor - }); + /** + * GET /api/books + * + * Retrieve a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.json({ + items: entities, + nextPageToken: cursor }); + }); }); - - router.post('/', function insert(req, res) { - model.create(req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * POST /api/books + * + * Create a new book. + */ + router.post('/', function insert(req, res, next) { + model.create(req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.get('/:book(\\d+)', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * GET /api/books/:id + * + * Retrieve a book. + */ + router.get('/:book(\\d+)', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.put('/:book(\\d+)', function update(req, res) { - model.update(req.params.book, req.body, function(err, entity) { - if (err) { return handleRpcError(err, res); } + /** + * PUT /api/books/:id + * + * Update a book. + */ + router.put('/:book(\\d+)', function update(req, res, next) { + model.update(req.params.book, req.body, function (err, entity) { + if (err) { return next(err); } res.json(entity); }); }); - - router.delete('/:book(\\d+)', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * DELETE /api/books/:id + * + * Delete a book. + */ + router.delete('/:book(\\d+)', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.status(200).send('OK'); }); }); - return router; + /** + * Errors on "/api/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = { + message: err.message, + internalCode: err.code + }; + next(err); + }); + return router; }; diff --git a/7-gce/books/crud.js b/7-gce/books/crud.js index 8fc7865b41..f5c4e271ea 100644 --- a/7-gce/books/crud.js +++ b/7-gce/books/crud.js @@ -15,48 +15,46 @@ var express = require('express'); - -module.exports = function(model, images, oauth2) { +module.exports = function (model, images, oauth2) { var router = express.Router(); - // Use the oauth middleware to automatically get the user's profile // information and expose login/logout URLs to templates. router.use(oauth2.aware); router.use(oauth2.template); - - function handleRpcError(err, res) { - res.status(err.code || 500).send(err.message); - } - - - router.use(function(req, res, next) { + // Set Content-Type for all responses for these routes + router.use(function (req, res, next) { res.set('Content-Type', 'text/html'); next(); }); - - router.get('/', function list(req, res) { - model.list(10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } - res.render('books/list.jade', { - books: entities, - nextPageToken: cursor - }); - } - ); + /** + * GET /books/add + * + * Display a page of books (up to ten at a time). + */ + router.get('/', function list(req, res, next) { + model.list(10, req.query.pageToken, function (err, entities, cursor) { + if (err) { return next(err); } + res.render('books/list.jade', { + books: entities, + nextPageToken: cursor + }); + }); }); - // Use the oauth2.required middleware to ensure that only logged-in users // can access this handler. - router.get('/mine', oauth2.required, function list(req, res) { - model.listBy(req.session.profile.id, 10, req.query.pageToken, - function(err, entities, cursor) { - if (err) { return handleRpcError(err, res); } + router.get('/mine', oauth2.required, function list(req, res, next) { + model.listBy( + req.session.profile.id, + 10, + req.query.pageToken, + function (err, entities, cursor, apiResponse) { + console.log(err, apiResponse); + if (err) { return next(err); } res.render('books/list.jade', { books: entities, nextPageToken: cursor @@ -65,7 +63,11 @@ module.exports = function(model, images, oauth2) { ); }); - + /** + * GET /books/add + * + * Display a form for creating a book. + */ router.get('/add', function addForm(req, res) { res.render('books/form.jade', { book: {}, @@ -73,9 +75,17 @@ module.exports = function(model, images, oauth2) { }); }); - - router.post('/add', images.multer.single('image'), images.sendUploadToGCS, - function insert(req, res) { + /** + * POST /books/add + * + * Create a book. + */ + // [START add] + router.post( + '/add', + images.multer.single('image'), + images.sendUploadToGCS, + function insert(req, res, next) { var data = req.body; // If the user is logged in, set them as the creator of the book. @@ -93,16 +103,22 @@ module.exports = function(model, images, oauth2) { } // Save the data to the database. - model.create(data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.create(data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); - - - router.get('/:book/edit', function editForm(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + } + ); + // [END add] + + /** + * GET /books/:id/edit + * + * Display a book for editing. + */ + router.get('/:book/edit', function editForm(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/form.jade', { book: entity, action: 'Edit' @@ -110,9 +126,16 @@ module.exports = function(model, images, oauth2) { }); }); - - router.post('/:book/edit', images.multer.single('image'), - images.sendUploadToGCS, function update(req, res) { + /** + * POST /books/:id/edit + * + * Update a book. + */ + router.post( + '/:book/edit', + images.multer.single('image'), + images.sendUploadToGCS, + function update(req, res, next) { var data = req.body; // Was an image uploaded? If so, we'll use its public URL @@ -121,31 +144,48 @@ module.exports = function(model, images, oauth2) { req.body.imageUrl = req.file.cloudStoragePublicUrl; } - model.update(req.params.book, data, function(err, savedData) { - if (err) { return handleRpcError(err, res); } + model.update(req.params.book, data, function (err, savedData) { + if (err) { return next(err); } res.redirect(req.baseUrl + '/' + savedData.id); }); - }); - - - router.get('/:book', function get(req, res) { - model.read(req.params.book, function(err, entity) { - if (err) { return handleRpcError(err, res); } + } + ); + + /** + * GET /books/:id + * + * Display a book. + */ + router.get('/:book', function get(req, res, next) { + model.read(req.params.book, function (err, entity) { + if (err) { return next(err); } res.render('books/view.jade', { book: entity }); }); }); - - router.get('/:book/delete', function _delete(req, res) { - model.delete(req.params.book, function(err) { - if (err) { return handleRpcError(err, res); } + /** + * GET /books/:id/delete + * + * Delete a book. + */ + router.get('/:book/delete', function _delete(req, res, next) { + model.delete(req.params.book, function (err) { + if (err) { return next(err); } res.redirect(req.baseUrl); }); }); + /** + * Errors on "/books/*" routes. + */ + router.use(function handleRpcError(err, req, res, next) { + // Format error and forward to generic error handler for logging and + // responding to the request + err.response = err.message; + next(err); + }); return router; - -}; +}; \ No newline at end of file