From e363b690470efd7c6ec72945f64cf026dd6d2dc1 Mon Sep 17 00:00:00 2001 From: Richard Gustin Date: Thu, 5 Jun 2014 10:00:32 +1000 Subject: [PATCH 1/6] feature(moduleLoader): Added injector instances for backend modules, clever-auth becomes cleverAuth --- lib/utils/moduleLoader.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/utils/moduleLoader.js b/lib/utils/moduleLoader.js index cd1611f..a359b1a 100644 --- a/lib/utils/moduleLoader.js +++ b/lib/utils/moduleLoader.js @@ -6,6 +6,7 @@ var Class = require( 'uberclass' ) , fs = require( 'fs' ) , async = require( 'async' ) , debug = require( 'debug' )( 'moduleLoader' ) + , i = require( 'i' )() , moduleClass; var Module = module.exports = Class.extend( @@ -54,6 +55,9 @@ var Module = module.exports = Class.extend( if ( this.modulesLoaded === false ) { debug( 'Loading modules...' ); + // Get a copy of the module class + moduleClass = require( 'classes' ).ModuleClass; + // Load each of our modules packageJson.bundledDependencies.forEach( this.proxy( 'loadModule', env ) ); @@ -82,12 +86,15 @@ var Module = module.exports = Class.extend( } debug( [ 'Loading the', moduleName, 'module' ].join( ' ' ) ); + var module = require( moduleName ); + + var moduleLowerCamelCase = i.camelize( moduleName.replace( /\-/ig, '_' ), false ); - // Get a copy of the module class - moduleClass = require( 'classes' ).ModuleClass; + debug( [ 'Adding the', moduleLowerCamelCase, 'module to the injector' ].join( ' ' ) ); + injector.instance( moduleLowerCamelCase, module ); - // Load (require) the module and add to our modules array - this.modules.push( require( moduleName ) ); + // Add the module into our modules array so we can keep track of them internally and call hooks in their module.js file + this.modules.push( module ); }, configureAppHook: function( module ) { From 365349b26f3c93da038c2bdb1a7d7a4df8e3a9f6 Mon Sep 17 00:00:00 2001 From: Richard Gustin Date: Thu, 5 Jun 2014 23:53:13 +1000 Subject: [PATCH 2/6] New Model class and first class citizen models --- index.js | 9 ++ lib/classes/Model.js | 183 +++++++++++++++++++++++++++++++++++++ lib/classes/ModuleClass.js | 47 ++++------ 3 files changed, 210 insertions(+), 29 deletions(-) create mode 100644 lib/classes/Model.js diff --git a/index.js b/index.js index f315aed..424f060 100644 --- a/index.js +++ b/index.js @@ -17,6 +17,15 @@ env.app.configure(function() { env.app.use( cors( env.config.cors ) ); }); +// Add some classes for simplicity +var classes = require( 'classes' ); + +injector.instance( 'Model', classes.Model ); +injector.instance( 'Controller', classes.Controller ); +injector.instance( 'EventedController', classes.EventedController ); +injector.instance( 'EventedClass', classes.EventedClass ); +injector.instance( 'ModuleClass', classes.ModuleClass ); + // Load all the modules env.moduleLoader.loadModules(); diff --git a/lib/classes/Model.js b/lib/classes/Model.js new file mode 100644 index 0000000..9a6262a --- /dev/null +++ b/lib/classes/Model.js @@ -0,0 +1,183 @@ +var Class = require( 'uberclass' ) + , debug = require( 'debug' )( 'Models' ) + , moduleLoader = injector.getInstance( 'moduleLoader' ) + , Promise = require( 'bluebird' ) + , async = require( 'async' ); + +module.exports = Class.extend( +/* @Static */ +{ + softDeletable: false, + + versionable: false, + + timeStampable: true, + + type: 'ORM', + + extend: function() { + var extendingArgs = [].slice.call( arguments ) + , modelName = extendingArgs.shift() + , Static = ( extendingArgs.length === 2 ) + ? extendingArgs.shift() + : {} + , Proto = extendingArgs.shift() + , extendingArgs = [ Static, Proto ] + , modelType = Static.type !== undefined + ? Static.type + : this.type + , moduleName = 'clever-' + modelType.toLowerCase() + , driver = null; + + debug( [ 'Defining the', modelName, '(' + modelType + ')', 'model...' ].join( ' ' ) ); + + // Add the name into the Static + Static.name = modelName; + + // Check to see if the module we need is enabled! + if ( moduleLoader.moduleIsEnabled( moduleName ) !== true ) { + throw new Error( [ 'To use type', modelType, 'on your', modelName, 'model you need to enable the', moduleName, 'module!' ].join( ' ' ) ); + } else { + Static._driver = driver = injector.getInstance( modelType.toLowerCase() === 'orm' ? 'cleverOrm' : 'cleverOdm' ); + } + + // Remove the actual properties from the Proto section so we can use __defineGetter__ and __defineSetter__ + Object.keys( Proto ).forEach( this.callback( 'getSchemaFromProto', Proto, Static ) ); + + // Define behaviours in Static for driver module to use + [ 'softDeletable', 'versionable', 'timeStampable' ].forEach(function( behaviour ) { + Static[ behaviour ] = Static[ behaviour ] !== undefined + ? Static[ behaviour ] + : this[ behaviour ]; + }.bind( this )); + + // Add the accessedAt field if we are timeStampable + if ( Static.timeStampable === true ) { + Static._schema.createdAt = Date; + Static._schema.updatedAt = Date; + Static._schema.accessedAt = Date; + } + + // Parse the _schema into an actual model we can use + Static._model = driver.parseModelSchema( Static, Proto ); + + // Call extend on the parent + return this._super.apply( this, extendingArgs ); + }, + + getSchemaFromProto: function( Proto, Static, key ) { + var prop = Proto[ key ]; + + if ( prop === Date || prop === String || prop instanceof String || prop === Number || prop === Boolean || ( typeof prop === 'object' ) ) { + if ( typeof Static._schema !== 'object' ) { + Static._schema = {}; + } + + Static._schema[ key ] = prop; + delete Proto[ key ]; + } + }, + + find: function( id ) { + var that = this + , options = {}; + + if ( !!id ) { + options.id = id; + } + + if ( !!that.softDeletable ) { + options.deletedAt = null; + } + + return new Promise(function( resolve, reject ) { + async.waterfall( + [ + function getModel( callback ) { + that._model + .find( { where: options } ) + .success( that.callback( callback, null ) ) + .error( callback ); + }, + + function updateIfTimestampable( model, callback ) { + if ( !!model && that.timeStampable === true ) { + model.updateAttributes( { accessedAt: Date.now() } ) + .success( that.callback( callback, null ) ) + .error( callback ); + } else { + callback( null, model ); + } + } + ], + function( err, model ) { + !err && !!model ? resolve( new that( model ) ) : reject( !!err ? err : 'Model not found.' ); + } + ); + }); + }, + + findAll: function( options ) { + var that = this; + + if ( !!that.softDeletable ) { + options.deletedAt = null; + } + + return new Promise(function( resolve, reject ) { + async.waterfall( + [ + function getModel( callback ) { + that._model + .findAll( { where: options } ) + .success( that.callback( callback, null ) ) + .error( callback ); + }, + + function updateIfTimestampable( models, callback ) { + if ( that.timeStampable === true ) { + var accessedAt = Date.now(); + + async.each( + models, + function updateModel( model, cb ) { + model.updateAttributes( { accessedAt: accessedAt } ) + .success( that.callback( cb, null ) ) + .error( cb ); + }, + function( err ) { + callback( err ? err : null, models ); + } + ) + } else { + callback( null, models ); + } + } + ], + function( err, _models ) { + var models = []; + + if ( !err ) { + _models.forEach(function( model ) { + models.push( new that( model ) ); + }); + + resolve( models ); + } else { + reject( err ); + } + } + ); + }); + } +}, +/* @Prototype */ +{ + setup: function( model ) { + this._model = model; + }, + + map: function() { + return this._model.map.apply( this, arguments ); + } +}); \ No newline at end of file diff --git a/lib/classes/ModuleClass.js b/lib/classes/ModuleClass.js index db8a3c0..2eea8bc 100644 --- a/lib/classes/ModuleClass.js +++ b/lib/classes/ModuleClass.js @@ -10,14 +10,14 @@ module.exports = Class.extend( moduleFolders: [ 'exceptions', 'classes', - 'models/orm', - 'models/odm', + 'models', 'services', 'controllers', 'tasks' ], injectableFolders: [ + 'models', 'controllers', 'services' ], @@ -101,12 +101,8 @@ module.exports = Class.extend( } } this[ rootFolder ] = obj; - - // Dont add paths for disabled model modules - if ( rootFolder !== 'models' || ( rootFolder === 'models' && moduleLoader.moduleIsEnabled( 'clever-' + currentFolder ) ) ) { - this.paths.push( p ); - injector._inherited.factoriesDirs.push( p ); - } + this.paths.push( p ); + injector._inherited.factoriesDirs.push( p ); }, loadResources: function() { @@ -148,30 +144,23 @@ module.exports = Class.extend( } } - if ( rootFolder === 'models' ) { - // Only include models for enabled modules - if ( moduleLoader.moduleIsEnabled( 'clever-' + currentFolder ) ) { - lastFolder[ name ] = require( 'clever-' + currentFolder ).getModel( [ pathToInspect, '/', file ].join( '' ) ); - } - } else { - // Load the resource - resource = require( [ pathToInspect, '/', file ].join( '' ) ); - - // Allow injection of certain dependencies - if ( typeof resource === 'function' && this.Class.injectableFolders.indexOf( rootFolder ) !== -1 ) { - debug( 'Injecting the ' + name + ' resource.' ); - resource = injector.inject( resource ); - } + // Load the resource + resource = require( [ pathToInspect, '/', file ].join( '' ) ); - // Add the resource to the injector - if ( name !== 'routes' ) { - debug( 'Adding ' + name + ' to the injector' ); - injector.instance( name, resource ); - } + // Allow injection of certain dependencies + if ( typeof resource === 'function' && this.Class.injectableFolders.indexOf( rootFolder ) !== -1 ) { + debug( 'Injecting the ' + name + ' resource.' ); + resource = injector.inject( resource ); + } - // Add the resource to the last object we found - lastFolder[ name ] = resource; + // Add the resource to the injector + if ( name !== 'routes' ) { + debug( 'Adding ' + name + ' to the injector' ); + injector.instance( name, resource ); } + + // Add the resource to the last object we found + lastFolder[ name ] = resource; } }, From 4ffd1b3f39f24dd8c6267c8598ef5b0b989c6472 Mon Sep 17 00:00:00 2001 From: Richard Gustin Date: Fri, 6 Jun 2014 03:21:18 +1000 Subject: [PATCH 3/6] Almost compeleted ORM implementation of FCC Models that work with clever-auth --- index.js | 2 - lib/classes/Model.js | 150 ++++++++++++++++++++++++++++++++----------- lib/models/index.js | 18 +----- 3 files changed, 114 insertions(+), 56 deletions(-) diff --git a/index.js b/index.js index 424f060..51fc82a 100644 --- a/index.js +++ b/index.js @@ -19,10 +19,8 @@ env.app.configure(function() { // Add some classes for simplicity var classes = require( 'classes' ); - injector.instance( 'Model', classes.Model ); injector.instance( 'Controller', classes.Controller ); -injector.instance( 'EventedController', classes.EventedController ); injector.instance( 'EventedClass', classes.EventedClass ); injector.instance( 'ModuleClass', classes.ModuleClass ); diff --git a/lib/classes/Model.js b/lib/classes/Model.js index 9a6262a..bf541b0 100644 --- a/lib/classes/Model.js +++ b/lib/classes/Model.js @@ -2,11 +2,16 @@ var Class = require( 'uberclass' ) , debug = require( 'debug' )( 'Models' ) , moduleLoader = injector.getInstance( 'moduleLoader' ) , Promise = require( 'bluebird' ) - , async = require( 'async' ); + , async = require( 'async' ) + , models = {}; module.exports = Class.extend( /* @Static */ { + getDefinedModels: function() { + return models; + }, + softDeletable: false, versionable: false, @@ -16,6 +21,10 @@ module.exports = Class.extend( type: 'ORM', extend: function() { + if ( models[ arguments[ 0 ] ] !== undefined ) { + return models[ arguments[ 0 ] ]; + } + var extendingArgs = [].slice.call( arguments ) , modelName = extendingArgs.shift() , Static = ( extendingArgs.length === 2 ) @@ -27,7 +36,8 @@ module.exports = Class.extend( ? Static.type : this.type , moduleName = 'clever-' + modelType.toLowerCase() - , driver = null; + , driver = null + , model = null; debug( [ 'Defining the', modelName, '(' + modelType + ')', 'model...' ].join( ' ' ) ); @@ -41,28 +51,47 @@ module.exports = Class.extend( Static._driver = driver = injector.getInstance( modelType.toLowerCase() === 'orm' ? 'cleverOrm' : 'cleverOdm' ); } + // Move the getters from the proto if we have them + if ( Proto.getters !== undefined ) { + Static._getters = Proto.getters; + delete Proto.getters; + } + + // Move the setters from the proto if we have them + if ( Proto.setters !== undefined ) { + Static._setters = Proto.setters; + delete Proto.setters; + } + // Remove the actual properties from the Proto section so we can use __defineGetter__ and __defineSetter__ Object.keys( Proto ).forEach( this.callback( 'getSchemaFromProto', Proto, Static ) ); // Define behaviours in Static for driver module to use [ 'softDeletable', 'versionable', 'timeStampable' ].forEach(function( behaviour ) { + Static[ behaviour ] = Static[ behaviour ] !== undefined ? Static[ behaviour ] : this[ behaviour ]; + }.bind( this )); // Add the accessedAt field if we are timeStampable if ( Static.timeStampable === true ) { Static._schema.createdAt = Date; Static._schema.updatedAt = Date; - Static._schema.accessedAt = Date; } // Parse the _schema into an actual model we can use Static._model = driver.parseModelSchema( Static, Proto ); // Call extend on the parent - return this._super.apply( this, extendingArgs ); + model = this._super.apply( this, extendingArgs ); + + // Keep track of it so we don't redefine the model again + models[ modelName ] = model; + + // Return the fully built model + return model; }, getSchemaFromProto: function( Proto, Static, key ) { @@ -73,16 +102,33 @@ module.exports = Class.extend( Static._schema = {}; } + if ( typeof Static._getters !== 'object' ) { + Static._getters = {}; + } + + if ( typeof Static._setters !== 'object' ) { + Static._setters = {}; + } + Static._schema[ key ] = prop; + Static._getters[ key ] = function() { + return this._model[ key ]; + }; + Static._setters[ key ] = function( val ) { + this._model[ key ] = val; + }; + delete Proto[ key ]; } }, find: function( id ) { var that = this - , options = {}; + , options = isNaN( id ) + ? id + : {}; - if ( !!id ) { + if ( !!id && !isNaN( id ) ) { options.id = id; } @@ -98,25 +144,19 @@ module.exports = Class.extend( .find( { where: options } ) .success( that.callback( callback, null ) ) .error( callback ); - }, - - function updateIfTimestampable( model, callback ) { - if ( !!model && that.timeStampable === true ) { - model.updateAttributes( { accessedAt: Date.now() } ) - .success( that.callback( callback, null ) ) - .error( callback ); - } else { - callback( null, model ); - } } ], function( err, model ) { - !err && !!model ? resolve( new that( model ) ) : reject( !!err ? err : 'Model not found.' ); + !err ? resolve( !!model ? new that( model ) : null ) : reject( err ); } ); }); }, + findById: function( id ) { + return this.find( id ); + }, + findAll: function( options ) { var that = this; @@ -132,26 +172,6 @@ module.exports = Class.extend( .findAll( { where: options } ) .success( that.callback( callback, null ) ) .error( callback ); - }, - - function updateIfTimestampable( models, callback ) { - if ( that.timeStampable === true ) { - var accessedAt = Date.now(); - - async.each( - models, - function updateModel( model, cb ) { - model.updateAttributes( { accessedAt: accessedAt } ) - .success( that.callback( cb, null ) ) - .error( cb ); - }, - function( err ) { - callback( err ? err : null, models ); - } - ) - } else { - callback( null, models ); - } } ], function( err, _models ) { @@ -169,15 +189,69 @@ module.exports = Class.extend( } ); }); + }, + + create: function( data ) { + var that = this; + + return new Promise( function( resolve, reject ) { + that._model + .create( data ) + .success( function( _model ) { + resolve( new that( _model ) ); + }) + .error( reject ); + }); } }, /* @Prototype */ { setup: function( model ) { this._model = model; + Object.keys( this.Class._getters ).forEach( this.proxy( '_setupProperty' ) ); + }, + + _setupProperty: function( propName ) { + Object.defineProperty( this, propName, { + get: this.proxy( this.Class._getters[ propName ] ), + set: this.proxy( this.Class._setters[ propName ] ), + enumerable: true + }); + }, + + _setModel: function( _model ) { + this._model = _model; }, map: function() { return this._model.map.apply( this, arguments ); - } + }, + + save: function() { + var that = this; + + return new Promise( function( resolve, reject ) { + that._model + .save() + .success( function( _model ) { + that._setModel( _model ); + resolve( that ); + }) + .error( reject ); + }); + }, + + destroy: function() { + var that = this; + + return new Promise( function( resolve, reject ) { + that._model + .destroy() + .success( function( _model ) { + delete that._model; + resolve(); + }) + .error( reject ); + }); + }, }); \ No newline at end of file diff --git a/lib/models/index.js b/lib/models/index.js index e875341..e8bde08 100644 --- a/lib/models/index.js +++ b/lib/models/index.js @@ -1,18 +1,4 @@ var path = require( 'path' ) - , dbModules = [ 'orm', 'odm' ] - , moduleLoader = require( 'utils' ).moduleLoader.getInstance( ) - , models = {}; + , Model = require( 'classes' ).Model; -dbModules.forEach( function( type ) { - if ( moduleLoader.moduleIsEnabled( 'clever-' + type ) ) { - models[ type ] = {}; - moduleLoader.modules.forEach( function( theModule ) { - Object.keys( theModule.models[ type ] ).forEach( function( key ) { - models[ type ][ key ] = theModule.models[ type ][ key ]; - models[ type ][ key ][ type.toUpperCase() ] = true; - }); - }); - } -}); - -module.exports = models; \ No newline at end of file +module.exports = Model.getDefinedModels(); \ No newline at end of file From ecb51d12be7e07599a5dc28afbf1fc77ec5572ba Mon Sep 17 00:00:00 2001 From: Richard Gustin Date: Sat, 7 Jun 2014 19:04:23 +1000 Subject: [PATCH 4/6] FCC modules, new service layer, new module layer and all other changes completed --- index.js | 5 +- lib/classes/Class.js | 5 + lib/classes/Controller.js | 47 +-- lib/classes/Model.js | 394 +++++++++++++++++----- lib/classes/{ModuleClass.js => Module.js} | 125 +++++-- lib/classes/Service.js | 229 +++++++++++++ lib/injector/index.js | 1 + lib/services/BaseService.js | 208 ------------ lib/services/index.js | 5 +- lib/utils/bootstrapEnv.js | 2 +- lib/utils/moduleLoader.js | 59 ++-- package.json | 19 +- 12 files changed, 723 insertions(+), 376 deletions(-) create mode 100644 lib/classes/Class.js rename lib/classes/{ModuleClass.js => Module.js} (59%) create mode 100644 lib/classes/Service.js create mode 100644 lib/injector/index.js delete mode 100644 lib/services/BaseService.js diff --git a/index.js b/index.js index 51fc82a..50f53ea 100644 --- a/index.js +++ b/index.js @@ -19,10 +19,11 @@ env.app.configure(function() { // Add some classes for simplicity var classes = require( 'classes' ); +injector.instance( 'Class', classes.Class ); injector.instance( 'Model', classes.Model ); +injector.instance( 'Service', classes.Service ); injector.instance( 'Controller', classes.Controller ); -injector.instance( 'EventedClass', classes.EventedClass ); -injector.instance( 'ModuleClass', classes.ModuleClass ); +injector.instance( 'Module', classes.Module ); // Load all the modules env.moduleLoader.loadModules(); diff --git a/lib/classes/Class.js b/lib/classes/Class.js new file mode 100644 index 0000000..cf92bf2 --- /dev/null +++ b/lib/classes/Class.js @@ -0,0 +1,5 @@ +var Class = require( 'uberclass' ) + , EventEmitter = require( 'events' ).EventEmitter + , uberUtil = require( 'utils' ).uberUtil; + +module.exports = Class.extend( uberUtil.inherits( {}, EventEmitter ) ); \ No newline at end of file diff --git a/lib/classes/Controller.js b/lib/classes/Controller.js index ef4e817..dea6ca8 100644 --- a/lib/classes/Controller.js +++ b/lib/classes/Controller.js @@ -1,4 +1,4 @@ -var Controller = require('clever-controller'); +var Controller = require( 'clever-controller' ); module.exports = Controller.extend( /* @Static */ @@ -9,14 +9,11 @@ module.exports = Controller.extend( { listAction: function() { if ( this.Class.service !== null ) { - var query = this.req.query; - var options = {}; - if ( Object.keys( query ).length ) { - options.where = query; - } - this.Class.service.find( options ) - .then( this.proxy( 'send' ) ) - .fail( this.proxy( 'handleException' ) ); + this.Class + .service + .findAll( this.req.query ) + .then( this.proxy( 'handleServiceMessage' ) ) + .catch( this.proxy( 'handleException' ) ); } else { this.next(); } @@ -24,9 +21,11 @@ module.exports = Controller.extend( getAction: function() { if ( this.Class.service !== null ) { - this.Class.service.findById( this.req.params.id ) - .then( this.proxy( 'send' ) ) - .fail( this.proxy( 'handleException' ) ); + this.Class + .service + .findById( this.req.params.id ) + .then( this.proxy( 'handleServiceMessage' ) ) + .catch( this.proxy( 'handleException' ) ); } else { this.next(); } @@ -34,9 +33,11 @@ module.exports = Controller.extend( postAction: function() { if ( this.Class.service !== null ) { - this.Class.service.create( this.req.body ) - .then( this.proxy( 'send' ) ) - .fail( this.proxy( 'handleException' ) ); + this.Class + .service + .create( this.req.body ) + .then( this.proxy( 'handleServiceMessage' ) ) + .catch( this.proxy( 'handleException' ) ); } else { this.next(); } @@ -44,9 +45,11 @@ module.exports = Controller.extend( putAction: function() { if ( this.Class.service !== null ) { - this.Class.service.update( this.req.params.id, this.req.body ) - .then( this.proxy( 'send' ) ) - .fail( this.proxy( 'handleException' ) ); + this.Class + .service + .update( this.req.params.id, this.req.body ) + .then( this.proxy( 'handleServiceMessage' ) ) + .catch( this.proxy( 'handleException' ) ); } else { this.next(); } @@ -54,9 +57,11 @@ module.exports = Controller.extend( deleteAction: function() { if ( this.Class.service !== null ) { - this.Class.service.destroy( this.req.params.id ) - .then( this.proxy( 'send' ) ) - .fail( this.proxy( 'handleException' ) ); + this.Class + .service + .destroy( this.req.params.id ) + .then( this.proxy( 'handleServiceMessage' ) ) + .catch( this.proxy( 'handleException' ) ); } else { this.next(); } diff --git a/lib/classes/Model.js b/lib/classes/Model.js index bf541b0..8f1ee18 100644 --- a/lib/classes/Model.js +++ b/lib/classes/Model.js @@ -1,27 +1,31 @@ -var Class = require( 'uberclass' ) - , debug = require( 'debug' )( 'Models' ) - , moduleLoader = injector.getInstance( 'moduleLoader' ) - , Promise = require( 'bluebird' ) - , async = require( 'async' ) - , models = {}; +var Class = require( 'uberclass' ) + , Promise = require( 'bluebird' ) + , async = require( 'async' ) + , debuggr = require( 'debug' )( 'Models' ) + , injector = require( 'injector' ) + , moduleLdr = injector.getInstance( 'moduleLoader' ) + , models = {}; module.exports = Class.extend( /* @Static */ -{ +{ + // Either 'ORM' or 'ODM' + type: 'ORM', + + // Get the model cache getDefinedModels: function() { return models; }, + // Behaviours you can use on models you define softDeletable: false, - versionable: false, - timeStampable: true, - type: 'ORM', - + // The function you call to create a new model extend: function() { if ( models[ arguments[ 0 ] ] !== undefined ) { + debuggr( arguments[ 0 ] + 'Model: Returning model class from the cache...' ); return models[ arguments[ 0 ] ]; } @@ -39,34 +43,47 @@ module.exports = Class.extend( , driver = null , model = null; - debug( [ 'Defining the', modelName, '(' + modelType + ')', 'model...' ].join( ' ' ) ); + // Make sure no one can override our extend function + if ( Static.extend ) { + delete Static.extend; + } + + debuggr( [ modelName + 'Model: Defining model using', modelType, 'type...' ].join( ' ' ) ); - // Add the name into the Static - Static.name = modelName; + Static._name = modelName; + Static.type = modelType; - // Check to see if the module we need is enabled! - if ( moduleLoader.moduleIsEnabled( moduleName ) !== true ) { + debuggr( modelName + 'Model: Checking to see if the driver is installed and enabled...' ); + if ( moduleLdr.moduleIsEnabled( moduleName ) !== true ) { throw new Error( [ 'To use type', modelType, 'on your', modelName, 'model you need to enable the', moduleName, 'module!' ].join( ' ' ) ); } else { Static._driver = driver = injector.getInstance( modelType.toLowerCase() === 'orm' ? 'cleverOrm' : 'cleverOdm' ); + var debug = function( msg ) { + debuggr( modelName + 'Model: ' + msg ); + }; } - // Move the getters from the proto if we have them + debug( 'Defining models this.debug() helper...' ); + Proto.debug = Static.debug = function( msg ) { + driver.debug( modelName + 'Model: ' + msg ); + }; + + debug( 'Checking for defined getters and setters...' ); + if ( Proto.getters !== undefined ) { Static._getters = Proto.getters; delete Proto.getters; } - // Move the setters from the proto if we have them if ( Proto.setters !== undefined ) { Static._setters = Proto.setters; delete Proto.setters; } - // Remove the actual properties from the Proto section so we can use __defineGetter__ and __defineSetter__ + debug( 'Defining schema...' ); Object.keys( Proto ).forEach( this.callback( 'getSchemaFromProto', Proto, Static ) ); - // Define behaviours in Static for driver module to use + debug( 'Setting up behaviours...' ); [ 'softDeletable', 'versionable', 'timeStampable' ].forEach(function( behaviour ) { Static[ behaviour ] = Static[ behaviour ] !== undefined @@ -77,23 +94,35 @@ module.exports = Class.extend( // Add the accessedAt field if we are timeStampable if ( Static.timeStampable === true ) { + debug( 'Defining timeStampable behaviour schema fields...' ); + Static._schema.createdAt = Date; Static._schema.updatedAt = Date; } - // Parse the _schema into an actual model we can use + // Add the deletedAt field if we are softDeletable + if ( !!Static.softDeletable ) { + debug( 'Defining softDeletable behaviour schema fields...' ); + + if ( modelType.toLowerCase() === 'odm' ) { + Static._schema.deletedAt = { + type: Date, + default: null + }; + } + } + + debug( 'Generating native model using driver.parseModelSchema()...' ); Static._model = driver.parseModelSchema( Static, Proto ); - // Call extend on the parent + debug( 'Creating model class...' ); model = this._super.apply( this, extendingArgs ); - // Keep track of it so we don't redefine the model again models[ modelName ] = model; - - // Return the fully built model return model; }, + // Private function used to build _schema so it can be passed to the _driver for schema creation getSchemaFromProto: function( Proto, Static, key ) { var prop = Proto[ key ]; @@ -112,10 +141,14 @@ module.exports = Class.extend( Static._schema[ key ] = prop; Static._getters[ key ] = function() { - return this._model[ key ]; + if ( key === 'id' && Static.type.toLowerCase() === 'odm' ) { + return this._model._id; + } else { + return this._model[ key ]; + } }; Static._setters[ key ] = function( val ) { - this._model[ key ] = val; + this._model[ key ] = val; }; delete Proto[ key ]; @@ -123,31 +156,75 @@ module.exports = Class.extend( }, find: function( id ) { - var that = this - , options = isNaN( id ) + var modelType = this.type.toUpperCase + ? this.type.toUpperCase() + : this.type + , isModel = !!this._model && this._model !== null + , that = this + , options = !/^[0-9a-fA-F]{24}$/.test( id ) && isNaN( id ) ? id : {}; - if ( !!id && !isNaN( id ) ) { - options.id = id; - } + return new Promise(function( resolve, reject ) { - if ( !!that.softDeletable ) { - options.deletedAt = null; - } + // Configure options + if ( !!id && ( /^[0-9a-fA-F]{24}$/.test( id ) || !isNaN( id ) ) ) { + if ( modelType === 'ODM' ) { + try { + options._id = that._driver.mongoose.Types.ObjectId.fromString( id ); + } catch( err ) { + resolve( null ); + return; + } + } else { + options.id = id; + } + } + + // Make sure we have either an id or options to find by models with + if ( !!isModel && !id && !options ) { + reject( [ 'You must specify either an id or an object containing fields to find a', that._name ].join( ' ' ) ); + return; + } - return new Promise(function( resolve, reject ) { async.waterfall( [ - function getModel( callback ) { - that._model - .find( { where: options } ) - .success( that.callback( callback, null ) ) - .error( callback ); + function validateModel( callback ) { + callback( !!isModel ? null : 'You cannot call Model.find() directly on the model class.' ); + }, + + function softDeletable( callback ) { + if ( !!that.softDeletable ) { + options.deletedAt = null; + } + callback( null ); + }, + + function findModel( callback ) { + that.debug( 'find(' + ( typeof options === 'object' ? JSON.stringify( options ) : options ) + ')' ); + + if ( modelType === 'ORM' ) { + + that._model + .find( { where: options } ) + .success( that.callback( callback, null ) ) + .error( callback ); + + } else if ( modelType === 'ODM' ) { + + that._model.findOne( options, callback ); + + } else { + + callback( 'Unsupported Model Type(' + modelType + ')' ); + + } } ], - function( err, model ) { - !err ? resolve( !!model ? new that( model ) : null ) : reject( err ); + function returnFoundModel( err, _model ) { + !!err + ? reject( [ 'Unable to find the', that._name, 'model because of', err ].join( ' ' ) ) + : resolve( !!_model && _model !== null ? new that( _model ) : null ); } ); }); @@ -157,34 +234,68 @@ module.exports = Class.extend( return this.find( id ); }, - findAll: function( options ) { - var that = this; + findOne: function( id ) { + return this.find( id ); + }, - if ( !!that.softDeletable ) { - options.deletedAt = null; - } + findAll: function( options ) { + var modelType = this.type.toUpperCase + ? this.type.toUpperCase() + : this.type + , isModel = !!this._model && this._model !== null + , that = this; return new Promise(function( resolve, reject ) { async.waterfall( [ - function getModel( callback ) { - that._model - .findAll( { where: options } ) - .success( that.callback( callback, null ) ) - .error( callback ); + function validateModel( callback ) { + callback( !!isModel ? null : 'You cannot call Model.findAll() directly on the model class.' ); + }, + + function softDeletable( callback ) { + if ( !!that.softDeletable ) { + options.deletedAt = null; + } + callback( null ); + }, + + function findModels( callback ) { + that.debug( 'findAll(' + ( typeof options === 'object' ? JSON.stringify( options ) : options ) + ')' ); + + if ( modelType === 'ORM' ) { + + that._model + .find( { where: options } ) + .success( that.callback( callback, null ) ) + .error( callback ); + + } else if ( modelType === 'ODM' ) { + + that._model.find( options, callback ); + + } else { + + callback( 'Unsupported Model Type(' + modelType + ')' ); + + } } ], - function( err, _models ) { - var models = []; + function returnFoundModels( err, _models ) { + var models = [] + , _models = _models instanceof Array + ? _models + : [ _models ]; if ( !err ) { _models.forEach(function( model ) { - models.push( new that( model ) ); + if ( model !== null ) { + models.push( new that( model ) ); + } }); resolve( models ); } else { - reject( err ); + reject( [ 'Unable to find any', that._name, 'models because of', err ].join( ' ' ) ); } } ); @@ -192,22 +303,61 @@ module.exports = Class.extend( }, create: function( data ) { - var that = this; + var modelType = this.type.toUpperCase + ? this.type.toUpperCase() + : this.type + , isModel = !!this._model && this._model !== null + , that = this; - return new Promise( function( resolve, reject ) { - that._model - .create( data ) - .success( function( _model ) { - resolve( new that( _model ) ); - }) - .error( reject ); + return new Promise(function( resolve, reject ) { + async.waterfall( + [ + function validateModel( callback ) { + callback( !!isModel ? null : 'You cannot call Model.create() directly on the model class.' ); + }, + + function timeStampable( callback ) { + if ( modelType === 'ODM' && !!that.timeStampable ) { + data.createdAt = Date.now(); + data.updatedAt = Date.now(); + } + callback( null ); + }, + + function createModel( callback ) { + that.debug( 'create(' + JSON.stringify( data ) + ')' ); + + if ( modelType === 'ORM' ) { + + that._model + .create( data ) + .success( that.callback( callback, null ) ) + .error( callback ) + + } else if ( modelType === 'ODM' ) { + + that._model.create( data, callback ); + + } else { + + callback( 'Unsupported Model Type(' + modelType + ')' ); + + } + } + ], + function returnCreatedModel( err, _model ) { + !!err + ? reject( [ 'Unable to create', that._name, 'because of', err ].join( ' ' ) ) + : resolve( new that( _model instanceof Array ? _model[ 0 ] : _model ) ); + } + ); }); } }, /* @Prototype */ { setup: function( model ) { - this._model = model; + this._setModel( model ); Object.keys( this.Class._getters ).forEach( this.proxy( '_setupProperty' ) ); }, @@ -230,28 +380,112 @@ module.exports = Class.extend( save: function() { var that = this; + this.debug( 'save(' + JSON.stringify( this ) + ')' ); + return new Promise( function( resolve, reject ) { - that._model - .save() - .success( function( _model ) { - that._setModel( _model ); - resolve( that ); - }) - .error( reject ); + if ( that.Class.type.toLowerCase() === 'orm' ) { + + that._model + .save() + .success( function( _model ) { + that._setModel( _model ); + resolve( that ); + }) + .error( reject ) + + } else if ( that.Class.type.toLowerCase() === 'odm' ) { + + if ( !!that.Class.timeStampable ) { + that._model.updatedAt = Date.now(); + } + + that._model + .save( function( err, _model ) { + if ( !err ) { + that._setModel( _model ); + resolve( that ); + } else { + reject( err ); + } + }); + + } else { + reject( 'Unsupported Model Type' ); + } }); }, destroy: function() { var that = this; + this.debug( 'destroy(' + JSON.stringify( this ) + ')' ); + return new Promise( function( resolve, reject ) { - that._model - .destroy() - .success( function( _model ) { - delete that._model; - resolve(); - }) - .error( reject ); + if ( that.Class.type.toLowerCase() === 'orm' ) { + + that._model + .destroy() + .success( function( _model ) { + delete that._model; + resolve( {} ); + }) + .error( reject ) + + } else if ( that.Class.type.toLowerCase() === 'odm' ) { + + if ( !!that.Class.softDeletable ) { + // Perform softDelete + that._model.deletedAt = Date.now(); + that._model + .save( function( err, _model ) { + if ( !err ) { + delete that._model; + resolve( {} ); + } else { + reject( err ); + } + }); + + } else { + that._model.remove(function( err ) { + if ( !err ) { + delete that._model; + resolve( {} ); + } else { + reject( err ); + } + }); + } + + } else { + reject( 'Unsupported Model Type' ); + } }); }, + + toJSON: function() { + var that = this + , json; + + if ( this.Class.type === 'ORM' ) { + json = this._model.values; + } else { + json = this._model.toObject(); + + // Add in the id if we have it defined + if ( !!json._id ) { + json.id = json._id; + delete json._id; + } + } + + // Add in getters + Object.keys( this.Class._getters ).forEach( function( getterName ) { + if ( json[ getterName ] === undefined ) { + json[ getterName ] = that[ getterName ]; + } + }); + + return json; + } }); \ No newline at end of file diff --git a/lib/classes/ModuleClass.js b/lib/classes/Module.js similarity index 59% rename from lib/classes/ModuleClass.js rename to lib/classes/Module.js index 2eea8bc..f57fc41 100644 --- a/lib/classes/ModuleClass.js +++ b/lib/classes/Module.js @@ -1,11 +1,14 @@ -var Class = require( 'uberclass' ) - , path = require( 'path' ) - , fs = require( 'fs' ) - , debug = require( 'debug' )( 'Modules' ) - , config = injector.getInstance( 'config' ) - , moduleLoader = injector.getInstance( 'moduleLoader' ); - -module.exports = Class.extend( +var Class = require( 'uberclass' ) + , path = require( 'path' ) + , fs = require( 'fs' ) + , injector = require( 'injector' ) + , i = require( 'i' )() + , moduleDebug = require('debug')( 'Modules' ) + , config = injector.getInstance( 'config' ) + , modules = {} + , Module; + +Module = Class.extend( { moduleFolders: [ 'exceptions', @@ -32,29 +35,34 @@ module.exports = Class.extend( { name: null, - paths: null, - config: null, - setup: function( name, injector ) { - debug( 'setup called for module ' + name ); - - // Set our module name - this.name = name; + path: null, - // Allow some code to be executed before the main setup - this.hook( 'preSetup' ); + pkg: null, + + paths: null, + + setup: function( _name, _path, _pkg ) { + // Set our module name + this.name = _name; // Set our config if there is any - this.config = typeof config[ name ] === 'object' - ? config[ name ] + this.config = typeof config[ _name ] === 'object' + ? config[ _name ] : {}; // Set the modules location - this.modulePath = [ path.dirname( path.dirname( __dirname ) ), 'modules', this.name ].join( path.sep ); + this.path = _path; - // Add the modulePath to our list of paths - this.paths = [ this.modulePath ]; + // Set the modules package.json + this.pkg = _pkg; + + // Allow some code to be executed before the main setup + this.hook( 'preSetup' ); + + // Add the modules path to our list of paths + this.paths = [ _path ]; // Add our moduleFolders to the list of paths, and our injector paths this.Class.moduleFolders.forEach( this.proxy( 'addFolderToPath', injector ) ); @@ -65,13 +73,15 @@ module.exports = Class.extend( hook: function( hookName ) { if ( typeof this[ hookName ] === 'function' ) { - debug( hookName + ' hook called for module ' + this.name ); - this[ hookName ]( injector ); + this.debug( 'calling ' + hookName + '() hook...' ); + + // @TODO implement injector.injectSync() for use cases like this + this[ hookName ](); } }, addFolderToPath: function( injector, folder ) { - var p = [ this.modulePath, folder ].join( path.sep ) + var p = [ this.path, folder ].join( path.sep ) , obj = {} , folders = p.split( '/' ) , currentFolder = null @@ -149,13 +159,13 @@ module.exports = Class.extend( // Allow injection of certain dependencies if ( typeof resource === 'function' && this.Class.injectableFolders.indexOf( rootFolder ) !== -1 ) { - debug( 'Injecting the ' + name + ' resource.' ); + this.debug( 'Injecting the ' + name + ' resource.' ); resource = injector.inject( resource ); } // Add the resource to the injector if ( name !== 'routes' ) { - debug( 'Adding ' + name + ' to the injector' ); + this.debug( 'Adding ' + name + ' to the injector' ); injector.instance( name, resource ); } @@ -166,8 +176,67 @@ module.exports = Class.extend( initRoutes: function() { if ( typeof this.routes === 'function' ) { - debug( 'initRoutes for module ' + this.name ); + this.debug( 'calling initRoutes() hook...' ); injector.inject( this.routes ); } } }); + +module.exports = { + Class: Module, + extend: function() { + var Reg = new RegExp( '\\)?.*\\(([^\\[\\:]+).*\\)', 'ig' ) + , stack = new Error().stack.split( '\n' ); + + // Get rid of the Error at the start + stack.shift(); + + if ( Reg.test( stack[ 1 ] ) ) { + var modulePath = RegExp.$1.split( path.sep ) + , modulePath = modulePath.splice( 0, modulePath.length - 1 ).join( path.sep ) + , moduleName = path.basename( modulePath ); + } else { + throw new Error( 'Error loading module, unable to determine modules location and name.' ); + } + + var extendingArgs = [].slice.call( arguments ) + , Static = ( extendingArgs.length === 2 ) + ? extendingArgs.shift() + : {} + , Proto = extendingArgs.shift() + , extendingArgs = [ Static, Proto ] + , pkg = [ modulePath, 'package.json' ]; + + if ( modules[ moduleName ] !== undefined ) { + moduleDebug( 'Returning previously defined module ' + moduleName + '...' ); + return modules[ moduleName ]; + } + + moduleDebug( 'Setting up ' + moduleName + ' module from path ' + modulePath + '...' ); + if ( Static.extend ) { + moduleDebug( 'You cannot override the extend() function provided by the CleverStack Module Class!' ); + delete Static.extend; + } + + if ( fs.existsSync( pkg ) ) { + moduleDebug( 'Loading ' + pkg + '...' ); + pkg = require( pkg ); + } else { + pkg = false; + } + + Proto._camelName = i.camelize( moduleName.replace( /\-/ig, '_' ), false ); + moduleDebug( 'Creating debugger with name ' + Proto._camelName + '...' ); + Proto.debug = require( 'debug' )( Proto._camelName ); + + moduleDebug( 'Creating module class...' ); + var Klass = Module.extend( Static, Proto ); + + modules[ moduleName ] = Klass; + + moduleDebug( 'Creating instance of module class...' ); + var instance = new Klass( moduleName, modulePath, pkg ); + + return instance; + } +} \ No newline at end of file diff --git a/lib/classes/Service.js b/lib/classes/Service.js new file mode 100644 index 0000000..253aca4 --- /dev/null +++ b/lib/classes/Service.js @@ -0,0 +1,229 @@ +var Class = require( 'uberclass' ) + , Promise = require( 'bluebird' ) + , path = require( 'path' ) + , util = require( 'util' ) + , injector = require( 'injector' ) + , debug = require( 'debug' )( 'Services' ) + , Model = injector.getInstance( 'Model' ) + , services = []; + +module.exports = Class.extend( +/** @Static **/ +{ + model: null, + + db: null, + + getDefinedServices: function() { + return services; + }, + + extend: function() { + var Reg = new RegExp( '\\)?.*\\(([^\\[\\:]+).*\\)', 'ig' ) + , stack = new Error().stack.split( '\n' ); + + // Get rid of the Error at the start + stack.shift(); + + // Use regular expression to get the name of this service + if ( Reg.test( stack[ 2 ] ) ) { + var serviceName = RegExp.$1.split( path.sep ).pop().replace( '.js', '' ); + } else { + throw new Error( 'Unable to determine services location and name.' ); + } + + var extendingArgs = [].slice.call( arguments ) + , Static = ( extendingArgs.length === 2 ) + ? extendingArgs.shift() + : {} + , Proto = extendingArgs.shift() + , extendingArgs = [ Static, Proto ]; + + if ( services[ serviceName ] !== undefined ) { + debug( 'Returning previously defined service ' + serviceName + '...' ); + return services[ serviceName ]; + } + + debug( 'Setting up ' + serviceName + '...' ); + + // Set the name of this service + Proto._name = Static._name = serviceName; + + if ( Static.extend ) { + debug( 'You cannot override the extend() function provided by the CleverStack Module Class!' ); + delete Static.extend; + } + + if ( !!Proto.model ) { + if ( Proto.model.extend === Model.extend ) { + debug( 'Using the ' + Proto.model._name + ' model for default (restful) CRUD on this service...' ); + + Proto.db = Proto.model._db; + Static.db = Proto.db; + Static.model = Proto.model; + + } else { + debug( util.inspect( Proto ) ); + throw new Error( 'Unknown model type passed to Service.extend(), set environment variable DEBUG=Services for more information.' ); + } + } else if ( !!Proto.db ) { + debug( 'Setting db adapter for service...' ); + Static.db = Proto.db; + } + + debug( 'Creating service class...' ); + var service = this._super.apply( this, extendingArgs ); + + debug( 'Creating instance of service class...' ); + var instance = new service(); + + debug( 'Caching...' ); + services[ serviceName ] = instance; + + debug( 'Completed.' ); + return instance; + } +}, +/** @Prototype */ +{ + db: false, + + model: false, + + // Currently only supports Sequelize + query: function( query ) { + this.db.query( sql, null, { raw: true } ); + }, + + // Create a new model + create: function( data ) { + var service = this; + + return new Promise( function( resolve, reject ) { + if ( !service.model ) { + reject( 'Model not found, either set ' + service._name + '.model or implement ' + service._name + '.find()' ); + return; + } + + if ( !!data.id ) { + resolve( { statuscode: 400, message: 'Unable to create a new ' + service.model._name + ', identity already exists.' } ); + } + + service.model + .create( data ) + .then( resolve ) + .catch( reject ); + }); + }, + + // Find one record using either id or a where {} + find: function( idOrWhere ) { + var service = this; + + return new Promise( function( resolve, reject ) { + if ( !service.model ) { + reject( 'Model not found, either set ' + service._name + '.model or implement ' + service._name + '.find()' ); + return; + } + + service.model + .find( idOrWhere ) + + .then( function( model ) { + if ( !!model && !!model.id ) { + resolve( model ); + } else { + resolve( { statuscode: 403, message: service.model._name + " doesn't exist." } ); + } + }) + .catch( reject ); + + }); + }, + + // Find more than one record using using a where {} + findAll: function( where ) { + var service = this; + + return new Promise( function( resolve, reject ) { + if ( !service.model ) { + reject( 'Model not found, either set ' + service._name + '.model or implement ' + service._name + '.find()' ); + return; + } + + service.model + .findAll( where ) + .then( function( models ) { + resolve( models ); + }) + .catch( reject ); + + }); + }, + + // Find one record and update it using either id or a where {} + update: function( idOrWhere, data ) { + var service = this; + + return new Promise( function( resolve, reject ) { + if ( !service.model ) { + reject( 'Model not found, either set ' + service._name + '.model or implement ' + service._name + '.find()' ); + return; + } + + if ( !idOrWhere || idOrWhere === null ) { + resolve( { statuscode: 400, message: 'Unable to update ' + service.model._name + ', unable to determine identity.' } ); + } + + service.model + .find( idOrWhere ) + .then( function( user ) { + if ( !!user && !!user.id ) { + + data.forEach(function( i ) { + user[ i ] = data[ i ]; + }); + + user.save() + .then( resolve ) + .catch( reject ); + + } else { + resolve( { statuscode: 403, message: service.model._name + " doesn't exist." } ); + } + }) + .catch( reject ); + + }); + }, + + // Find one record and delete it using either id or a where {} + destroy: function( idOrWhere ) { + var service = this; + + return new Promise( function( resolve, reject ) { + if ( !service.model ) { + reject( 'Model not found, either set ' + service._name + '.model or implement ' + service._name + '.find()' ); + return; + } + + if ( !idOrWhere || idOrWhere === null ) { + resolve( { statuscode: 400, message: 'Unable to delete ' + service.model._name + ', unable to determine identity.' } ); + } + + service.model + .find( idOrWhere ) + .then( function( user ) { + if ( !!user && !!user.id ) { + user.destroy() + .then( resolve ) + .catch( reject ) + } else { + resolve( { statuscode: 403, message: service.model._name + " doesn't exist." } ); + } + }) + .catch( reject ); + + }); + } +}); \ No newline at end of file diff --git a/lib/injector/index.js b/lib/injector/index.js new file mode 100644 index 0000000..6c72449 --- /dev/null +++ b/lib/injector/index.js @@ -0,0 +1 @@ +module.exports = require( 'clever-injector' )(); \ No newline at end of file diff --git a/lib/services/BaseService.js b/lib/services/BaseService.js deleted file mode 100644 index 19c1e08..0000000 --- a/lib/services/BaseService.js +++ /dev/null @@ -1,208 +0,0 @@ -var Class = require('uberclass') - , Q = require('q'); - -module.exports = Class.extend({ - instance: null, - Model: null -}, { - db: null, - - setup: function(dbAdapter) { - this.db = dbAdapter; - }, - - startTransaction: function() { - return this.db.startTransaction(); - }, - - query: function(sql) { - console.log('Running SQL: ' + sql); - return this.db.query(sql, null, { raw: true }); - }, - - findById: function (id) { - var deferred = Q.defer(); - - if (this.Class.Model !== null) { - if( this.Class.Model.ORM ){ - this.Class.Model.find(id).success(deferred.resolve).error(deferred.reject); - } else { - this.Class.Model.findById(id, function(err, result){ - if ( err ) { - process.nextTick(function() { - deferred.reject(); - }); - } else { - process.nextTick(function() { - deferred.resolve(result); - }); - } - }); - } - } else { - process.nextTick(function() { - deferred.reject('Function not defined and no Model provided'); - }); - } - - return deferred.promise; - }, - - findAll: function (options) { - options = options || {}; - var deferred = Q.defer(); - - if (this.Class.Model !== null) { - if ( this.Class.Model.ORM ) { - this.Class.Model.findAll().success(deferred.resolve).error(deferred.reject); - } else { - this.Class.Model.find(function(err, result){ - if ( err ) { - process.nextTick(function() { - deferred.reject(); - }); - } else { - process.nextTick(function() { - deferred.resolve(result); - }); - } - }); - } - - } else { - process.nextTick(function() { - deferred.reject('Function not defined and no Model provided.'); - }); - } - - return deferred.promise; - }, - - find: function (options) { - options = options || {}; - var deferred = Q.defer(); - - if (this.Class.Model !== null) { - if ( this.Class.Model.ORM ) { - this.Class.Model.findAll(options).success(deferred.resolve).error(deferred.reject); - } else { - this.Class.Model.find(options, function(err, result){ - if ( err ) { - process.nextTick(function() { - deferred.reject(); - }); - } else { - process.nextTick(function() { - deferred.resolve(result); - }); - } - }); - } - } else { - process.nextTick(function() { - deferred.reject('Function not defined and no Model provided.'); - }); - } - - return deferred.promise; - }, - - create: function (data) { - var deferred = Q.defer(); - - if (this.Class.Model !== null) { - if ( this.Class.Model.ORM ) { - this.Class.Model.create(data) - .success(deferred.resolve) - .error(deferred.reject); - } else { - new this.Class.Model(data).save(function(err, result){ - if ( err ) { - process.nextTick(function() { - deferred.reject(); - }); - } else { - process.nextTick(function() { - deferred.resolve(result); - }); - } - }); - } - - } else { - process.nextTick(function() { - deferred.reject('Function not defined and no Model provided.'); - }); - } - - return deferred.promise; - }, - - update: function (id, data) { - var deferred = Q.defer(); - - if (this.Class.Model !== null) { - if ( this.Class.Model.ORM ) { - this.Class.Model.find(id) - .success(function ( model ) { - model.updateAttributes(data) - .success(deferred.resolve) - .error(deferred.reject); - }) - .error(deferred.reject); - } else { - this.Class.Model.findOneAndUpdate({_id: id}, data, function(err, result){ - if ( err ) { - process.nextTick(function() { - deferred.reject(); - }); - } else { - process.nextTick(function() { - deferred.resolve(result); - }); - } - }); - } - } else { - process.nextTick(function() { - deferred.reject('Function not defined and no Model provided.'); - }); - } - - return deferred.promise; - }, - - destroy: function (id) { - var deferred = Q.defer(); - - if (this.Class.Model !== null) { - if ( this.Class.Model.ORM ) { - this.Class.Model.find(id) - .success(function ( model ) { - model.destroy() - .success(deferred.resolve) - .error(deferred.reject); - }) - .error(deferred.reject); - } else { - this.Class.Model.findById(id).remove(function(err, result){ - if ( err ) { - process.nextTick(function() { - deferred.reject(); - }); - } else { - process.nextTick(function() { - deferred.resolve(result); - }); - } - }); - } - } else { - process.nextTick(function() { - deferred.reject('Function not defined and no Model provided.'); - }); - } - - return deferred.promise; - } -}); \ No newline at end of file diff --git a/lib/services/index.js b/lib/services/index.js index 50eb4bb..5e24d0d 100644 --- a/lib/services/index.js +++ b/lib/services/index.js @@ -1 +1,4 @@ -module.exports = require( 'utils' ).magicModule( 'services' ); \ No newline at end of file +var path = require( 'path' ) + , Model = require( 'classes' ).Service; + +module.exports = Model.getDefinedServices(); \ No newline at end of file diff --git a/lib/utils/bootstrapEnv.js b/lib/utils/bootstrapEnv.js index bbfd61a..85e623a 100644 --- a/lib/utils/bootstrapEnv.js +++ b/lib/utils/bootstrapEnv.js @@ -14,7 +14,7 @@ module.exports = function( env ) { , bootstrappedEnv; // Bootstrap our DI - GLOBAL.injector = require( 'clever-injector' )(); + GLOBAL.injector = require( 'injector' ); injector.instance( 'express', express ); injector.instance( 'app', app ); diff --git a/lib/utils/moduleLoader.js b/lib/utils/moduleLoader.js index a359b1a..040f973 100644 --- a/lib/utils/moduleLoader.js +++ b/lib/utils/moduleLoader.js @@ -1,15 +1,16 @@ 'use strict'; -var Class = require( 'uberclass' ) - , path = require( 'path' ) +var Class = require( 'uberclass' ) + , path = require( 'path' ) , packageJson = require( path.resolve( __dirname + '/../../' ) + '/package.json' ) - , fs = require( 'fs' ) - , async = require( 'async' ) - , debug = require( 'debug' )( 'moduleLoader' ) - , i = require( 'i' )() - , moduleClass; - -var Module = module.exports = Class.extend( + , fs = require( 'fs' ) + , async = require( 'async' ) + , debug = require( 'debug' )( 'ModuleLoader' ) + , i = require( 'i' )() + , injector = require( 'injector' ) + , Module; + +var ModuleLoader = module.exports = Class.extend( { instance: null, @@ -33,8 +34,8 @@ var Module = module.exports = Class.extend( }, preShutdownHook: function( module ) { - if ( module instanceof moduleClass && typeof module.preShutdown === 'function' ) { - debug( [ 'preShutdown (hook) for module', module.name ].join( ' ' ) ); + if ( module instanceof Module.Class && typeof module.preShutdown === 'function' ) { + module.debug( 'Module.preShutdown() hook...' ); module.preShutdown(); } }, @@ -50,14 +51,13 @@ var Module = module.exports = Class.extend( }, loadModules: function( env ) { + Module = injector.getInstance( 'Module' ); + var self = this; if ( this.modulesLoaded === false ) { debug( 'Loading modules...' ); - // Get a copy of the module class - moduleClass = require( 'classes' ).ModuleClass; - // Load each of our modules packageJson.bundledDependencies.forEach( this.proxy( 'loadModule', env ) ); @@ -85,12 +85,12 @@ var Module = module.exports = Class.extend( process.env = env; } - debug( [ 'Loading the', moduleName, 'module' ].join( ' ' ) ); + debug( [ 'Loading the', moduleName, 'module' ].join( ' ' ) + '...' ); var module = require( moduleName ); var moduleLowerCamelCase = i.camelize( moduleName.replace( /\-/ig, '_' ), false ); - debug( [ 'Adding the', moduleLowerCamelCase, 'module to the injector' ].join( ' ' ) ); + debug( [ 'Adding the', moduleLowerCamelCase, 'module to the injector' ].join( ' ' ) + '...' ); injector.instance( moduleLowerCamelCase, module ); // Add the module into our modules array so we can keep track of them internally and call hooks in their module.js file @@ -98,27 +98,34 @@ var Module = module.exports = Class.extend( }, configureAppHook: function( module ) { - if ( module instanceof moduleClass && typeof module.configureApp === 'function' ) { - debug( 'configureApp hook called for module ' + module.name ); + if ( module instanceof Module.Class && typeof module.configureApp === 'function' ) { + module.debug( 'Module.configureApp() hook...' ); + injector.getInstance( 'app' ).configure( module.proxy( 'configureApp', injector.getInstance( 'app' ), injector.getInstance( 'express' ) ) ); } }, preResourcesHook: function( module ) { - if ( module instanceof moduleClass && typeof module.preResources === 'function' ) { + if ( module instanceof Module.Class && typeof module.preResources === 'function' ) { + module.debug( 'Module.preResources() hook...' ); + module.hook( 'preResources' ); } }, loadModuleResources: function( module ) { - if ( module instanceof moduleClass && typeof module.loadResources === 'function' ) { + if ( module instanceof Module.Class && typeof module.loadResources === 'function' ) { + module.debug( 'Module.loadResources() hook...' ); debug( [ 'loadResources for module', module.name ].join( ' ' ) ); + module.loadResources(); } }, modulesLoadedHook: function( module ) { - if ( module instanceof moduleClass && typeof module.modulesLoaded === 'function' ) { + if ( module instanceof Module.Class && typeof module.modulesLoaded === 'function' ) { + module.debug( 'Module.modulesLoaded() hook...' ); + module.hook( 'modulesLoaded' ); } }, @@ -128,7 +135,7 @@ var Module = module.exports = Class.extend( // Give the modules notice that we are about to add our routes to the app this.modules.forEach( this.proxy( 'preRouteHook' ) ); - // Initialize all the modules routes + debug( 'Initializing routes...' ); this.modules.forEach( this.proxy( 'initializeModuleRoutes' ) ); // We only want to do this once @@ -139,15 +146,15 @@ var Module = module.exports = Class.extend( }, preRouteHook: function( module ) { - if ( module instanceof moduleClass && typeof module.preRoute === 'function' ) { - debug( [ 'preRoute (hook) for module', module.name ].join( ' ' ) ); + if ( module instanceof Module.Class && typeof module.preRoute === 'function' ) { + module.debug( 'Module.configureApp() hook...' ); module.preRoute(); } }, initializeModuleRoutes: function( module ) { - if ( module instanceof moduleClass ) { - debug( [ 'Initializing the', module.name, 'modules routes.' ].join( ' ' ) ); + if ( module instanceof Module.Class ) { + module.debug( [ 'Initializing routes...' ].join( ' ' ) ); module.initRoutes(); } } diff --git a/package.json b/package.json index 8e967fc..f00c13c 100644 --- a/package.json +++ b/package.json @@ -20,20 +20,21 @@ "test": "grunt test --verbose" }, "dependencies": { - "express": "~3.4.7", - "clever-controller": "~1.1.2", + "async": "~0.2.9", + "bluebird": "^2.0.2", + "clever-controller": "~1.1.3", "clever-injector": "~1.0.0", - "nconf": "~0.6.9", - "uberclass": "~1.0.1", + "cors": "~2.1.1", "debug": "~0.7.2", + "deepmerge": "~0.2.7", + "express": "~3.4.7", + "i": "~0.3.2", "matchdep": "~0.3.0", - "async": "~0.2.9", - "underscore": "~1.5.2", + "nconf": "~0.6.9", "q": "~1.0.0", - "i": "~0.3.2", - "deepmerge": "~0.2.7", "should": "~3.1.2", - "cors": "~2.1.1" + "uberclass": "~1.0.1", + "underscore": "~1.5.2" }, "devDependencies": { "grunt-contrib-watch": "", From 50ecf28f8d1e4a9fbc1f1b57104d37500b9cac7d Mon Sep 17 00:00:00 2001 From: Richard Date: Sat, 7 Jun 2014 20:01:32 +1000 Subject: [PATCH 5/6] release(bump): 1.0.0 --- package.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/package.json b/package.json index f00c13c..3eb7f49 100644 --- a/package.json +++ b/package.json @@ -2,14 +2,14 @@ "main": "app.js", "name": "node-seed", "description": "Cleverstack Node-Seed", - "version": "0.1.4", + "version": "1.0.0", "author": { "name": "CleverStack", "email": "admin@cleverstack.io", "web": "http://cleverstack.io" }, "collaborators": [ - "Richard Gustin ", + "Richard Gustin ", "Dmitry Bashkatov ", "Leandro Ostera " ], @@ -54,4 +54,4 @@ "should": "~2.1.1" }, "bundledDependencies": [] -} \ No newline at end of file +} From 914d12cd1552559e19c4103431238726b6856c12 Mon Sep 17 00:00:00 2001 From: Richard Gustin Date: Sat, 7 Jun 2014 20:06:03 +1000 Subject: [PATCH 6/6] Fixing package.json --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 3eb7f49..6dd7e72 100644 --- a/package.json +++ b/package.json @@ -54,4 +54,4 @@ "should": "~2.1.1" }, "bundledDependencies": [] -} +} \ No newline at end of file