Source: adapters/http.js

'use strict';
const path = require('path');
const os = require('os');
const API_ADAPTERS = require(path.join(__dirname, '../api_adapters/index'));

/**
 * Appends a standard set of request data to an error response
 * @param  {Object} req  An express request object
 * @param  {Object} data Error data that should be included in response
 * @return {Object}      The original error data with request information added
 */
var generateErrorDetails = function (req, data = {}) {
	data.ipinfo = {
		'x-forwarded-for': req.headers['x-forwarded-for'],
		remoteAddress: req.connection.remoteAddress,
		referer: req.headers.referer,
		originalUrl: req.originalUrl,
		headerHost: req.headers.host,
		osHostname: os.hostname()
	};
	let user = req.user || req.body;
	if (user && (user.email || user.username)) data.ipinfo.user = user.email || user.username;
	return data;
};

/**
 * Appends a standard set of request data to a success response
 * @param  {Object} req  An express request object
 * @param  {Object} data Data to be included in response
 * @return {Object}      The original data with request information added
 */
var _generateSuccessDetails = function (req, data = {}) {
	data.periodic = data.periodic || {};
	data.periodic = {
		version: this.settings.version,
		name: this.settings.name
	};
	return Object.assign(data, {
		request: {
			query: req.query,
			params: req.params,
			baseurl: req.baseUrl,
			originalurl: req.originalUrl,
			parsed: req._parsedUrl,
			'x-forwarded-for': req.headers['x-forwarded-for'],
			remoteAddress: req.connection.remoteAddress,
			referer: req.headers.referer,
			headerHost: req.headers.host
		}
	});
};

/**
 * A protocol adapter for HTTP requests and responses
 * @type {HTTP_Adapter}
 */
const HTTP_ADAPTER = class HTTP_Adapter {
	/**
	 * Constructor for HTTP_Adapter
	 * @param  {Object} options Options for the HTTP adapter
	 * @param {Object} options.db Database adapters indexed by model name (periodicjs.core.data)
	 * @param {Object} options.express Express module
	 * @param {Object} options.responder A response adapter (periodicjs.core.responder)
	 * @param {Object} options.config Periodic configuration object
	 * @param {Object} options.settings Periodic application settings
	 * @param {Object} options.resources Periodic resources
	 * @param {string} options.api Name of the api adapter that should be used
	 * @param {Object} [options.logger=console] A logger module
	 */
	constructor (options = {}) {
		this.db = options.db;
		this.express = options.express;
		this.responder = options.responder;
		this.config = options.config || {};
		this.settings = options.settings || {};
		this.resources = options.resources || {};
		this.api = new API_ADAPTERS[options.api](this, options);
		this.logger = options.logger || console;
		this.utilities = options.utilities || {};
	}
	/**
	 * Handles logging errors
	 * @param  {Object} req     Express request object
	 * @param  {Object} res     Express response object
	 * @param  {Object} [options={}] Configurable options for error logging
	 * @param {Object} options.err An error to log
	 * @return {Object}         this
	 */
	error (req, res, options = {}) {
		let err = options.err;
		let data = (err) ? { err } : {};
		if (req) data = generateErrorDetails.call(this, req, data);
		this.logger.error((err instanceof Error || err.message) ? err.message : 'ERROR\r\n', (err instanceof Error || err.stack) ? err.stack : {}, data);
		return this;
	}
	/**
	 * Handles logging warns
	 * @param  {Object} req     Express request object
	 * @param  {Object} res     Express response object
	 * @param  {Object} [options={}] Configurable options for warning logging
	 * @param {Object} options.err An error to log
	 * @return {Object}         this
	 */
	warn (req, res, options = {}) {
		let err = options.err;
		let data = (err) ? { err } : {};
		if (req) data = generateErrorDetails.call(this, req, data);
		this.logger.warn((err instanceof Error || err.message) ? err.message : 'WARNING\r\n', (err instanceof Error || err.stack) ? err.stack : {}, data);
		return this;
	}
	/**
	 * Handles sending an error response
	 * @param  {Object} req     Express request object
	 * @param  {Object} res     Express response object
	 * @param  {Object} [options={}] Configurable options for error logging
	 * @param {Object} options.err An error to log. If this.config.exception_message is set error param is ignored
	 * @return {Object}         this
	 */
	exception (req, res, options = {}) {
		let err = (typeof this.config.exception_message === 'string') ? { message: this.config.exception_message } : options.err;
		if (req.xhr) {
			res.status(500).send({
				result: 'error',
				data: {
					error: (err) ? err.message : 'something blew up!'
				}
			});
		}
		else {
			res.status(500).render('home/error500', {
				message: err.message,
				error: err
			});
		}
		return this;
	}
	/**
	 * Handles sending the response to a request by rendering data according to this.responder configuration
	 * @param  {Object} req     Express request object
	 * @param  {Object} res     Express response object
	 * @param  {Object} [options={}] Configurable options for response
	 * @param {Boolean} options.ignore_error If true error will be treated like a normal response
	 * @param {Boolean} options.return_response_data If true respond will not send HTTP response and will instead return rendered data
	 * @param {*} options.responder_override Data to send in response. If this value is defined A success response will always be set and all formatting rules will be ignored
	 * @param {Boolean} options.skip_default_props If true request details will not be appended to success response
	 * @param {Object} options.err An error to send in response. If this value is set an error response will be generated unless options.ignore_error is true
	 * @param {Object} options.data Data to send in success response. If options.err is defined this value will be ignored
	 * @return {Object}         this
	 */
	respond (req, res, options = {}) {
		let { err, data } = options;
		if (err && !options.ignore_error) this.error(req, res, options);
		let responder = (err) ? this.responder.error.bind(this.responder) : this.responder.render.bind(this.responder);
		let responder_override = (options.responder_override) ? options.responder_override : false;
		if (responder_override) {
			if (req.query.callback) res.status(200).jsonp(responder_override);
			else res.status(200).send(responder_override);
		}
		else {
			return responder((err) ? err : ((!options.skip_default_props) ? _generateSuccessDetails.call(this, req, data) : data), options)
				.try(result => {
					if (options.return_response_data === true) return result;
					else {
						if (req.query.callback) res.status((err) ? 500 : 200).jsonp(result);
						else res.status((err) ? 500 : 200).send(result);
					}
				})
				.catch(err => {
					if (options.return_response_data === true) return Promise.reject(err);
					this.exception(req, res, Object.assign({}, options, { err }));
				});
		}
		return this;
	}
	/**
	 * Handles redirects
	 * @param  {Object} req     Express request object
	 * @param  {Object} res     Express response object
	 * @param  {Object} [options={}] Configurable options for redirect
	 * @param {string} options.model_name A path to attempt for redirect if req.redirect is not provided
	 * @return {Object}         this
	 */
	redirect (req, res, options = {}) {
		res.redirect(req.redirectpath || `/${ options.model_name }`);
		return this;
	}
	/**
	 * Convenience method for accessing .implement method on API adapter. Also handles implementing controllers and routers for multiple models
	 * @param  {Object} options Configurable options for implementing controllers and routers. See API adapter .implement method for more details
	 * @param {string|string[]} options.model_name An array of model names or a single model name that should have controllers and routes implemented
	 * @return {Object}         this
	 */
	implement (options = {}) {
		let router = this.router;
		this.controller = (this.controller && typeof this.controller === 'object') ? this.controller : {};
		if (typeof options.model_name === 'string') {
			let implemented = this.api.implement(Object.assign(options, { router }));
			this.router = (implemented.router && implemented.router._router) ? implemented.router._router : implemented.router;
			router = this.router;
			delete implemented.router;
			this.controller[options.model_name] = implemented;
		}
		else {
			Object.keys(this.db).forEach(key => {
				let implemented = this.api.implement(Object.assign(options, { router, model_name: key }));
				this.router = (implemented.router && implemented.router._router) ? implemented.router._router : implemented.router;
				router = this.router;
				delete implemented.router;
				this.controller[key] = implemented;
			});
		}
		return this;
	}
};

module.exports = HTTP_ADAPTER;