appirio-tech/lc1-discussion-service

View on GitHub
lib/controllerHelper.js

Summary

Maintainability
F
4 days
Test Coverage
/**
 * Copyright (c) 2014 TopCoder, Inc. All rights reserved.
 */
/**
 * Helper methods for controller logic.
 *
 * @version 1.0
 * @author peakpado
 */
'use strict';

var async = require('async');
var _ = require('lodash');
var queryConfig = require('config').get('app.query');
var routeHelper = require('./routeHelper');
var paramHelper = require('./paramHelper');
var partialResponseHelper = require('./partialResponseHelper');

/**
 * Find an entity with the provided filters and its own id.
 * @param model the entity model
 * @param filters the current filters
 * @param req the request
 * @param callback the async callback
 */
function _findEntityByFilter(model, filters, req, callback) {
  // add the id parameter to the filters
  var idParam = routeHelper.getRefIdField(model);
  var idParamValue = req.swagger.params[idParam].value;
  var idValue = Number(idParamValue);
  // check id is valid numer and positive number
  if (_.isNaN(idValue) || idValue < 0) {
    routeHelper.addErrorMessage(req, 'Invalid id parameter '+idParamValue, 400);
    callback(req.error);
  } else {
    var refFilters = _.cloneDeep(filters);
    refFilters.where.id = idValue;
    model.find(refFilters).success(function(entity) {
      if (!entity) {
        routeHelper.addErrorMessage(req, 'Cannot find the '+ model.name + ' with id '+idParamValue, 404);
        callback(req.error);
      } else {
        callback(null, filters, entity);
      }
    })
    .error(function(err) {
      routeHelper.addError(req, err);
      callback(req.error);
    });
  }
}

/**
 * Build filters from request query parameters.
 * @param model the entity model
 * @param filtering the boolean flag of whether a filtering is enabled or not
 * @param filters the current filters
 * @param req the request
 * @param callback the async callback
 */
function _buildQueryFilter(model, filtering, filters, req, callback) {
  if (!filters) {
    filters = { where: {} };   // start with emtpty filter
  }
  if (filtering) {
    filters.offset = 0;
    filters.limit = queryConfig.pageSize;
    // req.swagger.params returns empty value for non-existing parameters, it can't determine it's non-existing
    // or empty value. So req.query should be used to validate empty value and not-supportd parameters.
    // parse request parameters.
    _.each(_.keys(req.query), function(key) {
      if (key === 'offset' || key === 'limit') {
        paramHelper.parseLimitOffset(req, filters, key, req.query[key]);
      } else if (key === 'orderBy') {
        paramHelper.parseOrderBy(model, req, filters, req.query[key]);
      } else if (key === 'filter') {
        paramHelper.parseFilter(model, req, filters, req.query[key]);
      } else {
        routeHelper.addValidationError(req, 'The request parameter ' + key + ' is not supported');
      }
    });
  }
  callback(req.error, filters);
}

/**
 * Build filters from reference models.
 * @param referenceModels the array of referencing models
 * @param req the request
 * @param callback the async callback
 */
function _buildReferenceFilter(referenceModels, req, callback) {
  var filters = { where: {} };   // start with emtpty filter
  if (!referenceModels) {
    callback(null, filters);
  } else {
    async.eachSeries(referenceModels, function(refModel, cb) {
      var idParam = routeHelper.getRefIdField(refModel);
      var idParamValue = req.swagger.params[idParam].value;
      var idValue = Number(idParamValue);
      // check id is valid numer and positive number
      if (_.isNaN(idValue) || idValue < 0) {
        routeHelper.addErrorMessage(req, 'Invalid id parameter '+idParamValue, 400);
        cb(req.error);
      } else {
        var refFilters = _.cloneDeep(filters);
        refFilters.where.id = idValue;
        // verify an element exists in the reference model
        refModel.find(refFilters).success(function(refEntity) {
          if(!refEntity) {
            routeHelper.addErrorMessage(req, 'Cannot find the '+ refModel.name + ' with id '+idParamValue, 404);
            cb(req.error);
          } else {
            // add the id of reference element to filters
            filters.where[idParam] = refEntity.id;
            cb(null);
          }
        }).error(function(err) {
            routeHelper.addError(req, err);
            cb(req.error);
        });
      }
    }, function(err) {
      if (err) {
        callback(req.error);
      } else {
        // pass the filters to the next function in async
        callback(null, filters);
      }
    });
  }
}

/**
 * Return error if there are extra parameters.
 * @param req the request
 * @param callback the async callback
 */
function _checkExtraParameters(req, callback) {
  if (_.keys(req.query).length > 0) {
    routeHelper.addErrorMessage(req, 'Query parameter is not allowed', 400);
    callback(req.error);
  } else {
    callback(null);
  }
}

/**
 * This function retrieves all entities in the model by filters.
 * @param model the entity model
 * @param filters the filters to search
 * @param req the request
 * @param funcCallback the callback to return the result
 */
function findEntities(model, filters, req, funcCallback) {
  model.findAndCountAll(filters).success(function(result) {
    funcCallback(null, result.count, result.rows);
  })
  .error(function(err) {
    routeHelper.addError(req, err);
    funcCallback(req.error);
  });
}

/**
 * This function retrieves all entities in the model filtered by referencing model
    and search criterias if filtering is enabled.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param req the request
 * @param funcCallback the callback to return the result
 */
function allEntities(model, referenceModels, options, req, funcCallback) {
  async.waterfall([
    function(callback) {
      if (!options.filtering) {
        _checkExtraParameters(req, callback);
      } else {
        callback(null);
      }
    },
    function(callback) {
      _buildReferenceFilter(referenceModels, req, callback);
    },
    function(filters, callback) {
      _buildQueryFilter(model, options.filtering, filters, req, callback);
    },
    function(filters, callback) {
      // use entity filter IDs if configured
      if (options && options.entityFilterIDs) {
        filters.where = _.omit(filters.where, function(value, key) {
          return options.entityFilterIDs.indexOf(key) === -1;
        });
      }

      findEntities(model, filters, req, callback);
    }
  ], function(err, totalCount, entities) {
    funcCallback(err, totalCount, entities);
  });
}

/**
 * This function retrieves all entities in the model filtered by referencing model
    and search criterias if filtering is enabled.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param req the request
 * @param res the response
 * @param next the next function in the chain
 */
function all(model, referenceModels, options, req, res, next) {
  allEntities(model, referenceModels, options, req, function(err, totalCount, entities) {
    if (!err) {
      req.data = {
        success: true,
        status: 200,
        metadata: {
          totalCount: totalCount
        },
        content: entities
      };
    }
    partialResponseHelper.reduceFieldsAndExpandObject(model, req, next);
  });
}

/**
 * This function gets an entity by id.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param funcCallback the callback to return the result
 */
function getEntity(model, referenceModels, options, req, funcCallback) {
  async.waterfall([
    function(callback) {
      _checkExtraParameters(req, callback);
    },
    function(callback) {
      _buildReferenceFilter(referenceModels, req, callback);
    },
    function(filters, callback) {
      // use entity filter IDs if configured
      if (options && options.entityFilterIDs) {
        filters.where = _.omit(filters.where, function(value, key) {
          return options.entityFilterIDs.indexOf(key) === -1;
        });
      }
      _findEntityByFilter(model, filters, req, callback);
    }
  ], function(err, filters, entity) {
    funcCallback(err, entity);
  });

}

/**
 * This function gets an entity by id.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param res the response
 * @param next the next function in the chain
 */
function get(model, referenceModels, options, req, res, next) {
  getEntity(model, referenceModels, options, req, function(err, entity) {
    if (!err) {
      req.data = {
        success: true,
        status: 200,
        content: entity
      };
    }
    partialResponseHelper.reduceFieldsAndExpandObject(model, req, next);
  });
}

/**
 * This function creates an entity.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param req the request
 * @param funcCallback the callback to return the result
 */
function createEntity(model, referenceModels, options, req, funcCallback) {
  async.waterfall([
    function(callback) {
      _checkExtraParameters(req, callback);
    },
    function(callback) {
      _buildReferenceFilter(referenceModels, req, callback);
    },
    function(filters, callback) {
      // exclude prohibited fields
      var data = _.omit(req.swagger.params.body.value, 'id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy');
      // set createdBy and updatedBy user
      data.createdBy = routeHelper.getSigninUser(req).id;
      data.updatedBy = routeHelper.getSigninUser(req).id;
      if (req.param('useCurrentUserAsAuthor')) {
        data.authorId = routeHelper.getSigninUser(req).id;
      }
      // add foreign keys
      _.extend(data, filters.where);
      model.create(data).success(function(entity) {
        callback(null, entity);
      })
      .error(function(err) {
        routeHelper.addError(req, err);
        callback(req.error);
      });
    }
  ], function(err, entity) {
    funcCallback(err, entity);
  });
}

/**
 * This function creates an entity.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param req the request
 * @param res the response
 * @param next the next function in the chain
 */
function create(model, referenceModels, options, req, res, next) {
  createEntity(model, referenceModels, options, req, function(err, entity) {
    if (!err) {
      req.data = {
        id: entity.id,
        result: {
          success: true,
          status: 200
        }
      };
    }
    next();
  });
}

/**
 * This function updates an entity.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param req the request
 * @param funcCallback the callback to return the result
 */
function updateEntity(model, referenceModels, options, req, funcCallback) {
  async.waterfall([
    function(callback) {
      _checkExtraParameters(req, callback);
    },
    function(callback) {
      _buildReferenceFilter(referenceModels, req, callback);
    },
    function(filters, callback) {
      // use entity filter IDs if configured
      if (options && options.entityFilterIDs) {
        filters.where = _.omit(filters.where, function(value, key) {
          return options.entityFilterIDs.indexOf(key) === -1;
        });
      }
      _findEntityByFilter(model, filters, req, callback);
    },
    function(filters, entity, callback) {
      var excludeFields = Object.keys(filters.where);
      _.map(['id', 'createdAt', 'updatedAt', 'createdBy', 'updatedBy'], function(field) {
        excludeFields.push(field);
      });
      // exclude prohibited fields
      var data = _.omit(req.swagger.params.body.value, excludeFields);
      _.extend(entity, data);
      entity.updatedBy = routeHelper.getSigninUser(req).id;
      entity.save().success(function() {
        callback(null, entity);
      })
      .error(function(err) {
        routeHelper.addError(req, err);
        callback(req.error);
      });
    }
  ], function(err, entity) {
    funcCallback(err, entity);
  });
}

/**
 * This function updates an entity.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param req the request
 * @param res the response
 * @param next the next function in the chain
 */
function update(model, referenceModels, options, req, res, next) {
  updateEntity(model, referenceModels, options, req, function(err, entity) {
    if (!err) {
      req.data = {
        id: entity.id,
        result: {
          success: true,
          status: 200
        }
      };
    }
    next();
  });
}

/**
 * This function deletes an entity.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param req the request
 * @param funcCallback the callback to return the result
 */
function deleteEntity(model, referenceModels, options, req, funcCallback) {
  async.waterfall([
    function(callback) {
      _checkExtraParameters(req, callback);
    },
    function(callback) {
      _buildReferenceFilter(referenceModels, req, callback);
    },
    function(filters, callback) {
      // use entity filter IDs if configured
      if (options && options.entityFilterIDs) {
        filters.where = _.omit(filters.where, function(value, key) {
          return options.entityFilterIDs.indexOf(key) === -1;
        });
      }
      _findEntityByFilter(model, filters, req, callback);
    },
    function(filters, entity, callback) {
      entity.destroy().success(function() {
        callback(null, entity);
      })
      .error(function(err) {
        routeHelper.addError(req, err);
        callback(req.error);
      });
    }
  ], function(err, entity) {
    funcCallback(err, entity);
  });
}

/**
 * This function deletes an entity.
 * @param model the entity model
 * @param referenceModels the array of referencing models
 * @param options the controller options
 * @param req the request
 * @param res the response
 * @param next the next function in the chain
 */
function deleteMethod(model, referenceModels, options, req, res, next) {
  deleteEntity(model, referenceModels, options, req, function(err, entity) {
    if (!err) {
      req.data = {
        id: entity.id,
        result: {
          success: true,
          status: 200
        }
      };
    }
    next();
  });
}

exports.allEntities = allEntities;
exports.findEntities = findEntities;
exports.getEntity = getEntity;
exports.createEntity = createEntity;
exports.updateEntity = updateEntity;
exports.deleteEntity = deleteEntity;

/**
 * Build the CRUD controller for a model.
 */
exports.buildController = function(model, referenceModels, options) {
  var controller = {};

  // Get an entity.
  controller.get = function(req, res, next) {
    get(model, referenceModels, options, req, res, next);
  };

  // Create an entity.
  controller.create = function(req, res, next) {
    create(model, referenceModels, options, req, res, next);
  };

  // Update an entity.
  controller.update = function(req, res, next) {
    update(model, referenceModels, options, req, res, next);
  };

  // Retrieve all entities.
  controller.all = function(req, res, next) {
    all(model, referenceModels, options, req, res, next);
  };

  // Delete an entity.
  controller.delete = function(req, res, next) {
    deleteMethod(model, referenceModels, options, req, res, next);
  };

  return controller;
};