namchey/linkpreview

View on GitHub
modules/application/controllers/errors.js

Summary

Maintainability
F
3 days
Test Coverage
'use strict';

const path = require('path'),
    HttpException = require(path.resolve('./modules/application/rest/http-exception')),
    PromiseValidationError = require(path.resolve('./modules/application/rest/promise-validation-error')),
    Slack = require('slack-node'),
    config = require(path.resolve('server/config')),
    events = require(path.resolve('modules/application/services/events')),
    debug = require('debug')('errors'),
    logger = require(path.resolve('./server/bunyan-logger'));

const bugWebhookURI = config.slack.bugWebhookURI;

const bugSlack = new Slack();
bugSlack.setWebhook(bugWebhookURI);

/**
 * @param err
 * @param req
 * @param res
 * @param next
 * @returns {*}
 */
exports.apiErrors = (err, req, res, next) => {
  // If the error object doesn't exists
  if(err === undefined) {
    debug('promise breaker');
    return;
  }

  if (!err) return next();

  if(res.headersSent) {
     return exports.logError(err, req);
  }

  if (err instanceof HttpException) {
    return res.status(err.statusCode).sendMessage(err.message);
  }

  if(err instanceof PromiseValidationError) {
    return res.status(err.statusCode).sendMessage(err.message);
  }

  if (err.name === 'ValidationError') {
    if (config.isProd) {
      exports.logError(err, req);
    }
    const firstValidationField = Object.keys(err.errors)[0];
    return res.status(400).sendMessage(`validation.failed.${firstValidationField}`);
  }

  if (err.name === 'CastError' && err.kind === 'ObjectId') {
    if (config.isProd) {
      exports.logError(err, req);

      return res.status(404).sendMessage('not_found');
    }

    return res.status(400).sendMessage('cast_error', null,
      {
        model: err.model.modelName,
        stringValue: err.stringValue,
        kind: err.kind,
        value: err.value,
        path: err.path,
        reason: err.reason
      }
    );

  }

  if(err.status === 404) {
    exports.logError(err, req);
    return res.status(404).sendMessage(err.message || 'not_found');
  }

  if(err.status === 400) {

    exports.logError(err, req);

    return res.status(400).sendMessage(err.message || 'not_found');
  }

  // Log it
  exports.logError(err, req);

  if(config.isProd) {
    return res.status(500).sendMessage('internal_server_error');
  }

  res.status(500).sendMessage('internal_server_error', null, {msg: err.message, stack: err.stack});

};

exports.redirectToServerError = (err, req, res, next) => {
  // If the error object doesn't exists
  if (!err) return next();

  if(res.headersSent) {
     return exports.logError(err, req);
  }

  // Log it
  exports.logError(err, req);
  // Redirect to error page
  if(config.isProd) {
    return res.status(500).json({message:'internal_server_error'});
  }

  res.status(500).json({message: err.message, stack: err.stack});
};

exports.noApiMiddlewareResponded = (req, res) => {
  if(res.headersSent) {
     return exports.logError(new Error('noApiMiddlewareResponded'), req);
  }

  if (config.isProd) {
      exports.logError(new Error(`noAPIMiddlewareResponded: ${req.url}`), req);
      return res.status(404).sendMessage('not_found');
  }
  return res.status(404).sendMessage('noApiMiddlewareResponded. Please see the API for reference');
};

/**
 * POST
 * Browser Error handler
 * @param req
 * @param res
 * @param next
 */

exports.postBrowserError = (req, res, next) => {
  exports.logBrowserError(req, req.body);
  res.status(200).json({message: 'ok'});
};

exports.noMiddlewareResponded = (req, res) => {
  if(res.headersSent) {
     return exports.logError(new Error('noMiddlewareResponded'), req);
  }

  if (config.isProd) {
      exports.logError(new Error(`noMiddlewareResponded: ${req.url}`), req);
      return res.status(404).sendMessage('not_found');
  }
  return res.status(404).sendMessage('noMiddlewareResponded. Please see the API for reference');
};

exports.logError = (error, req) => {

  if(!error instanceof Error) {
    if(config.isDev || config.isTest) {
      throw Error('err object should be instance of error');
    }
  }

  if (config.isProd) {
    logger.error(error);

    const agentInfo = events.getAgentInfo(req);
    let slackString = '';
    try {
      slackString = JSON.stringify({url: req.originalUrl, stack: error && error.stack, ...agentInfo});
    }catch(err) {
      return logger.error(err);
    }

    bugSlack.webhook({
      channel: config.slackBugChannel,
      username: config.slackUsername,
      text: slackString
    }, (err, response) => {
      if(err) {
        logger.error(error);
        return logger.error(err);
      }

      if(response.statusCode !== 200) {
        return logger.error(new Error(`slack_response_error: ${response.response}`));
      }
    });
  } else {
    console.log(error);
  }

};

/**
 * Log browser error in log file and slack
 * @param req
 * @param body
 */
exports.logBrowserError = (body = {}, req) => {
  if (config.isProd) {
    const fileLogContents = { ...body };

    //delete screenshot field for loggin in file
    if(fileLogContents.screenshot) {
      delete fileLogContents.screenshot;
    }

    logger.warn(fileLogContents, 'browser-error');

    //save to database
    const agentInfo = events.getAgentInfo(req);
    let slackString = '';

    try {
      slackString = JSON.stringify({...fileLogContents, user: req.user && req.user.username})
    }catch(err) {
      return logger.error(err);
    }

    bugSlack.webhook({
      channel: config.slackBugChannel,
      username: config.slackUsername,
      text: slackString
    }, (err, response) => {
      if(err) {
        logger.error(body);
        return logger.error(err);
      }
      if(response.statusCode !== 200) {
        return logger.error(new Error(`slack_response_error: ${response.response}`));
      }
    });

  } else {
    //delete screenshot dataURL for not polluting the console
    delete body.screenshot;
    console.log('browser-error');
    console.error(body);
    return;
  }

};