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"