diff --git a/lib/index.js b/lib/index.js index 0726210..267163c 100644 --- a/lib/index.js +++ b/lib/index.js @@ -3,17 +3,57 @@ var path = require('path'); var wrench = require('wrench'); var events = require('events'); var mongoose = require('mongoose'); +var extend = require('node.extend'); // Patch mongoose-types bug (#17 and #21) // @link {https://github.com/bnoguchi/mongoose-types/} var bson = require(__dirname + '/../node_modules/mongoose/node_modules/mongodb/node_modules/bson'); mongoose.mongo.BinaryParser = bson.BinaryParser; -var mongooseTypes = require('mongoose-types'); +var mongooseTypes= require('mongoose-types'), + keywordize= require('mongoose-keywordize'), + mongoosastic = require('mongoosastic'); +// Expose mongoose and mongoose's types +exports.mongoose = mongoose; +exports.types = mongoose.SchemaTypes; + exports.init = function(conf) { + +console.log('mongoose version: %s', mongoose.version); + + mongoose.set('debug', conf.debug || false); + var connection = mongoose.createConnection(conf.url); + // Add AMQP stuff to enable models messaging if provided else defaults to console... + var amqpClient= conf.clients.amqp; + if (!amqpClient){ + amqpClient= { + sendMessage: function(routing_key, payload){ + var encoded_payload = JSON.stringify(payload); + console.log('__NO_AMQP_TRANSPORT_DEFINED__%s!%s', routing_key, payload); + } + }; + } + + // Add MAIL stuff to enable email notifications... + var mailClient= conf.clients.mail; + if (!mailClient){ + mailClient= { + sendMailMessage: function(emailFrom, emailTo, subject, tplName, tplVars, cb){ + console.log('__NO_MAIL_TRANSPORT_DEFINED__%s!%s', emailFrom, emailTo); + cb(null,'__NO_MAIL_TRANSPORT_DEFINED__'); + } + }; + } + + // Add mongoose transport to the logging system if needed... + var auditLog= conf.logger; + if (auditLog){ + auditLog.addTransport("mongoose", {connectionString: conf.url, collectionName: 'audit.logs'}); + } + var virtuals = { }; exports.installVirtuals = function(type, builder) { virtuals[type._mmId] = builder; @@ -37,7 +77,6 @@ exports.init = function(conf) { }); } - // Find all of the models (This does not load models, // simply creates a registry with all of the file paths) var models = { }; @@ -74,12 +113,44 @@ exports.init = function(conf) { // Handles circular references var circles = new events.EventEmitter(); + // Load external schema definitions if any... + var schemas = {}, + commonSchema = require(path.join(conf.schemaPath, 'common.js')); + + wrench.readdirSyncRecursive(conf.schemaPath).forEach(function(file) { + + if (file[0] === '.') {return;} + file = file.split('.'); + if (file.length > 1 && file.pop() === 'js') { + file = file.join('.'); + file = path.join(conf.schemaPath, file); + + // schema name for the current model... + var model = path.basename(file); + var schema= require(file); + + schemas[model]= { + path: file, + collection: schema.collection || '', + schema: schema.definition || {}, + funcs: extend(schema.funcs || {}, commonSchema.funcs), + globals: schema.globals || {}, + options: schema.options || {}, + columns: schema.columns || {}, + }; + } + }); + // Creates a new model exports.create = function(name, props) { - props = props || { }; + + // Retrieve schema definition from file if any.... + var schemaDef = schemas[name] || {options: {}}, + _virtuals = {}; + + props = props || { }; + extend(props, schemaDef); - var _virtuals = { }; - // Check for a scheme definition if (props.schema) { // Look for circular references @@ -94,11 +165,11 @@ exports.init = function(conf) { if (typeof def.ref === 'object' && def.ref && def.ref.$circular) { var model = def.ref.$circular; // First, check if the model is already loaded - if (models[model] && typeof models[model] === 'object') { + if (models[model] && typeof models[model] === 'object') { props.schema[key].ref = models[model].schema; } // Otherwise, wait and resolve it later - else { + else { circles.once(model, function(model) { def.ref = model.schema; var update = { }; @@ -134,12 +205,56 @@ exports.init = function(conf) { .set(funcs.set || function() { }); }) } + + Object.keys(props.options).forEach(function(opt){ + + switch (opt.toLowerCase()) { + case "usetimestamps": + if (String(props.options[opt]) === 'true') + props.schema.plugin(mongooseTypes.useTimestamps); + break; + + case "usekeyword": + if (typeof props.options[opt] === 'object') + props.schema.plugin(keywordize, props.options[opt]); + break; + + case "useaudit": + if (auditLog && typeof props.options[opt] === 'object'){ + var pluginFn = auditLog.getPlugin('mongoose', props.options[opt]); // setup occurs here + props.schema.plugin(pluginFn.handler); // .handler is the pluggable function for mongoose in this case + } + break; + + case "useelastic": + if (typeof props.options[opt] === 'object') + props.schema.plugin(mongoosastic, props.options[opt]); + break; + } + }); // Check if we are loading the timestamps plugin - if (props.useTimestamps) { + if (props.hasOwnProperty('useTimestamps') && props.useTimestamps) { props.schema.plugin(mongooseTypes.useTimestamps); } + // Check if we are loading the keywordiez plugin + if (props.useKeyword && typeof props.useKeyword === 'object') { + props.schema.plugin(keywordize, props.useKeyword); + } + + // Check if we are loading the audit-log plugin + if (auditLog && props.useAudit && typeof props.useAudit === 'object') { + + var pluginFn = auditLog.getPlugin('mongoose', props.useAudit); // setup occurs here + props.schema.plugin(pluginFn.handler); // .handler is the pluggable function for mongoose in this case + } + + // Check if we are loading the elasticsearch plugin + if (props.useElastic && typeof props.useElastic === 'object') { + props.schema.plugin(mongoosastic, props.useElastic); + } + // Bind any instance methods to the schema.methods object if (props.methods) { Object.keys(props.methods).forEach(function(i) { @@ -147,30 +262,67 @@ exports.init = function(conf) { }); } + mongoose.Model.paginate = function(q, skipFrom, resultsPerPage, sortCols, selCols, callback){ + var MyModel = this, + query, + pageCount= 0, + callback = callback || function(){}; + + if (skipFrom>0) { + query = MyModel.find(q).skip(skipFrom).limit(resultsPerPage).sort(sortCols).select(selCols); + } + else { + query = MyModel.find(q).limit(resultsPerPage).sort(sortCols).select(selCols); + } + + query.exec(function(error, results) { + if (error) { + callback(error, null, null); + } else { + MyModel.count(q, function(error, count) { + if (error) { + callback(error, null, null); + } else { + pageCount = Math.floor(count / resultsPerPage); + callback(null, count, results); + } + }); + } + }); + } + // Create the mongoose model - var model = connection.model(name, props.schema); + var model = connection.model(name, props.schema, props.collection); + var excludeProps= ['schema', 'collection', 'useTimestamps', 'useKeyword', 'useAudit', 'useElastic', 'methods', 'statics', 'options', 'columns', 'funcs']; - // Copy over all other properties as static model properties - Object.keys(props).forEach(function(i) { - if (i !== 'schema' && i !== 'useTimestamps' && i !== 'methods') { - model[i] = props[i]; + // Copy over all other properties as static model properties + Object.keys(props).forEach(function(key) { + if (excludeProps.indexOf(key)<0) { + model[key] = props[key]; } }); - + + if (props.statics){ + Object.keys(props.statics).forEach(function(key) { + model[key] = props.statics[key]; + }); + } + // Store the model models[name].model = model; // The model is done being built, allow circular reference to resolve - circles.emit(name, model); - + circles.emit(name, model); return model; }; + + // Expose schemas repo in outside world... + exports.schemaRepository = schemas; - // Expose mongoose and mongoose's types - exports.mongoose = mongoose; - exports.types = mongoose.SchemaTypes; + //Expose specific clients in models world... + exports.amqpClient = amqpClient; + exports.mailClient = mailClient; // Don't allow re-init exports.init = undefined; -}; - +}; \ No newline at end of file diff --git a/package.json b/package.json index 3990842..504bf5f 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "repository": { "type": "git", - "url": "git://github.com/SportZing/mongoose-models" + "url": "git://github.com/marco48/mongoose-models" }, "keywords": [ "mongoose", @@ -16,13 +16,13 @@ "db", "model" ], - "author": "James Brumond", + "author": "marco", "license": "MIT", "dependencies": { - "mongoose": "~3.0.0", + "mongoose": "3.8.4", "wrench": "~1.3.9", - "mongoose-types": "~1.0.3", + "mongoose-types": "marco48/mongoose-types", "uuid-v4": "~0.1.0", "colors": "0.6.0-1" } -} +} \ No newline at end of file