From a01195590fecb9362e6f48cc70def86086d7df58 Mon Sep 17 00:00:00 2001 From: Piotr Kierzniewski Date: Wed, 14 Mar 2018 21:51:03 +0100 Subject: [PATCH] Add error handling --- lib/db/mongodb.js | 22 ++++++++++++++++++++++ lib/respositories/image.js | 15 +++++++++++++++ lib/respositories/index.js | 3 +++ lib/services/image.js | 4 ---- lib/web/controllers/image.js | 16 +++++++++++++--- lib/web/middlewares/error.js | 25 +++++++++++++++++++++++++ lib/web/routes/image.js | 3 ++- lib/web/routes/notfound.js | 25 +++++++++++++++++++++++++ lib/web/routes/optimize.js | 3 ++- lib/web/server.js | 35 +++++++++++++++++++++++++++++++++++ package.json | 18 +++++++++++------- yarn.lock | 6 ++++++ 12 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 lib/respositories/image.js create mode 100644 lib/respositories/index.js create mode 100644 lib/web/middlewares/error.js create mode 100644 lib/web/routes/notfound.js diff --git a/lib/db/mongodb.js b/lib/db/mongodb.js index be9311a..b96d92f 100644 --- a/lib/db/mongodb.js +++ b/lib/db/mongodb.js @@ -1,5 +1,27 @@ import mongoose from "mongoose"; +import logger from "winston"; import { mongodburl } from "./../config/"; +mongoose.connection.on("connected", function() { + logger.info("MongoDB connected."); +}); + +mongoose.connection.once("open", function() { + logger.info("MongoDB connection opened."); +}); + +mongoose.connection.on("disconnected", function() { + logger.info("MongoDB disconnected."); +}); + +mongoose.connection.on("reconnected", function() { + logger.info("MongoDB reconnected."); +}); + +mongoose.connection.on("error", function(err) { + logger.error("MongoDB error."); + logger.error(err); +}); + mongoose.connect(mongodburl); diff --git a/lib/respositories/image.js b/lib/respositories/image.js new file mode 100644 index 0000000..008cdea --- /dev/null +++ b/lib/respositories/image.js @@ -0,0 +1,15 @@ +import Image from "./../models/image"; + +export default { + findById(id) { + return new Promise(function(resolve, reject) { + Image.findById(id, function(err, image) { + if (err) { + return reject(err); + } + + resolve(image); + }); + }); + } +}; diff --git a/lib/respositories/index.js b/lib/respositories/index.js new file mode 100644 index 0000000..0156c78 --- /dev/null +++ b/lib/respositories/index.js @@ -0,0 +1,3 @@ +import image from "./image.js"; + +export { image }; diff --git a/lib/services/image.js b/lib/services/image.js index 8cf8621..58c9078 100644 --- a/lib/services/image.js +++ b/lib/services/image.js @@ -83,17 +83,14 @@ class ImageService { let buffer, publicPath, basePath, filePath; logger.info("Image optimization: Find image."); - image = await Image.findById(image.id); logger.info("Image optimization: Start image optimization."); - buffer = await imagemin.buffer(fs.readFileSync(image.path), { plugins: [imageminMozjpeg(), imageminJpegoptim(), imageminPngquant()] }); logger.info("Image optimization: Save optimized image to public path."); - publicPath = config.get("public"); if (!fs.existsSync(publicPath)) { @@ -110,7 +107,6 @@ class ImageService { fs.writeFileSync(filePath, buffer); logger.info("Image optimization: Update image model."); - image.optimized = true; image.optimized_path = filePath; image.optimized_size = image.resolveSize(); diff --git a/lib/web/controllers/image.js b/lib/web/controllers/image.js index c0fddee..2f80f8f 100644 --- a/lib/web/controllers/image.js +++ b/lib/web/controllers/image.js @@ -1,4 +1,6 @@ -import Image from "./../../models/image"; +import boom from "boom"; + +import { image as ImageRepository } from "./../../respositories"; /** * Image action. @@ -9,8 +11,16 @@ import Image from "./../../models/image"; * @returns {void} Returns nothing. */ async function image(req, res) { - let image = await Image.findById(req.params.id); - let response = Object.assign({}, { success: true }, image.toJSON()); + let image; + let response; + + image = await ImageRepository.findById(req.params.id); + + if (image === null) { + throw boom.notFound(`No image found for id ${req.params.id}`); + } + + response = Object.assign({}, { success: true }, image.toJSON()); res.status(200); res.json(response); diff --git a/lib/web/middlewares/error.js b/lib/web/middlewares/error.js new file mode 100644 index 0000000..75adf33 --- /dev/null +++ b/lib/web/middlewares/error.js @@ -0,0 +1,25 @@ +import boom from "boom"; + +/** + * Error middleware. + * + * Error middleware will catch any error throwed in controllers + * and pass it to express next function. + * + * @param {Function} fn - Express route controller action. + * + * @returns {Function} Express middleware function. + */ +function error(fn) { + return function(req, res, next) { + Promise.resolve(fn(req, res, next)).catch(err => { + if (!err.isBoom) { + return next(boom.badImplementation(err)); + } + + return next(err); + }); + }; +} + +export default error; diff --git a/lib/web/routes/image.js b/lib/web/routes/image.js index 241d963..df7612b 100644 --- a/lib/web/routes/image.js +++ b/lib/web/routes/image.js @@ -1,3 +1,4 @@ +import error from "./../middlewares/error.js"; import { image as imageAction } from "./../controllers/image.js"; /** @@ -9,7 +10,7 @@ import { image as imageAction } from "./../controllers/image.js"; */ function image(app) { // Get image route - app.route("/image/:id").get(imageAction); + app.route("/image/:id([a-f\\d]{24})").get(error(imageAction)); } export default image; diff --git a/lib/web/routes/notfound.js b/lib/web/routes/notfound.js new file mode 100644 index 0000000..5496cb2 --- /dev/null +++ b/lib/web/routes/notfound.js @@ -0,0 +1,25 @@ +import boom from "boom"; + +import error from "./../middlewares/error.js"; + +/** + * Not found route. + * + * Throw not found error if any routes get here. + * + * @param {Object} app - Express app. + * + * @returns {void} Returns nothing. + */ +function notfound(app) { + // Match all routes + app.route("*").get( + error(function(req, res) { + throw boom.notFound( + "The URI requested is invalid or the resource requested does not exist." + ); + }) + ); +} + +export default notfound; diff --git a/lib/web/routes/optimize.js b/lib/web/routes/optimize.js index 3a79a0b..d6d7c95 100644 --- a/lib/web/routes/optimize.js +++ b/lib/web/routes/optimize.js @@ -1,3 +1,4 @@ +import error from "./../middlewares/error.js"; import validate from "./../middlewares/validator.js"; import imageHandler from "./../middlewares/handlers/image.js"; @@ -15,7 +16,7 @@ function optimize(app) { // Optimize image route app .route("/optimize") - .post(validate(optimizeValidator), imageHandler, optimizeAction); + .post(validate(optimizeValidator), imageHandler, error(optimizeAction)); } export default optimize; diff --git a/lib/web/server.js b/lib/web/server.js index 43570f4..d1493f8 100644 --- a/lib/web/server.js +++ b/lib/web/server.js @@ -6,6 +6,7 @@ import bodyParser from "body-parser"; import optimizeRoutes from "./routes/optimize.js"; import imageRoutes from "./routes/image.js"; +import notfoundRoutes from "./routes/notfound.js"; /** * Init web server. @@ -23,10 +24,44 @@ function web() { optimizeRoutes(app); imageRoutes(app); + notfoundRoutes(app); + + app.use(errors); logger.info(`Starting Motimize web REST API server on ${port} port.`); app.listen(port); } +/** + * Error handler. + * + * @param {Object} err - Error object. + * @param {Object} req - Express request object. + * @param {Object} res - Express response object. + * @param {Function} next - Express next function. + * + * @returns {void} Returns nothing. + */ +function errors(err, req, res, next) { + /* Log error internaly */ + logger.error(err); + + /** + * Remove Error's `stack` property. We don't want + * users to see this at the production env + */ + if ( + process.env.NODE_ENV !== "development" || + process.env.NODE_ENV !== "test" + ) { + delete err.stack; + } + + /* Finaly respond to the request */ + res + .status(err.output.statusCode || 500) + .json({ success: false, message: err.output.payload.message }); +} + export default web; diff --git a/package.json b/package.json index d43a527..c885566 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "babel-core": "^6.26.0", "babel-preset-env": "^1.6.1", "babel-register": "^6.26.0", + "boom": "^7.2.0", "bull": "^3.3.10", "dotenv": "^5.0.1", "download": "^6.2.5", @@ -79,14 +80,17 @@ "jsdoc/require-param-type": "error", "jsdoc/require-returns-description": "error", "jsdoc/require-returns-type": "error", - "require-jsdoc": ["error", { - "require": { - "FunctionDeclaration": true, - "MethodDefinition": true, - "ClassDeclaration": true, - "ArrowFunctionExpression": true + "require-jsdoc": [ + "error", + { + "require": { + "FunctionDeclaration": true, + "MethodDefinition": true, + "ClassDeclaration": true, + "ArrowFunctionExpression": true + } } - }], + ], "valid-jsdoc": "error" } }, diff --git a/yarn.lock b/yarn.lock index 969e4a9..01df154 100644 --- a/yarn.lock +++ b/yarn.lock @@ -967,6 +967,12 @@ boom@5.x.x: dependencies: hoek "4.x.x" +boom@^7.2.0: + version "7.2.0" + resolved "https://registry.yarnpkg.com/boom/-/boom-7.2.0.tgz#2bff24a55565767fde869ec808317eb10c48e966" + dependencies: + hoek "5.x.x" + boxen@^1.2.1: version "1.3.0" resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"