Asymmetrik/mean2-starter

View on GitHub
src/server/app/util/services/util.server.service.js

Summary

Maintainability
A
3 hrs
Test Coverage
'use strict';

var _ = require('lodash'),
    path = require('path'),
    mongoose = require('mongoose'),
    platform = require('platform'),

    deps = require(path.resolve('./src/server/dependencies.js')),
    config = deps.config,
    errorHandler = deps.errorHandler,
    logger = deps.logger;

function getValidationErrors(err) {
    var errors = [];

    if(null != err.errors) {
        for (var field in err.errors) {
            if(err.errors[field].path) {
                var message = (err.errors[field].type === 'required')? field + ' is required' : err.errors[field].message;
                errors.push({ field: field, message: message });
            }
        }
    }

    return errors;
}

module.exports.getErrorMessage = function(err) {
    if(typeof err === 'string') {
        return err;
    }

    var msg = 'unknown error';
    if(null != err.message) {
        msg = err.message;
    }

    if(null != err.stack) {
        msg = '[' + msg + '] ' + err.stack;
    }

    return msg;
};

module.exports.getClientErrorMessage = function(err) {
    if(config.exposeServerErrors) {
        return module.exports.getErrorMessage(err);
    } else {
        return 'A server error has occurred.';
    }
};

module.exports.handleErrorResponse = function(res, errorResult) {
    // Return the error state to the client, defaulting to 500
    errorResult = errorResult || {};

    if(errorResult.name === 'ValidationError') {
        var errors = getValidationErrors(errorResult);
        errorResult = {
            status: 400,
            type: 'validation',
            message: errors.map(function(e) { return e.message; }).join(', '),
            errors: errors
        };
    }

    // If the status is missing or invalid, default to 500
    if(!(errorResult.status >= 400 && errorResult.status <= 600)) {
        errorResult.status = 500;
    }

    // If it's a server error, get the client message
    if(errorResult.status >= 500 && errorResult.status < 600) {
        // Log the error since it's a server error
        logger.error(errorResult);

        // Generate the client-facing error
        errorResult = {
            status: errorResult.status,
            type: 'server-error',
            message: module.exports.getClientErrorMessage(errorResult)
        };
    }

    // Send the response
    res.status(errorResult.status).json(errorResult);
};

module.exports.catchError = function(res, err, callback) {
    if (err) {
        logger.error(err);
        return this.send400Error(res, err);
    } else if (null != callback) {
        callback();
    }
};

module.exports.send400Error = function (res, err) {
    return res.status(400).json({
        message: errorHandler.getErrorMessage(err)
    });
};

module.exports.send403Error = function (res) {
    return res.status(403).json({
        message: 'User is not authorized'
    });
};

module.exports.validateNonEmpty = function (property) {
    return (null != property && property.length > 0);
};

module.exports.validateArray = function (arr) {
    return (null != arr && arr.length > 0);
};

module.exports.toLowerCase = function (v){
    return (null != v)? v.toLowerCase(): undefined;
};

/**
 * Parse an input as a date. Handles various types
 * of inputs, such as Strings, Date objects, and Numbers.
 *
 * @param {date} The input representing a date / timestamp
 * @returns The timestamp in milliseconds since the Unix epoch
 */
module.exports.dateParse = function (date) {

    // Handle nil values by simply returning null
    if (_.isNil(date)) {
        return null;
    }

    // Date object should return its time in milliseconds
    if (_.isDate(date)) {
        return date.getTime();
    }

    // A number that exists will be interpreted as millisecond
    if (_.isFinite(date)) {
        return date;
    }

    // Handle number string
    if (!isNaN(date)) {
        return +date;
    }

    // Handle String, Object, etc.
    return Date.parse(date);
};

/**
 * Get the limit provided by the user, if there is one.
 * Limit has to be at least 1 and no more than 100, with
 * a default value of 20.
 *
 * @param queryParams
 * @param maxSize (optional) default: 100
 * @returns {number}
 */
module.exports.getLimit = function (queryParams, maxSize) {
    const max = maxSize || 100;
    const limit = _.get(queryParams, 'size', 20);
    return isNaN(limit) ? 20 : Math.max(1, Math.min(max, Math.floor(limit)));
};

/**
 * Page needs to be positive and has no upper bound
 * @param queryParams
 * @returns {number}
 */
module.exports.getPage = function (queryParams) {
    const page = _.get(queryParams, 'page', 0);
    return isNaN(page) ? 0 : Math.max(0, page);
};

/**
 * Extract given field from request header
 */
module.exports.getHeaderField = function (header, fieldName) {
    return (null == header || null == header[fieldName]) ? null : header[fieldName];
};

/**
 * Parses user agent information from request header
 */
module.exports.getUserAgentFromHeader = function(header) {
    let userAgent = this.getHeaderField(header, 'user-agent');

    let data = {};
    if (null != userAgent) {
        let info = platform.parse(userAgent);
        data = {
            browser: `${info.name} ${info.version}`,
            os: info.os.toString()
        };
    }

    return data;
};

function propToMongoose(prop, nonMongoFunction) {
    if (typeof prop === 'object' && prop.$date != null && typeof prop.$date === 'string') {
        return new Date(prop.$date);
    } else if (typeof prop === 'object' && prop.$obj != null && typeof prop.$obj === 'string') {
        return mongoose.Types.ObjectId(prop.$obj);
    }

    if (null != nonMongoFunction) {
        return nonMongoFunction(prop);
    }

    return null;
}

function toMongoose(obj) {
    if (null != obj) {
        if (typeof obj === 'object') {
            if (Array.isArray(obj)) {
                var arr = [];

                for (var index in obj) {
                    arr.push(propToMongoose(obj[index], toMongoose));
                }

                return arr;
            } else {
                var newObj = {};

                for (var prop in obj) {
                    newObj[prop] = propToMongoose(obj[prop], toMongoose);
                }

                return newObj;
            }
        }
    }

    return obj;
}

exports.toMongoose = toMongoose;

/**
 * Determine if an array contains a given element by doing a deep comparison.
 * @param arr
 * @param element
 * @returns {boolean} True if the array contains the given element, false otherwise.
 */
module.exports.contains = function(arr, element) {
    for (var i = 0; i < arr.length; i++) {
        if (_.isEqual(element, arr[i])) {
            return true;
        }
    }
    return false;
};

module.exports.toProvenance = function(user) {
    let now = new Date();
    return {
        username: user.username,
        org: user.organization,
        created: now.getTime(),
        updated: now.getTime()
    };
};

module.exports.emailMatcher = /.+@.+\..+/;