kwiniarski/spiral

View on GitHub
lib/resource/index.js

Summary

Maintainability
A
3 hrs
Test Coverage
/**
 * @author Krzysztof Winiarski
 * @copyright (c) 2014 Krzysztof Winiarski
 * @license MIT
 */

'use strict';

var ResourceAction = require('./action')
  , RequestError = require('../errors').RequestError
  , resourceActionBlueprint = require('./action-blueprint')
  , support = require('../support')
  , router = require('express').Router;

/**
 * Resolves mount path from model or controller object.
 * @param model
 * @param controller
 * @returns {*}
 */
function resolveMountPath(model, controller) {
  var mountPath = null;
  if (controller && controller.path) {
    mountPath = controller.path;
  }
  if (model) {
    if (mountPath === null) {
      mountPath = model.path;
    }
    if (mountPath !== model.path) {
      throw new Error('Mount path for model ("' + model.path + '") and controller ("' + controller.path + '") do not match');
    }
  }

  if (mountPath && mountPath.indexOf('/') !== 0) {
    mountPath = '/' + mountPath;
  }

  return mountPath;
}

function Resource(model, controller, policies) {

  var blueprint = {}
    , actions = controller ? controller.actions : null;

  this.id = controller ? controller.name : (model ? model.name : null);

  this.replaced = {};
  this.actions = {};
  this.policies = [];
  this.mountPath = resolveMountPath(model, controller);

  if (this.mountPath === null) {
    throw new Error('Cannot initialize resource without mount path');
  }

  if (model) {
    blueprint = resourceActionBlueprint(model, this.mountPath);
  }

  this
    .registerActions(blueprint)
    .registerActions(actions);

  if (policies) {
    this.registerPolicies(policies);
  }
}

Resource.prototype = Object.create(router());

Resource.prototype.agregateActions = function (actions) {
  var mountPaths = {};

  for (var i in actions) {
    var action = actions[i];
    var mountPath = action.mountPath;

    if (mountPath instanceof Array === false) {
      mountPath = [mountPath];
    }

    for (var m = 0; m < mountPath.length; m++) {
      if (!mountPaths[mountPath[m]]) {
        mountPaths[mountPath[m]] = {
          mountPath: mountPath[m],
          methods: {}
        };
      }

      for (var n = 0, method; !!(method = action.methods[n]); n++) {
        mountPaths[mountPath[m]].methods[method] = action;
      }
    }
  }

  return mountPaths;
}

function respondWithMethodNotAllowedStatus(req, res, next) {
  next(RequestError.MethodNotAllowed());
}

function respondWithNotFoundStatus(req, res, next) {
  next(RequestError.NotFound());
}

/**
 * Configure and return Resource instance as a Router.
 * @returns {Object} Express Router object
 */
Resource.prototype.getRouter = function () {
  var resourceRouter = router()
    , actions = this.agregateActions(this.actions)
    , replaced = this.agregateActions(this.replaced);

  //console.log('  ' + this.mountPath, this.policies);
  for (var mountPathId in actions) {
    var mountPath = actions[mountPathId].mountPath;
    var actionRouter = resourceRouter.route(mountPath);
    var method, action;

    for (method in actions[mountPathId].methods) {
      action = actions[mountPathId].methods[method];
      //console.log('    ' + method + ' <' + (typeof mountPath) + '>' + mountPath + ' ' + action.id);
      actionRouter[method.toLowerCase()](action.policies, action.handler);
    }

    if (replaced[mountPathId]) {
      for (method in replaced[mountPathId].methods) {
        action = replaced[mountPathId].methods[method];
        //console.log('    ' + method + ' <' + (typeof mountPath) + '>' + mountPath + ' ' + action.id + ' [respond with 404]');
        actionRouter[method.toLowerCase()](respondWithNotFoundStatus);
      }
    }

    actionRouter.all(respondWithMethodNotAllowedStatus);
  }

  return (router()).use(this.mountPath, this.policies, resourceRouter);
};

/**
 * Registers actions from given source
 * @param collection
 * @returns {Resource}
 */
Resource.prototype.registerActions = function (collection) {
  for (var actionName in collection) {
    this.registerAction(collection[actionName]);
  }
  return this;
};

/**
 * Registers single action
 * @param factory
 */
Resource.prototype.registerAction = function (factory) {
  try {
    var action = new ResourceAction(factory);

    if (this.actions[action.id]) {
      this.replaced[action.id] = this.actions[action.id];
    }

    this.actions[action.id] = action;
  } catch (error) {
    //console.log(error, factory);
    // TODO decide how to handle startup errors
  }
};

Resource.prototype.getAction = function (actionName) {
  return this.actions[actionName] || null;
};

Resource.prototype.registerPolicies = function (policies) {
  this.policies = policies.get(this.id);

  for (var actionName in this.actions) {
    var action = this.actions[actionName];
    action.registerPolicies(policies.get(this.id, action.id));
  }
};

exports = module.exports = Resource;