fmbaucis
Version:
Build scalable REST APIs using the open source tools and standards you already know.
180 lines (159 loc) • 5.75 kB
JavaScript
// __Dependencies__
var mongoose = require('mongoose');
var semver = require('semver');
var pluralize = require('mongoose/lib/utils').toCollectionName;
var BaucisError = require('../BaucisError');
// __Module Definition__
var decorator = module.exports = function (model, protected) {
var controller = this;
if (typeof model !== 'string' && !model.schema) {
throw BaucisError.Configuration('You must pass in a model or model name');
}
// __Private Instance Members__
var properties = {};
var multi = {
methods: {
get: true,
post: true,
put: true,
del: true
},
operators: {
$push: false,
$pull: false,
$set: false
}
};
// __Protected Instance Members__
var property = protected.property = function (name, initial, action) { // TODO detect if prop already added and throw
// Initialize the property value.
properties[name] = initial;
// Add the property to the controller.
controller[name] = function (value) {
if (arguments.length === 0) return properties[name];
if (action) value = action(value);
properties[name] = value;
controller.validate();
return controller;
};
return controller;
};
var multiproperty = protected.multiproperty = function (name, action) {
// Add the property to the controller.
controller[name] = function (items, cargo) {
var store = multi[name];
// If one argument was passed, returned the value for that item.
if (arguments.length === 1) {
if (items.match(/\s/)) throw new Error();
return store[items];
}
// If two arguments were passed, update the items with the cargo.
else if (arguments.length === 2) {
items.split(/\s+/g).forEach(function (item) {
store[item] = action ? action(cargo) : cargo;
});
}
// Return a list of activated items.
return Object.keys(store).filter(function (item) {
return store[item];
});
};
return controller;
};
// __Property Definitions__
property('findBy', '_id');
property('versions', '*'); // TODO changing after activation is an NOP
property('comments', false);
property('hints', false);
property('locking', false);
property('relations', true);
property('select', '');
property('schema');
property('lastModified');
property('api'); // TODO changing this is an NOP
property('parentPath');
property('model', undefined, function (model) {
var name;
var definition;
// If it's a string, get the model from mongoose.
if (typeof model === 'string') {
name = model;
definition = mongoose.model(name);
}
// Otherwise, assume it's a model object.
else {
definition = model;
name = model.modelName;
}
// If the singular name hasn't been set, set it.
if (!controller.singular()) controller.singular(name);
// Record the new schema.
controller.schema(definition.schema);
return definition;
});
property('singular', undefined, function (name) {
// If the plural name hasn't yet been set, set it to the pluralized version
// of the singular name.
if (!name) return name;
if (!controller.plural()) controller.plural(pluralize(name));
return name;
});
// TODO changing this after activation is a NOP
property('plural', undefined, function (name) {
if (!name) return name;
return name;
});
// TODO changing this after activation is a NOP
property('path', undefined, function (path) {
if (!path) return path;
return '/' + path.replace(/[.]/g, '/');
});
// TODO changing this after release init is an NOP
property('children', [], function (child) {
if (!child) throw BaucisError.Configuration('A child controller must be supplied when using the children poperty');
if (properties.children.indexOf(child) !== -1) return properties.children;
properties.children.push(child);
if (!child.parentPath()) child.parentPath(controller.singular());
child.api(controller.api());
controller.use(child);
return properties.children;
});
multiproperty('operators');
multiproperty('methods', function (enabled) {
return enabled ? true : false;
});
// Check the configuration.
controller.validate = function () {
var findByPath;
if (controller.schema() && controller.findBy() && controller.findBy() !== '_id') {
findByPath = controller.schema().path(controller.findBy());
if (!findByPath.options.unique && !(findByPath.options.index && findByPath.options.index.unique)) {
throw BaucisError.Configuration('`findBy` path for model "%s" must be unique', controller.model().modelName);
}
}
if (!semver.validRange(controller.versions())) {
throw BaucisError.Configuration('Controller version range "%s" was not a valid semver range', controller.versions());
}
return controller;
};
controller.deselected = function (path) {
var deselected = [];
// Store naming, model, and schema.
// Find deselected paths in the schema.
controller.schema().eachPath(function (name, path) {
if (path.options.select === false) deselected.push(name);
});
// Add deselected paths from the controller.
controller.select().split(/\s+/).forEach(function (path) {
var match = /^(?:[-]((?:[\w]|[-])+)\b)$/.exec(path);
if (match) deselected.push(match[1]);
});
var finalized = deselected.filter(function(path, position) {
return deselected.indexOf(path) === position;
});
if (arguments.length === 0) return finalized;
else return (finalized.indexOf(path) !== -1);
};
// Set the controller model.
controller.model(model);
};