├── .npmignore ├── index.js ├── src ├── bootstraps │ ├── ConfigManager.js │ ├── readme.md │ ├── RouterHandlerFactory.js │ ├── ErrorFactory.js │ ├── EventEmitterFactory.js │ ├── QueryFactory.js │ ├── ModuleBootstrap.js │ ├── ModelFactory.js │ ├── ControllerFactory.js │ ├── Default.js │ ├── SchemaFactory.js │ ├── GatewayFactory.js │ ├── ComponentFactory.js │ ├── Element.js │ └── AppBootstrap.js ├── mvc │ ├── AbstractEventListener.js │ ├── AbstractElement.js │ ├── AbstractModel.js │ ├── AbstractController.js │ ├── AbstractCRUDController.js │ ├── AbstractCRUDModel.js │ ├── DefaultQuery.js │ └── AbstractSchema.js ├── errors │ ├── Parse.js │ ├── Internal.js │ ├── Validation.js │ ├── InvalidParams.js │ ├── NotFound.js │ ├── InvalidRequest.js │ ├── NotImplemented.js │ ├── MethodNotFound.js │ ├── Forbidden.js │ └── Default.js ├── gateways │ ├── SocketIO.js │ ├── HTTP.js │ ├── AbstractGateway.js │ └── JSONRPC.js ├── components │ ├── Default.js │ ├── SocketIO.js │ ├── middleware │ │ ├── Default.js │ │ ├── EnsureAuthenticated.js │ │ ├── JSONMiddlewareProxy.js │ │ └── ValidJSONRPC.js │ ├── router │ │ ├── JSONRPC.js │ │ └── Default.js │ ├── Log.js │ ├── Session.js │ ├── Authentication.js │ ├── Express.js │ ├── Authorization.js │ └── DataBase.js ├── config │ ├── defaultEngine.js │ └── defaultApp.js └── Ginger.js ├── .gitignore ├── tests ├── exampleApplication │ ├── components │ │ ├── Log.js │ │ ├── middleware │ │ │ └── SomeMiddleware.js │ │ └── router │ │ │ ├── Default.js │ │ │ ├── JSONRPC.js │ │ │ └── .fuse_hidden0000cb6100000002 │ ├── config │ │ ├── env │ │ │ ├── development.js │ │ │ └── production.js │ │ └── app.js │ ├── modules │ │ └── sum │ │ │ ├── models │ │ │ ├── sum.js │ │ │ ├── Index.js │ │ │ └── schemas │ │ │ │ └── sum.js │ │ │ ├── modules │ │ │ └── multiplication │ │ │ │ ├── models │ │ │ │ └── Index.js │ │ │ │ └── controllers │ │ │ │ └── Index.js │ │ │ └── controllers │ │ │ ├── Index.js │ │ │ └── Sum.js │ ├── errors │ │ └── NotFound.js │ ├── models │ │ ├── Categories.js │ │ ├── Event.js │ │ ├── Hello.js │ │ ├── schemas │ │ │ ├── categories.js │ │ │ ├── posts.js │ │ │ ├── login.js │ │ │ └── tags.js │ │ └── Authentication.js │ ├── controllers │ │ ├── Restricted.js │ │ ├── ExceptionTrhower.js │ │ ├── Hello.js │ │ └── Authentication.js │ ├── gateways │ │ └── HTTP.js │ └── main.js ├── core │ ├── components │ │ ├── Session.js │ │ ├── JSONRpc.js │ │ ├── SocketIO.js │ │ ├── Authentication.js │ │ ├── Authorization.js │ │ └── DataBase.js │ ├── gateways │ │ ├── SocketIO.js │ │ ├── HTTP.js │ │ └── JSONRpc.js │ ├── bootstraps │ │ ├── ModelFactory.js │ │ └── SchemaFactory.js │ ├── mvc │ │ ├── AbstractSchema.js │ │ ├── AbstractCRUDModel.js │ │ └── DefaultQuery.js │ ├── engine.js │ └── application.js ├── fixtures │ ├── tags.js │ ├── categories.js │ └── login.js └── tools │ ├── utils.js │ └── http.js ├── docs ├── sequenceDiagram_appUp.png ├── ConsolidatorBackendSequence.png ├── ConsolidatorBackendSequence.uxf └── sequenceDiagram_appUp.uxf ├── package.json └── README.md /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | src/tests -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports=require(__dirname+'/src/Ginger.js'); -------------------------------------------------------------------------------- /src/bootstraps/ConfigManager.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | 3 | }; -------------------------------------------------------------------------------- /src/mvc/AbstractEventListener.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | 3 | }; -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /circle.yml 3 | /circle.PLIST-yml 4 | *.fuse* -------------------------------------------------------------------------------- /src/errors/Parse.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Parse error', 3 | code:'PARSE' 4 | } -------------------------------------------------------------------------------- /tests/exampleApplication/components/Log.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports={ 3 | iAmOverwritten:true 4 | } -------------------------------------------------------------------------------- /tests/exampleApplication/config/env/development.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | name:'Development' 3 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/config/env/production.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | name:'Production' 3 | }; -------------------------------------------------------------------------------- /src/errors/Internal.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Internal error', 3 | code:'INTERNAL' 4 | }; -------------------------------------------------------------------------------- /src/errors/Validation.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Validation error', 3 | code:'VALIDATION' 4 | }; -------------------------------------------------------------------------------- /docs/sequenceDiagram_appUp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drFabio/gingerJs/master/docs/sequenceDiagram_appUp.png -------------------------------------------------------------------------------- /src/errors/InvalidParams.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Invalid params', 3 | code:'INVALID_PARAMS' 4 | } -------------------------------------------------------------------------------- /src/errors/NotFound.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Resource not found', 3 | code:'NOT_FOUND' 4 | 5 | } -------------------------------------------------------------------------------- /tests/core/components/Session.js: -------------------------------------------------------------------------------- 1 | var chai=require('chai'); 2 | var expect=chai.expect; 3 | var should = chai.should(); -------------------------------------------------------------------------------- /src/errors/InvalidRequest.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Invalid request', 3 | code:'INVALID_REQUEST' 4 | } -------------------------------------------------------------------------------- /src/errors/NotImplemented.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Not implemented', 3 | code:'NOT_IMPLEMENTED' 4 | 5 | } -------------------------------------------------------------------------------- /tests/exampleApplication/modules/sum/models/sum.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | read:function(search,cb){ 3 | 4 | } 5 | }; -------------------------------------------------------------------------------- /docs/ConsolidatorBackendSequence.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/drFabio/gingerJs/master/docs/ConsolidatorBackendSequence.png -------------------------------------------------------------------------------- /src/errors/MethodNotFound.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Resource not found', 3 | code:'METHOD_NOT_FOUND' 4 | 5 | } -------------------------------------------------------------------------------- /src/gateways/SocketIO.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports={ 3 | _buildRoute:function(controllerName,action,controllerData){ 4 | } 5 | } -------------------------------------------------------------------------------- /src/errors/Forbidden.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'You are not allowed to perform this actions', 3 | code:'FORBIDDEN' 4 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/errors/NotFound.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | defaultMessage:'Resource not found', 3 | iAmOverwritten:true 4 | 5 | } -------------------------------------------------------------------------------- /tests/exampleApplication/models/Categories.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | _getDefaultFields:function(){ 3 | return {enabled:false} 4 | } 5 | 6 | } -------------------------------------------------------------------------------- /tests/exampleApplication/modules/sum/models/Index.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | sum:function(a,b){ 3 | return parseInt(a)+parseInt(b); 4 | } 5 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/controllers/Restricted.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | helloAction:function(req,res){ 3 | res.send('Acessing restricted page'); 4 | } 5 | } -------------------------------------------------------------------------------- /tests/exampleApplication/models/Event.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | hasEvents:true, 3 | callHello:function(to){ 4 | this.emit('hello','Hello '+to); 5 | } 6 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/controllers/ExceptionTrhower.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | errorAction:function(req,res){ 3 | throw new Error('An error has ocurred'); 4 | } 5 | } -------------------------------------------------------------------------------- /tests/exampleApplication/models/Hello.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | isSingleton:false, 3 | foo:'bar', 4 | sayHello:function(){ 5 | return "Hello"; 6 | } 7 | }; -------------------------------------------------------------------------------- /src/bootstraps/readme.md: -------------------------------------------------------------------------------- 1 | Bootstraps are used to initialize the application, are basically engine Helpers and the average user should not neet to overwride them 2 | 3 | -------------------------------------------------------------------------------- /src/mvc/AbstractElement.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | init:function(engine){ 3 | this._engine=engine; 4 | this._setup(); 5 | }, 6 | _setup:function(){ 7 | } 8 | 9 | }; -------------------------------------------------------------------------------- /src/components/Default.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | init:function(engine,params) { 3 | this._engine=engine; 4 | this._params=params; 5 | }, 6 | up:function(cb){ 7 | cb(); 8 | } 9 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/modules/sum/modules/multiplication/models/Index.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | inheritsAbstract:true, 3 | multiply:function(a,b){ 4 | return a*b; 5 | } 6 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/modules/sum/modules/multiplication/controllers/Index.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | inheritsAbstract:true, 3 | multiplyAction:function(req,res){ 4 | 5 | } 6 | 7 | }; -------------------------------------------------------------------------------- /src/components/SocketIO.js: -------------------------------------------------------------------------------- 1 | //var socketio=require( 'socket.io' ); 2 | module.exports={ 3 | init: function(engine,params) { 4 | var port=params.port; 5 | cb(null); 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tests/exampleApplication/controllers/Hello.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | helloAction:function(req,res){ 3 | var model=this._engine.getModel('Hello'); 4 | res.send(model.sayHello()); 5 | } 6 | } -------------------------------------------------------------------------------- /src/components/middleware/Default.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | getMiddleware: function(controllerObj,gateway) { 3 | return function(req, res, next) { 4 | next(req,res); 5 | } 6 | } 7 | }; -------------------------------------------------------------------------------- /src/mvc/AbstractModel.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | parent:'ginger.mvc.AbstractElement', 3 | getModel:function(name,var_args){ 4 | return this._engine.getModel.apply(this._engine,arguments); 5 | } 6 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/components/middleware/SomeMiddleware.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | getMiddleware: function(controllerObj,gateway) { 3 | return function(req, res, next) { 4 | next(req,res); 5 | } 6 | } 7 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/gateways/HTTP.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | //var to check on the test if it was overwritten 3 | iAmOverwritten:true, 4 | init:function(ginger,params,cb){ 5 | this._super(ginger,params,cb); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /tests/exampleApplication/main.js: -------------------------------------------------------------------------------- 1 | var Ginger=require(__dirname+'/../../src/Ginger.js'); 2 | ginger=new Ginger(); 3 | ginger.setAppPath(__dirname); 4 | var cb=function(err,data){ 5 | if(err){ 6 | throw err; 7 | } 8 | } 9 | ginger.up(cb); -------------------------------------------------------------------------------- /src/components/router/JSONRPC.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | buildUrl:function(prefix,contoller,action){ 3 | var url= '/'+contoller.replace(/\./g,'/') 4 | if(prefix){ 5 | url='/'+prefix+url; 6 | } 7 | return url; 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /src/gateways/HTTP.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | _app:null, 3 | _expressComponent:null, 4 | 5 | init: function(engine,params) { 6 | this._super(engine,params); 7 | }, 8 | start:function(cb){ 9 | this._super(cb); 10 | } 11 | } -------------------------------------------------------------------------------- /tests/exampleApplication/modules/sum/models/schemas/sum.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | getStructure:function(schema){ 3 | return { 4 | numberA: {type: Number}, 5 | numberB: {type: Number}, 6 | result: {type: Number} 7 | 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/exampleApplication/models/schemas/categories.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | getStructure:function(schema){ 3 | return { 4 | name: {type: String, required: true, trim: true, unique: true}, 5 | enabled:{type:Boolean}, 6 | user:{type:schema.ObjectId,ref:'login'} 7 | } 8 | } 9 | } -------------------------------------------------------------------------------- /tests/exampleApplication/config/app.js: -------------------------------------------------------------------------------- 1 | var dbName='gingerTests'; 2 | module.exports={ 3 | "name":"Example", 4 | 'components':{ 5 | 'DataBase':{ 6 | 'mongo':{ 7 | 'url':'mongodb://localhost', 8 | 'port':'27017', 9 | 'base':dbName 10 | } 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /tests/exampleApplication/models/Authentication.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | login:function(username,password,done){ 3 | if(username==='foo' && password==='bar'){ 4 | done(null,{'name':'johnson','email':'johnson@johnson.com'}); 5 | return; 6 | } 7 | done(new Error('User not found')); 8 | } 9 | }; -------------------------------------------------------------------------------- /tests/fixtures/tags.js: -------------------------------------------------------------------------------- 1 | var id = require('pow-mongodb-fixtures').createObjectId; 2 | module.exports={ 3 | 'tags':{ 4 | 'NodeJS':{ 5 | '_id':id(), 6 | 'name':'NodeJS', 7 | 'active':true, 8 | 'numHits':100, 9 | 'slug':'nodejs', 10 | 'url':'http://nodejs.org/' 11 | } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/exampleApplication/models/schemas/posts.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | _isAuto:false, 3 | getStructure:function(schema){ 4 | return { 5 | title: {type: String, required: true, trim: true, unique: true}, 6 | content: {type: String}, 7 | at: {type: Date, default:Date.now,required: true}, 8 | }; 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /tests/core/gateways/SocketIO.js: -------------------------------------------------------------------------------- 1 | var chai=require('chai'); 2 | var expect=chai.expect; 3 | var should = chai.should(); 4 | describe('Gateway SocketIO',function(){ 5 | describe('SocketIO',function(){ 6 | it('Should proxy SocketIO queries to controller/action and respond to the same query'); 7 | 8 | 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/exampleApplication/components/router/Default.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | getRouteData:function(controller,action){ 3 | if(controller=='restricted' && action !='login' && action !='logout'){ 4 | return { 5 | middlewares:['EnsureAuthenticated'] 6 | } 7 | } 8 | return this._super(controller,action); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /tests/exampleApplication/components/router/JSONRPC.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | getRouteData:function(controller,action){ 3 | if(controller=='Restricted' && action !='login' && action !='logout'){ 4 | return { 5 | middlewares:['EnsureAuthenticated'] 6 | } 7 | } 8 | return this._super(controller,action); 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /src/bootstraps/RouterHandlerFactory.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | parent:'ginger.bootstraps.ComponentFactory', 3 | _defaultEngineNamespace:'ginger.components.router', 4 | _defaulAppNamespace:'components.router', 5 | _defaultEngineParent:'ginger.components.router.Default', 6 | _defaultAppParent:'ginger.components.router.Default', 7 | _configValue:'components' 8 | } -------------------------------------------------------------------------------- /src/components/router/Default.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | getRouteData:function(contoller,action){ 3 | return { 4 | verb:'get', 5 | middlewares:[] 6 | } 7 | }, 8 | buildUrl:function(prefix,contoller,action){ 9 | var url= '/'+contoller.replace(/\./g,'/')+'/'+action; 10 | if(prefix){ 11 | url='/'+prefix+url; 12 | } 13 | return url; 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /tests/exampleApplication/models/schemas/login.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | getStructure:function(schema){ 3 | return { 4 | email: {type: String, required: true, trim: true, unique: true}, 5 | active: {type: Boolean,default:true}, 6 | name: {type: String, required: true}, 7 | password: {type: String, required: true, trim: true} 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /tests/exampleApplication/modules/sum/controllers/Index.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | inheritsAbstract:true, 3 | _model:null, 4 | init: function(engine) { 5 | this._super(engine); 6 | this._model=this._engine.getModel('sum.index'); 7 | }, 8 | indexAction:function(req,res){ 9 | 10 | res.send(200,this._model.sum(req.query.a,req.query.b)); 11 | } 12 | }; -------------------------------------------------------------------------------- /tests/exampleApplication/modules/sum/controllers/Sum.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | _model:null, 3 | indexAction:function(req,res){ 4 | 5 | res.send(200,this._model.sum(req.query.a,req.query.b)); 6 | }, 7 | sumThreeNumbersAction:function(req,res){ 8 | var sumA=this._model.sum(req.query.a,req.query.b) 9 | res.send(200,this._model.sum(sumA,req.query.c)); 10 | 11 | } 12 | }; -------------------------------------------------------------------------------- /tests/core/components/JSONRpc.js: -------------------------------------------------------------------------------- 1 | var chai=require('chai'); 2 | var expect=chai.expect; 3 | var should = chai.should(); 4 | describe('Component',function(){ 5 | describe('JSONRpc',function(){ 6 | it('Should be able to validate JSONRPC requests'); 7 | it('Should be able to send JSONRPC error'); 8 | it('Should be able to send JSONRPC response'); 9 | }); 10 | }); 11 | -------------------------------------------------------------------------------- /tests/exampleApplication/components/router/.fuse_hidden0000cb6100000002: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | getRouteData:function(contoller,action){ 3 | return { 4 | verb:'get', 5 | middlewares:[] 6 | } 7 | }, 8 | buildUrl:function(prefix,contoller,action){ 9 | var url= '/'+contoller.replace(/\./g,'/')+'/'+action; 10 | if(prefix){ 11 | url='/'+prefix+url; 12 | } 13 | return url; 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /tests/core/components/SocketIO.js: -------------------------------------------------------------------------------- 1 | var chai=require('chai'); 2 | var expect=chai.expect; 3 | var should = chai.should(); 4 | describe('Component',function(){ 5 | describe('SocketIO',function(){ 6 | it('Should connect clients'); 7 | it('Should disconnect client'); 8 | it('Should be able to broadcast to multiple users'); 9 | it('Should allow the authorization and session usage'); 10 | }); 11 | }); 12 | -------------------------------------------------------------------------------- /src/bootstraps/ErrorFactory.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | parent:'ginger.bootstraps.Element', 3 | _defaultEngineNamespace:'ginger.errors', 4 | _defaulAppNamespace:'errors', 5 | _configValue:'errors', 6 | _defaultEngineParent:'ginger.errors.Default', 7 | _isSingleton:false, 8 | _getObject:function(name,message,code,data){ 9 | 10 | return this._classFactory.createObject(name,this._engine,message,code,data); 11 | } 12 | 13 | } -------------------------------------------------------------------------------- /src/config/defaultEngine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the abstract configuration for Ginger you can overwrite it by AssinINg a config to yur engine 3 | */ 4 | module.exports={ 5 | 'gatewaysDir':__dirname+'/../gateways/', 6 | 'componentsDir':__dirname+'/../components/', 7 | 'bootstrapsDir':__dirname+'/../bootstraps/', 8 | 'mvcDir':__dirname+'/../mvc/', 9 | 'errorsDir':__dirname+'/../errors/', 10 | 'rootDir':__dirname+'/../' 11 | }; -------------------------------------------------------------------------------- /src/components/Log.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | init:function(engine,params) { 3 | this._jl=require('jsnlog').JL(); 4 | //Inherited methods 5 | var inheritedMethods=[ 6 | 'debug', 7 | 'error', 8 | 'fatal', 9 | 'fatalException', 10 | 'info', 11 | 'log', 12 | 'trace', 13 | 'warn' 14 | ]; 15 | var self=this; 16 | inheritedMethods.forEach(function(f){ 17 | self[f]=self._jl[f].bind(self._jl); 18 | }) 19 | } 20 | } -------------------------------------------------------------------------------- /src/components/middleware/EnsureAuthenticated.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | getMiddleware: function(controllerObj, gateway) { 3 | var self = this; 4 | return function(req, res, next) { 5 | if (req.isAuthenticated()) { return next(); } 6 | var forbidden=self._engine.getError('Forbidden', 'You have to be logged in to access this area!'); 7 | 8 | res.send(forbidden); 9 | return; 10 | } 11 | } 12 | }; -------------------------------------------------------------------------------- /tests/fixtures/categories.js: -------------------------------------------------------------------------------- 1 | var id = require('pow-mongodb-fixtures').createObjectId; 2 | var logins=require(__dirname+'/login').login; 3 | module.exports= { 4 | 'categories':{ 5 | 'forDevelopers':{ 6 | name:'for users', 7 | enabled:true, 8 | user:logins.user1._id.toString(), 9 | _id:id() 10 | }, 11 | 'forClients':{ 12 | name:'for clients', 13 | enabled:true, 14 | user:logins.user2._id.toString(), 15 | _id:id() 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /src/components/Session.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | init:function(engine,params) { 3 | this._super(engine,params); 4 | var expressComponent=engine.getComponent('express'); 5 | var app=expressComponent.getApp(); 6 | var self=this; 7 | var cookieParser = require('cookie-parser'); 8 | var session =this._getSession(); 9 | app.use(cookieParser()); 10 | app.use(session(self._params)); 11 | }, 12 | _getSession:function(){ 13 | return require('express-session'); 14 | } 15 | } -------------------------------------------------------------------------------- /tests/core/components/Authentication.js: -------------------------------------------------------------------------------- 1 | var chai=require('chai'); 2 | chai.config.includeStack =true; 3 | var expect=chai.expect; 4 | var Ginger=require(__dirname+'/../../../src/Ginger.js'); 5 | var should = chai.should 6 | var ginger; 7 | 8 | describe('Component Authentication',function(){ 9 | it('Should not be aple to login with the wrong password'); 10 | it('Should be able to login with the right password'); 11 | it('Should show a error if there isn\t an authentication modle'); 12 | it('Should be able to logout'); 13 | }); -------------------------------------------------------------------------------- /src/bootstraps/EventEmitterFactory.js: -------------------------------------------------------------------------------- 1 | var util = require("util"); 2 | var EventEmitter = require("events").EventEmitter; 3 | var _=require('lodash'); 4 | module.exports= { 5 | parent:'ginger.bootstraps.Default', 6 | makeObjectEventEmitter:function(obj){ 7 | if(!!obj.on && !!obj.emit){ 8 | return obj; 9 | } 10 | var ee = new EventEmitter(); 11 | for(var k in ee){ 12 | if(obj[k]){ 13 | continue; 14 | } 15 | if(typeof(ee[k])=='function'){ 16 | obj[k]=ee[k].bind(obj); 17 | } 18 | else{ 19 | obj[k]=ee[k]; 20 | } 21 | } 22 | return obj; 23 | } 24 | }; -------------------------------------------------------------------------------- /src/bootstraps/QueryFactory.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | parent:'ginger.bootstraps.Element', 3 | _defaulAppNamespace:'query', 4 | _defaultAppParent:'ginger.mvc.DefaultQuery', 5 | _defaultEngineNamespace:'ginger.query', 6 | _isSingleton:false, 7 | _isLazy:true, 8 | create : function ( var_args) { 9 | 10 | var newArgs=[]; 11 | newArgs.push(this._defaultAppParent); 12 | newArgs.push(this._engine); 13 | for(var x in arguments){ 14 | newArgs.push(arguments[x]); 15 | } 16 | 17 | var classFactory=this._classFactory; 18 | return classFactory.createObject.apply(classFactory,newArgs); 19 | } 20 | } -------------------------------------------------------------------------------- /src/bootstraps/ModuleBootstrap.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | 3 | /** 4 | * @todo clean clode 5 | * @type {String} 6 | */ 7 | parent:'ginger.bootstraps.Default', 8 | init : function(engine,params) { 9 | this._super(engine,params); 10 | this._moduleIndex={}; 11 | this._classFactory=this._engine.libs.classFactory; 12 | }, 13 | addToEngine:function(name,path,parentNamespace){ 14 | name=name.toLowerCase(); 15 | var mapIndex=this._buildNamespace(parentNamespace,name); 16 | this._moduleIndex[mapIndex]=true; 17 | }, 18 | hasElement:function(name){ 19 | name=name.toLowerCase(); 20 | return !!this._moduleIndex[name]; 21 | } 22 | 23 | } -------------------------------------------------------------------------------- /src/errors/Default.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | module.exports={ 3 | message:null, 4 | code:'DEFAULT', 5 | data:null, 6 | isError:true, 7 | defaultMessage:'There was an error', 8 | 9 | init:function(engine,message,code,data){ 10 | this._engine=engine; 11 | if(message){ 12 | this.setMessage(message); 13 | } 14 | else{ 15 | this.setMessage(this.defaultMessage); 16 | } 17 | if(!_.isEmpty(code)){ 18 | this.setCode(code); 19 | } 20 | if(data){ 21 | this.setData(data); 22 | } 23 | }, 24 | setCode:function(code){ 25 | this.code=code; 26 | }, 27 | setMessage:function(message){ 28 | this.message=message; 29 | }, 30 | setData:function(data){ 31 | this.data=data; 32 | } 33 | } 34 | 35 | -------------------------------------------------------------------------------- /tests/exampleApplication/models/schemas/tags.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | _isAuto:false, 3 | getStructure:function(schema){ 4 | return { 5 | name: {type: String, required: true, trim: true, unique: true}, 6 | active: {type: Boolean,default:true}, 7 | numHits:{type:Number}, 8 | slug:{type:String}, 9 | url:{type:String} 10 | }; 11 | }, 12 | getValidators:function(){ 13 | return { 14 | active: 'toBoolean', 15 | numHits:'isInt', 16 | name: ['isAlpha','isLowercase'], 17 | url: { 18 | 'isURL': [//A validator with parameters 19 | { 20 | 'protocols': [ 21 | 'http', 22 | 'https', 23 | 24 | ], 25 | require_tld: true, 26 | require_protocol: true, 27 | allow_underscores: false 28 | } 29 | ] 30 | } 31 | 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /src/bootstraps/ModelFactory.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | parent:'ginger.bootstraps.Element', 3 | _defaulAppNamespace:'models', 4 | _defaultAppParent:'ginger.mvc.AbstractModel', 5 | _defaultCRUDParent:'ginger.mvc.AbstractCRUDModel', 6 | init : function(engine,params) { 7 | this._super(engine,params); 8 | this._eventFactory=engine.getBootstrap('EventEmitterFactory'); 9 | }, 10 | handleAutoSchemaCrud:function(schemaNames){ 11 | schemaNames.forEach(function(s){ 12 | if(this.hasElement(s)){ 13 | this.changeObjectParent(s,this._defaultCRUDParent); 14 | } 15 | else{ 16 | this.setEmptyAppClass(s,this._defaultCRUDParent); 17 | } 18 | this.mergeObjectPojo(s,{_schemaName:s}); 19 | },this); 20 | }, 21 | create : function (name, var_args) { 22 | var obj=this._super.apply(this,arguments); 23 | if(obj.hasEvents){ 24 | obj=this._eventFactory.makeObjectEventEmitter(obj); 25 | } 26 | return obj; 27 | } 28 | } -------------------------------------------------------------------------------- /tests/exampleApplication/controllers/Authentication.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | loginAction:function(req,res){ 3 | 4 | var self=this; 5 | if (req.isAuthenticated()) { 6 | res.send("success"); 7 | return; 8 | } 9 | var authenticationModel=this._engine.getModel('Authentication'); 10 | var authenticationCb=function(err,user){ 11 | if(err){ 12 | res.send(self._engine.getError('Forbidden','User name or password invalid')); 13 | return; 14 | } 15 | req.logIn(user, function(err) { 16 | if (err) { 17 | res.send(self._engine.getError('Internal','An error ocurred while logging in user')); 18 | return; 19 | } 20 | 21 | res.send(user); 22 | return ; 23 | }); 24 | return; 25 | }; 26 | authenticationModel.login(req.query.user,req.query.password,authenticationCb); 27 | }, 28 | logoutAction:function(req,res){ 29 | req.logout(); 30 | res.send('success'); 31 | } 32 | }; -------------------------------------------------------------------------------- /src/bootstraps/ControllerFactory.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | parent:'ginger.bootstraps.Element', 3 | _defaulAppNamespace:'controllers', 4 | _defaultAppParent:'ginger.mvc.AbstractController', 5 | _defaultCRUDParent:'ginger.mvc.AbstractCRUDController', 6 | 7 | _debugController:true, 8 | handleAutoSchemaCrud:function(schemaNames){ 9 | schemaNames.forEach(function(s){ 10 | if(this.hasElement(s)){ 11 | this.changeObjectParent(s,this._defaultCRUDParent); 12 | } 13 | else{ 14 | this.setEmptyAppClass(s,this._defaultCRUDParent); 15 | } 16 | },this); 17 | }, 18 | init : function(engine,params) { 19 | this._super(engine,params); 20 | if(!this._params.actionSuffix){ 21 | this._params.actionSuffix='Action'; 22 | } 23 | }, 24 | _buildIndexData:function(name,namespace,POJO,isApp,isEngine){ 25 | if(!POJO.modelName){ 26 | POJO.modelName=name; 27 | } 28 | var data=this._super(name,namespace,POJO,isApp,isEngine); 29 | return data; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/bootstraps/Default.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | init : function(engine,params) { 3 | this._engine=engine; 4 | this._params=params; 5 | this._classFactory=this._engine.libs.classFactory; 6 | 7 | }, 8 | sanitizeName:function(name){ 9 | return name.substring(0, 1).toLowerCase() + name.substring(1); 10 | }, 11 | _buildNamespace:function(parentNamespace,currentDirectory){ 12 | var ret; 13 | if(parentNamespace==='' || typeof(parentNamespace)=='undefined' || parentNamespace==null){ 14 | ret=currentDirectory; 15 | } 16 | else{ 17 | ret=parentNamespace+'.'+currentDirectory; 18 | } 19 | return ret; 20 | }, 21 | _getPojo:function(path,defaultParent){ 22 | var pojo=require(path); 23 | return this._setDefaultParentOnPOJO(pojo,defaultParent); 24 | }, 25 | _setDefaultParentOnPOJO:function(pojo,defaultParent){ 26 | if(pojo.parent!==false && !pojo.parent && !!defaultParent){ 27 | pojo.parent=defaultParent; 28 | } 29 | return pojo; 30 | 31 | } 32 | } -------------------------------------------------------------------------------- /src/components/Authentication.js: -------------------------------------------------------------------------------- 1 | var passport = require('passport'); 2 | module.exports={ 3 | init:function(engine,params) { 4 | this._super(engine,params); 5 | this.setupPassport(); 6 | this.setLoginStrategy(); 7 | }, 8 | setupPassport:function(){ 9 | var expressComponent=this._engine.getComponent('express'); 10 | var app=expressComponent.getApp(); 11 | app.use(passport.initialize()); 12 | this._passport=passport; 13 | app.use(passport.session()); // persistent login sessions 14 | /* 15 | if(!engine.hasModel(this._params.model)){ 16 | throw new Error('The authentication component needs the '+this._params.model+' to work (you can change the name of the model trough the config)'); 17 | } 18 | this._model=engine.getModel(this._params.model);*/ 19 | }, 20 | getPassport:function(){ 21 | return this._passport; 22 | }, 23 | setLoginStrategy:function(){ 24 | /*var LocalStrategy = require('passport-local').Strategy; 25 | passport.use({passReqToCallback: true},new LocalStrategy());*/ 26 | passport.serializeUser(function(user, done) { 27 | done(null, user); 28 | }); 29 | passport.deserializeUser(function(user, done) { 30 | done(null, user); 31 | }); 32 | 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /src/config/defaultApp.js: -------------------------------------------------------------------------------- 1 | /** 2 | * This is the abstract configuration for Ginger you can overwrite it by AssinINg a config to yur engine 3 | */ 4 | module.exports={ 5 | //Components configurations 6 | 'components':{ 7 | 'Express':{ 8 | 'port':3000, 9 | 'host':'0.0.0.0', 10 | 'startOrder':'beforeGateways' 11 | }, 12 | 'SocketIO':{ 13 | 'port':3040 14 | }, 15 | 'DataBase':{ 16 | 'mongo':{ 17 | //'uri':'mongodb://localhost:27017/gingerTests' 18 | 'url':'mongodb://localhost', 19 | 'port':'27017', 20 | 'base':'ginger', 21 | 'maxAllowedLimit':false 22 | } 23 | }, 24 | 'Authentication':{ 25 | 'model':'authentication' 26 | }, 27 | 'Session':{ 28 | 'secret':'Change_this!', 29 | 'saveUninitialized':true, 30 | 'resave':true 31 | }, 32 | 'Log':{} 33 | }, 34 | 'gateways':{ 35 | 'HTTP':{ 36 | 'components':['Express','Session','Authentication','DataBase'] 37 | }, 38 | 'JSONRPC':{ 39 | 'prefix':'JSONRPC', 40 | 'components':['Express','Session','Authentication','DataBase'] 41 | }, 42 | 'SocketIO':{} 43 | }, 44 | 'errors':[ 45 | 'NotFound', 46 | 'Validate', 47 | 'InvalidParams', 48 | 'InvalidRequest', 49 | 'InternalError' 50 | ] 51 | }; -------------------------------------------------------------------------------- /src/components/middleware/JSONMiddlewareProxy.js: -------------------------------------------------------------------------------- 1 | var async=require('async'); 2 | /** 3 | * Makes the role of a middleware stack for JSON RPC 4 | * @type {Object} 5 | */ 6 | module.exports = { 7 | getMiddleware: function(controllerObj, gateway,controllerData) { 8 | var getComponent=this._engine.getComponent.bind(this._engine); 9 | return function(req,res,next){ 10 | var action=req.jsonRPC.method; 11 | /** 12 | * @todo clean code to this middleware does'nt have to use _routerHandlerComponent directlry 13 | */ 14 | var middlewares=gateway._routerHandlerComponent.getRouteData(controllerData.name,action).middlewares; 15 | if(middlewares.length==0){ 16 | next(); 17 | return; 18 | } 19 | var functionsToExecute=[]; 20 | var buildReqFunction=function(middleware){ 21 | var middlewareFunc=middleware.getMiddleware(controllerObj,gateway,controllerData); 22 | return function(cb){ 23 | middlewareFunc(req,res,cb); 24 | }; 25 | } 26 | middlewares.forEach(function(m){ 27 | var middleware=getComponent('middleware.'+m); 28 | functionsToExecute.push(buildReqFunction(middleware)); 29 | },this); 30 | 31 | async.series(functionsToExecute,function(err){ 32 | next(err); 33 | }); 34 | } 35 | } 36 | }; -------------------------------------------------------------------------------- /src/components/Express.js: -------------------------------------------------------------------------------- 1 | var express = require('express'); 2 | var http=require('http'); 3 | module.exports={ 4 | init:function(engine,params) { 5 | this._super(engine,params); 6 | this.server=null; 7 | this._isRunning=false; 8 | this._isClosed=false; 9 | this._app = express(); 10 | var bodyParser = require('body-parser'); 11 | this._app.use(bodyParser.json()); 12 | this._app.use(bodyParser.urlencoded({extended: true})); 13 | }, 14 | isRunning:function(){ 15 | return this._isRunning; 16 | }, 17 | up:function(cb){ 18 | this.listen(cb); 19 | }, 20 | listen:function(cb,port,host) { 21 | 22 | if(this.isRunning()){ 23 | cb(); 24 | return; 25 | } 26 | var self=this; 27 | port=port || this._params.port; 28 | host=host || this._params.host; 29 | this.server=this._app.listen(port,host, function(err) { 30 | if(!err){ 31 | self._isRunning=true; 32 | } 33 | self._addFunctionToClose(); 34 | cb(err); 35 | 36 | 37 | }); 38 | }, 39 | _addFunctionToClose:function(){ 40 | var self=this; 41 | var func=function(cb){ 42 | self.end(); 43 | cb(); 44 | } 45 | this._engine.addFunctionToCloseQueue(func); 46 | }, 47 | getApp:function(){ 48 | return this._app; 49 | }, 50 | end:function(){ 51 | if(this._isRunning && !this._isClosed){ 52 | this._isClosed=true; 53 | this._isRunning=false; 54 | this.server.close(); 55 | return; 56 | } 57 | 58 | } 59 | } -------------------------------------------------------------------------------- /tests/core/bootstraps/ModelFactory.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | var async=require('async'); 3 | var Utils=require('../../tools/utils'); 4 | var utils=new Utils(); 5 | var expect=utils.expect; 6 | var should = utils.should; 7 | var modelFactory; 8 | var ginger; 9 | var addModelSuccessfully=function(name){ 10 | var path=utils.appDir+'/models/'+name+'.js'; 11 | var namespace=modelFactory.setAppClass(name,path); 12 | expect(namespace).to.equal('models.'+name); 13 | var obj=modelFactory.create(name); 14 | return obj; 15 | } 16 | /** 17 | * @todo move tests to some kind of fixture 18 | */ 19 | describe('Model Factory',function(){ 20 | //Initializing the app by path first 21 | before(function(done){ 22 | ginger=utils.getServer(); 23 | ginger._setup(function(err,data){ 24 | if(err){ 25 | done(err); 26 | return; 27 | } 28 | modelFactory=ginger.getBootstrap('ModelFactory'); 29 | done(); 30 | }); 31 | }); 32 | describe('Events',function(){ 33 | it('Should be able to set a model as event emmiter',function(done){ 34 | var obj=addModelSuccessfully('Event'); 35 | expect(obj).to.exist; 36 | var testName='bar'; 37 | var model=ginger.getModel('Event'); 38 | model.on('foo',function(){ 39 | 40 | model.callHello(testName); 41 | }); 42 | model.on('hello',function(helloName){ 43 | expect(helloName).to.equal('Hello '+testName); 44 | done(); 45 | }); 46 | model.emit('foo'); 47 | }); 48 | }); 49 | after(function(done){ 50 | ginger.down(done); 51 | }); 52 | 53 | }); 54 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "gingerjs", 3 | "private": false, 4 | "author":"Fabio Oliveira Costa ", 5 | "version": "0.0.5", 6 | "description": "A HMVC framework", 7 | "devDependencies": { 8 | "chai":"*", 9 | "pow-mongodb-fixtures":"*", 10 | "request":"*", 11 | "tough-cookie":"*" 12 | 13 | }, 14 | "dependencies": { 15 | "lodash":"2.*", 16 | "express":"*", 17 | "socket.io":"*", 18 | "class.extend":"0.9.1", 19 | "async":"*", 20 | "olive_oil":"git://github.com/drFabio/OliveOil.git#master", 21 | "body-parser":"*", 22 | "mongoose":"3.8.19", 23 | "cookie-parser":"*", 24 | "express-session":"*", 25 | "passport":"*", 26 | "passport-local":"*", 27 | "validator":"*", 28 | "jsnlog":"*", 29 | "jsnlog-nodejs":"*" 30 | }, 31 | "scripts": { 32 | "start": "node app.js", 33 | "debug": "node debug app.js", 34 | "test":"mocha --timeout 10000 --recursive tests/core" 35 | }, 36 | "main": "index.js", 37 | "repository": { 38 | "type": "git", 39 | "url": "https://github.com/drFabio/gingerJs" 40 | }, 41 | "keywords":[ 42 | "HMVC", 43 | "Framework", 44 | "I18N" 45 | ], 46 | "author": "Fabio Oliveira Costa ", 47 | "license": "", 48 | "engines": { 49 | "node": ">=0.10.22" 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/mvc/AbstractController.js: -------------------------------------------------------------------------------- 1 | module.exports={ 2 | parent:'ginger.mvc.AbstractElement', 3 | 4 | _actionSuffix:'Action', 5 | _setup:function(){ 6 | this._initializeModel(); 7 | this._actions=this.getActionsMap(); 8 | }, 9 | _initializeModel:function(){ 10 | if(this._engine.hasModel(this.modelName)){ 11 | this._model=this._engine.getModel(this.modelName); 12 | } 13 | 14 | }, 15 | getModel:function(){ 16 | return this._model; 17 | }, 18 | getActions:function(){ 19 | return this._actions; 20 | }, 21 | getActionsMap:function(){ 22 | var actionsMap={}; 23 | for(var x in this){ 24 | if(typeof(this[x])==='function' && (plainName=this.getActionPlainName(x))!==false){ 25 | actionsMap[plainName]=x; 26 | } 27 | } 28 | return actionsMap; 29 | }, 30 | getActionFunctionByName:function(name){ 31 | return this._actions[name] 32 | }, 33 | getActionPlainName:function(name){ 34 | var suffix=this._actionSuffix; 35 | var pos=name.indexOf(suffix, name.length - suffix.length); 36 | if( pos == -1){ 37 | return false; 38 | } 39 | return name.substr(0,pos); 40 | }, 41 | actionExists:function(plainName){ 42 | return !!this._actions[plainName]; 43 | }, 44 | _getSendResponse:function(req,res){ 45 | return function(err,data){ 46 | var response; 47 | if(err){ 48 | res.status(200).send(err); 49 | return; 50 | } 51 | else{ 52 | response=data; 53 | } 54 | res.status(200).send(response); 55 | } 56 | }, 57 | _sendResponse:function(req,res,err,data){ 58 | this._getSendResponse(req,res)(err,data); 59 | }, 60 | }; -------------------------------------------------------------------------------- /tests/tools/utils.js: -------------------------------------------------------------------------------- 1 | var Ginger = require(__dirname+'/../../src/Ginger'); 2 | var appDir=__dirname+'/../exampleApplication'; 3 | /** 4 | * @todo get this from the joined config file 5 | */ 6 | var config=require(appDir+'/config/app'); 7 | var dbName=config.components.DataBase.mongo.base; 8 | var host='localhost'; 9 | var port=3000; 10 | var powDbFixtures=require('pow-mongodb-fixtures'); 11 | 12 | var fixtures = powDbFixtures.connect(dbName); 13 | var fixtureDir=__dirname+'/../fixtures/'; 14 | //var prefix=config.gateways.JSONRPC.prefix; 15 | var chai=require('chai'); 16 | var expect=chai.expect; 17 | var should = chai.should(); 18 | var _=require('lodash'); 19 | chai.config.includeStack =true; 20 | 21 | 22 | var httpHelper=require('./http')(host,port); 23 | 24 | function Utils(){ 25 | this._ginger=new Ginger(); 26 | this._ginger.setAppPath(appDir); 27 | this.jsonRpcErrors={ 28 | 'invalidParams':'-32602', 29 | 'notFound':'-32001' 30 | }; 31 | this.createObjectId=powDbFixtures.createObjectId; 32 | this.chai=chai; 33 | this.appDir=appDir; 34 | this.expect=expect; 35 | this.should=should; 36 | this.fixtures=fixtures; 37 | this.httpHelper=httpHelper; 38 | this.appConfig=config; 39 | } 40 | Utils.prototype.initServer = function(cb) { 41 | this._ginger.up(cb); 42 | }; 43 | Utils.prototype.endServer = function(cb) { 44 | this._ginger.down(cb); 45 | }; 46 | Utils.prototype.getServer=function(){ 47 | return this._ginger; 48 | } 49 | Utils.prototype.getFixtureData = function(var_args) { 50 | var ret={}; 51 | for(var k in arguments){ 52 | ret=_.extend(ret,require(fixtureDir+arguments[k])) 53 | } 54 | return ret; 55 | }; 56 | module.exports=Utils; -------------------------------------------------------------------------------- /tests/fixtures/login.js: -------------------------------------------------------------------------------- 1 | var id = require('pow-mongodb-fixtures').createObjectId; 2 | module.exports= { 3 | 'login':{ 4 | 'user1':{ 5 | 'email':'email@gmail.com', 6 | 'active':true, 7 | 'name':'bar', 8 | 'password':'foo', 9 | '_id':id() 10 | 11 | }, 12 | 'user2':{ 13 | 'email':'othermail@gmail.com', 14 | 'active':true, 15 | 'name':'bar', 16 | 'password':'foo', 17 | '_id':id() 18 | 19 | }, 20 | 'user3':{ 21 | 'email':'yetanothermail@gmail.com', 22 | 'active':true, 23 | 'name':'someone', 24 | 'password':'foo', 25 | '_id':id() 26 | }, 27 | 'user4':{ 28 | 'email':'email4@gmail.com', 29 | 'active':true, 30 | 'name':'bar', 31 | 'password':'foo', 32 | '_id':id() 33 | 34 | }, 35 | 'user5':{ 36 | 'email':'othermail5@gmail.com', 37 | 'active':true, 38 | 'name':'bar', 39 | 'password':'foo', 40 | '_id':id() 41 | 42 | }, 43 | 'user6':{ 44 | 'email':'user6@gmail.com', 45 | 'active':true, 46 | 'name':'someone', 47 | 'password':'foo', 48 | '_id':id() 49 | }, 50 | 'user7':{ 51 | 'email':'othermail7@gmail.com', 52 | 'active':true, 53 | 'name':'bar', 54 | 'password':'foo', 55 | '_id':id() 56 | 57 | }, 58 | 'user8':{ 59 | 'email':'othermail8@gmail.com', 60 | 'active':true, 61 | 'name':'bar', 62 | 'password':'foo', 63 | '_id':id() 64 | 65 | }, 66 | 'user9':{ 67 | 'email':'user9@gmail.com', 68 | 'active':true, 69 | 'name':'bar', 70 | 'password':'foo', 71 | '_id':id() 72 | 73 | }, 74 | 'user10':{ 75 | 'email':'user10@gmail.com', 76 | 'active':true, 77 | 'name':'someone', 78 | 'password':'foo', 79 | '_id':id() 80 | } 81 | } 82 | }; -------------------------------------------------------------------------------- /src/mvc/AbstractCRUDController.js: -------------------------------------------------------------------------------- 1 | module.exports= { 2 | parent:'ginger.mvc.AbstractController', 3 | saveAction:function(req,res){ 4 | var data=req.query.data; 5 | this._model.save(data,this._getSendResponse(req,res)); 6 | }, 7 | createAction:function(req,res){ 8 | var data=req.query.data; 9 | this._model.create(data,this._getSendResponse(req,res)); 10 | }, 11 | updateAction:function(req,res){ 12 | var search=req.query.search; 13 | var data=req.query.data; 14 | this._model.update(data,search,this._getSendResponse(req,res)); 15 | }, 16 | updateByIdAction:function(req,res){ 17 | var id=req.query.id; 18 | var data=req.query.data; 19 | this._model.updateById(id,data,this._getSendResponse(req,res)); 20 | }, 21 | readAction:function(req,res){ 22 | var search=req.query.search; 23 | var fields=req.query.fields; 24 | this._model.read(search,this._getSendResponse(req,res),fields); 25 | 26 | }, 27 | readOneAction:function(req,res){ 28 | var search=req.query.search; 29 | var fields=req.query.fields; 30 | this._model.readOne(search,this._getSendResponse(req,res),fields); 31 | 32 | }, 33 | destroyByIdAction:function(req,res){ 34 | var id=req.query.id; 35 | this._model.destroyById(id,this._getSendResponse(req,res)); 36 | }, 37 | readByIdAction:function(req,res){ 38 | var id=req.query.id; 39 | var fields=req.query.fields; 40 | this._model.readById(id,this._getSendResponse(req,res),fields); 41 | }, 42 | listAction:function(req,res){ 43 | var cb=this._getSendResponse(req,res); 44 | var limit=req.query.limit; 45 | var page=req.query.page; 46 | var search=req.query.search; 47 | var fields=req.query.fields; 48 | var options=req.query.options; 49 | this._model.list(search,limit,page,fields,options,cb); 50 | } 51 | 52 | }; -------------------------------------------------------------------------------- /src/components/middleware/ValidJSONRPC.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | module.exports= { 3 | 4 | getMiddleware: function(controllerObj,gateway) { 5 | var self=this; 6 | return function(req, res, next) { 7 | if (_.isEmpty(req.body) && !_.isEmpty(req.query)) { 8 | req.body = req.query; 9 | } 10 | if (!req.body) { 11 | var err=self._engine.getError('InvalidRequest','Missing parameters'); 12 | gateway._sendError(req,res,err,id); 13 | return; 14 | } 15 | 16 | var method = req.body.method; 17 | var id = req.body.id; 18 | req.JSONRPC={ 19 | method:method, 20 | id:id 21 | }; 22 | if (typeof(id) == 'undefined') { 23 | 24 | var err=self._engine.getError('InvalidRequest','Missing id'); 25 | gateway._sendError(req,res,err,id); 26 | return; 27 | } 28 | if (!method) { 29 | var err=self._engine.getError('InvalidRequest','Missing method'); 30 | gateway._sendError(req,res,err,id); 31 | return; 32 | } 33 | if (!controllerObj.actionExists(method)) { 34 | var err=self._engine.getError('MethodNotFound',method + ' not found'); 35 | gateway._sendError(req,res,err,id); 36 | return; 37 | } 38 | //Remove the json params an make the body as the parasm 39 | req.body = req.body.params; 40 | req.jsonRPC={ 41 | id:id, 42 | method:method 43 | }, 44 | req.query=req.body; 45 | if(!req.query){ 46 | req.query={}; 47 | } 48 | res.send=gateway._getSendProxy(res,id); 49 | next(); 50 | } 51 | }, 52 | }; -------------------------------------------------------------------------------- /tests/core/bootstraps/SchemaFactory.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | var async=require('async'); 3 | var Utils=require('../../tools/utils'); 4 | var utils=new Utils(); 5 | var expect=utils.expect; 6 | var should = utils.should; 7 | var schemaFactory; 8 | var ginger; 9 | var addSchemaSucessfully=function(name){ 10 | var path=utils.appDir+'/models/schemas/'+name+'.js'; 11 | var namespace=schemaFactory.setAppClass(name,path); 12 | expect(namespace).to.equal('schemas.'+name); 13 | var obj=schemaFactory.create(name); 14 | obj.setup(); 15 | return obj; 16 | } 17 | /** 18 | * @todo move tests to some kind of fixture 19 | */ 20 | describe('Schema Factory',function(){ 21 | //Initializing the app by path first 22 | before(function(done){ 23 | ginger=utils.getServer(); 24 | ginger._setup(function(err,data){ 25 | if(err){ 26 | done(err); 27 | return; 28 | } 29 | schemaFactory=ginger.getBootstrap('SchemaFactory'); 30 | done(); 31 | }); 32 | }); 33 | describe('Add schema',function(){ 34 | it('Should be able to add a normal one',function(done){ 35 | addSchemaSucessfully('login'); 36 | done(); 37 | }); 38 | it('Should be able to add a complex one',function(done){ 39 | addSchemaSucessfully('tags'); 40 | done(); 41 | }); 42 | it('Should be able to add a incomplete one ',function(done){ 43 | addSchemaSucessfully('posts'); 44 | done(); 45 | }); 46 | it('Should be able to add a schema initialize it remove it and then add it again',function(done){ 47 | var obj=addSchemaSucessfully('categories'); 48 | obj.clearSchema(function(err){ 49 | if(err){ 50 | cb(err); 51 | return; 52 | } 53 | obj.setup(); 54 | done(); 55 | }); 56 | }); 57 | it('Should create a empty schema if one is not defined',function(done){ 58 | var obj=schemaFactory.create('nonExistantSchema'); 59 | expect(obj).to.exist; 60 | done(); 61 | }); 62 | }); 63 | after(function(done){ 64 | ginger.down(done); 65 | }); 66 | 67 | }); 68 | -------------------------------------------------------------------------------- /tests/tools/http.js: -------------------------------------------------------------------------------- 1 | var http=require('http'); 2 | var querystring=require('querystring'); 3 | var request=require('request'); 4 | request = request.defaults({jar: true}) 5 | module.exports = function(host, port) { 6 | var baseURL='http://'+host; 7 | if(port){ 8 | baseURL+=':'+port; 9 | } 10 | var executRequest=function(){ 11 | 12 | } 13 | return { 14 | "sendGet": function(url, data, cb) { 15 | var queryData=querystring.stringify(data); 16 | if(queryData){ 17 | url+='?'+queryData; 18 | } 19 | url=baseURL+url; 20 | 21 | 22 | 23 | var req = request.get(url, function(err,data) { 24 | if(err){ 25 | 26 | cb(err); 27 | return; 28 | } 29 | cb(null,{'body':data.body,status:data.statusCode}); 30 | }); 31 | 32 | }, 33 | "sendJSONRpc":function(url,method,id,data,cb){ 34 | var postData={ 35 | 'method':method, 36 | 'id':id 37 | 38 | }; 39 | for(var x in data){ 40 | postData['params'+x]=data[x]; 41 | } 42 | url=baseURL+url; 43 | var r=request.post({uri:url,json:true}, function(err,httpResponse,body) { 44 | if(err){ 45 | cb(err); 46 | return; 47 | } 48 | 49 | if(body.error){ 50 | cb(body.error); 51 | return; 52 | } 53 | cb(null,body.result); 54 | 55 | 56 | }); 57 | r.form(postData); 58 | }, 59 | "sendPost": function(url, postData, cb) { 60 | 61 | url=baseURL+url; 62 | var r=request.post(url, function(err,httpResponse,body) { 63 | if(err){ 64 | cb(err); 65 | return; 66 | } 67 | cb(null,{'body':body,status:httpResponse.statusCode}); 68 | 69 | }); 70 | r.form(postData); 71 | 72 | } 73 | } 74 | }; -------------------------------------------------------------------------------- /tests/core/gateways/HTTP.js: -------------------------------------------------------------------------------- 1 | var chai=require('chai'); 2 | var expect=chai.expect; 3 | var should = chai.should(); 4 | var http=require('http'); 5 | var host='localhost'; 6 | var port=3000; 7 | var Ginger=require(__dirname+'/../../../src/Ginger.js'); 8 | 9 | describe('Gateways HTTP ',function(){ 10 | var httpHelper=require(__dirname+'/../../tools/http')(host,port); 11 | var ginger; 12 | //Initializing the app by path first 13 | before(function(done){ 14 | ginger=new Ginger(); 15 | ginger.setAppPath(__dirname+'/../../exampleApplication/'); 16 | var cb=function(err){ 17 | if(err){ 18 | done(err); 19 | return; 20 | } 21 | var fixtures = require('pow-mongodb-fixtures').connect('gingerTests'); 22 | fixtures.clear(function(err) { 23 | done(err); 24 | }); 25 | } 26 | ginger.up(cb); 27 | }); 28 | describe('HTTP',function(){ 29 | it('Should send an html response on success',function(done){ 30 | httpHelper.sendGet('/hello/hello',null,function(err,data){ 31 | expect(data.body).to.equal('Hello'); 32 | done(err); 33 | }); 34 | }); 35 | it('Should respond with 404 in case of not found URL',function(done){ 36 | httpHelper.sendGet('/URLthatDoesNotExists',null,function(err,data){ 37 | if(err){ 38 | done(err); 39 | return; 40 | } 41 | expect(data.status).to.equal(404); 42 | done(); 43 | }); 44 | }); 45 | it('Should be able to pass params to the URL',function(done){ 46 | httpHelper.sendGet('/sum/index/index',{a:1,b:4},function(err,data){ 47 | expect(data.body).to.equal('5'); 48 | done(err); 49 | }); 50 | }); 51 | 52 | }); 53 | describe('CRUD',function(){ 54 | 55 | it.skip('Should created routes for schemas without controller',function(done){ 56 | var data={'data[email]':'email@email.com', 57 | 'data[active]':true, 58 | 'data[name]':'mr someone', 59 | 'data[password]':'12345'}; 60 | httpHelper.sendGet('/login/create',data,function(err,data){ 61 | 62 | done(err); 63 | }); 64 | }); 65 | }), 66 | after(function(done){ 67 | ginger.down(done); 68 | }); 69 | }); -------------------------------------------------------------------------------- /src/bootstraps/SchemaFactory.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var Schema = mongoose.Schema; 3 | var _=require('lodash'); 4 | var async=require('async'); 5 | module.exports={ 6 | parent:'ginger.bootstraps.Element', 7 | _defaulAppNamespace:'schemas', 8 | _isSingleton:true, 9 | _defaultAppParent:'ginger.mvc.AbstractSchema', 10 | _indexedByName:true, 11 | init : function(engine,params) { 12 | this._super(engine,params); 13 | this._autoCruds=[]; 14 | 15 | }, 16 | _buildIndexData:function(name,namespace,POJO,isApp,isEngine){ 17 | if(!POJO.schemaName){ 18 | POJO.schemaName=name; 19 | } 20 | if(!POJO.schemaDefinitions){ 21 | POJO.schemaDefinitions=Schema; 22 | } 23 | if(POJO._isAuto!==false){ 24 | this._autoCruds.push(name); 25 | this._autoCruds=_.uniq(this._autoCruds); 26 | } 27 | return this._super(name,namespace,POJO,isApp,isEngine); 28 | }, 29 | createSchema:function(name){ 30 | var schema=this.create(name); 31 | return schema.getDbObject(); 32 | }, 33 | getAutoCrudSchemas:function(){ 34 | 35 | return this._autoCruds; 36 | }, 37 | create:function(name,var_args){ 38 | try{ 39 | return this._super.apply(this,arguments); 40 | } 41 | catch(err){ 42 | return this._createEmptySchema.apply(this,arguments); 43 | 44 | } 45 | 46 | 47 | }, 48 | /** 49 | *@todo cleancode 50 | */ 51 | _createEmptySchema:function(name){ 52 | var obj={ 53 | getStructure:function(){ 54 | return new mongoose.Schema({},{strict:false}) 55 | } 56 | } 57 | var askedName=name; 58 | var appName=this._defaulAppNamespace+'.'+name; 59 | var namespaceData= {name:name,namespace:appName,isApp:true,isEngine:false}; 60 | var namespace=namespaceData.namespace; 61 | //If is configurable the first argument is the parasm from the config 62 | if(this.isConfigurable()){ 63 | var params=this._getParams(name,arguments[1]); 64 | arguments[1]=params; 65 | } 66 | this.setAppClass(name,null,null,namespace,obj); 67 | arguments[0]=namespace; 68 | return this._getObject.apply(this,arguments); 69 | 70 | }, 71 | initializeSchemas:function(){ 72 | var functionsToExecute=[]; 73 | for(var x in this._nameMap){ 74 | var obj=this.create(x); 75 | obj.setup(); 76 | } 77 | } 78 | } -------------------------------------------------------------------------------- /src/bootstraps/GatewayFactory.js: -------------------------------------------------------------------------------- 1 | var async=require('async'); 2 | module.exports={ 3 | parent:'ginger.bootstraps.Element', 4 | _defaultEngineNamespace:'ginger.gateways', 5 | _defaulAppNamespace:'gateways', 6 | _configValue:'gateways', 7 | _defaultEngineParent:'ginger.gateways.AbstractGateway', 8 | 9 | _defaultAppParent:'ginger.gateways.AbstractGateway', 10 | _debugGateway:true, 11 | init : function(engine,params) { 12 | this._objectList={}; 13 | this._super(engine,params); 14 | 15 | }, 16 | _getConfig:function(){ 17 | return this._engine.getConfigValue('gateways'); 18 | }, 19 | create:function(name,params){ 20 | 21 | var gateway=this._super(name,params); 22 | name=name.toLowerCase(); 23 | this._objectList[name]=gateway; 24 | return gateway; 25 | }, 26 | getGateway:function(name){ 27 | name=name.toLowerCase(); 28 | return this._objectList[name]; 29 | }, 30 | isGatewayLoaded:function(name){ 31 | name=name.toLowerCase(); 32 | return !!this._objectList[name]; 33 | }, 34 | isGatewayCancelled:function(name){ 35 | var gatewayConfig=this._getConfig(); 36 | if(gatewayConfig && gatewayConfig[name] === false) { 37 | return true; 38 | } 39 | return false; 40 | }, 41 | getGatewaysRequiredComponents:function(){ 42 | var config; 43 | var self=this; 44 | var required=[]; 45 | var gatewayConfig=this._getConfig(); 46 | 47 | for(var name in gatewayConfig){ 48 | if(this.isGatewayCancelled(name)){ 49 | continue; 50 | } 51 | config=gatewayConfig[name]; 52 | if(!config.components){ 53 | continue; 54 | } 55 | config.components.forEach(function(c){ 56 | required.push(c); 57 | }); 58 | 59 | } 60 | return required; 61 | }, 62 | startGateways:function(cb){ 63 | var asyncFunctions = []; 64 | var self = this; 65 | var gatewayConfig=this._getConfig(); 66 | 67 | var funcToCreateGateway = function (name, params) { 68 | 69 | return function (asyncCb) { 70 | var gateway=self.create(name, params); 71 | if(self.isGatewayCancelled(name)){ 72 | 73 | asyncCb(); 74 | } 75 | else{ 76 | gateway.start(asyncCb) 77 | } 78 | } 79 | } 80 | 81 | //Looping trough each gateway to create it assynchronously 82 | for (var name in gatewayConfig ) { 83 | if (this.isGatewayLoaded(name) || this.isGatewayCancelled(name)) { 84 | continue; 85 | } 86 | asyncFunctions.push(funcToCreateGateway(name, gatewayConfig[name])); 87 | } 88 | async.series(asyncFunctions, function (err, res) { 89 | cb(err); 90 | }); 91 | }, 92 | 93 | } -------------------------------------------------------------------------------- /src/bootstraps/ComponentFactory.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @todo clean code especially config part 3 | */ 4 | module.exports={ 5 | parent:'ginger.bootstraps.Element', 6 | _defaultEngineNamespace:'ginger.components', 7 | _defaulAppNamespace:'components', 8 | _defaultEngineParent:'ginger.components.Default', 9 | _defaultAppParent:'ginger.components.Default', 10 | _configValue:'components', 11 | init : function(engine,params) { 12 | this._objectList={}; 13 | this._initializedMap={}; 14 | this._eventFactory=engine.getBootstrap('EventEmitterFactory'); 15 | this._super(engine,params); 16 | var config=this._engine._config.components; 17 | this._config={} 18 | for(var x in config){ 19 | this._config[x.toLowerCase()]=config[x]; 20 | } 21 | }, 22 | _getConfig:function(){ 23 | var config=this._engine.getConfigValue('components'); 24 | var retConfig={} 25 | for(var x in config){ 26 | retConfig[x.toLowerCase()]=config[x]; 27 | } 28 | return retConfig; 29 | }, 30 | isLoaded:function(name){ 31 | var saneName=this._sanitizeName(name); 32 | return !!this._objectList[saneName]; 33 | }, 34 | isCancelled:function(name){ 35 | var saneName=this._sanitizeName(name); 36 | var config=this._getConfig(); 37 | if(config && config[saneName] === false) { 38 | return true; 39 | } 40 | return false; 41 | }, 42 | create:function(name,params){ 43 | var saneName=this._sanitizeName(name); 44 | if(this.isCancelled(name)){ 45 | return false; 46 | } 47 | if(this.isLoaded(name)){ 48 | return this._objectList[saneName]; 49 | } 50 | var component=this._super(name,params); 51 | this._objectList[saneName]=component; 52 | return component; 53 | }, 54 | isComponentInitialized:function(name){ 55 | var saneName=this._sanitizeName(name); 56 | 57 | return !!this._initializedMap[saneName]; 58 | }, 59 | initializeComponents:function(names){ 60 | var self=this; 61 | names.forEach(function(n){ 62 | self.initialize(n); 63 | }); 64 | }, 65 | initialize:function(name,params){ 66 | if(this.isComponentInitialized(name)){ 67 | return true; 68 | } 69 | var component=this.create(name,params); 70 | if(!component ){ 71 | return false; 72 | } 73 | var saneName=this._sanitizeName(name); 74 | var func=component.up.bind(component); 75 | this._engine.addFunctionToLaunchQueue(func); 76 | this._initializedMap[saneName]=true; 77 | return true; 78 | }, 79 | create : function (name, var_args) { 80 | var obj=this._super.apply(this,arguments); 81 | if(obj.hasEvents){ 82 | obj=this._eventFactory.makeObjectEventEmitter(obj); 83 | } 84 | return obj; 85 | } 86 | } -------------------------------------------------------------------------------- /tests/core/mvc/AbstractSchema.js: -------------------------------------------------------------------------------- 1 | var databaseComponent; 2 | var createObjectId = require('pow-mongodb-fixtures').createObjectId; 3 | var _=require('lodash'); 4 | var async=require('async'); 5 | var Utils=require('../../tools/utils'); 6 | var utils=new Utils(); 7 | var expect=utils.expect; 8 | var should = utils.should; 9 | var fixtures=utils.fixtures; 10 | var fixtureData; 11 | var ginger; 12 | var schemaFactory; 13 | var tagSchema; 14 | var validationError; 15 | describe('AbstractSchema',function(){ 16 | before(function(done){ 17 | utils.initServer(function(err){ 18 | if(err){ 19 | done(err); 20 | return; 21 | } 22 | ginger=utils.getServer(); 23 | validationError=ginger.getError('Validation'); 24 | schemaFactory=ginger.getBootstrap('SchemaFactory'); 25 | tagSchema=schemaFactory.create('tags'); 26 | done(); 27 | }); 28 | }); 29 | describe('Validation',function(){ 30 | it('Should validate simple rules',function(){ 31 | var field='numHits'; 32 | var ok=tagSchema.validateField(field,1234); 33 | expect(ok).to.be.true; 34 | var fails=function(){ 35 | var fail=tagSchema.validateField(field,'foo'); 36 | } 37 | expect(fails).to.throw(validationError); 38 | }); 39 | it('Should validate multiple rules',function(){ 40 | var field='name'; 41 | var ok=tagSchema.validateField(field,'shouldpass'); 42 | expect(ok).to.be.true; 43 | var fails=function(){ 44 | tagSchema.validateField(field,'AAA') 45 | } 46 | expect(fails).to.throw(validationError); 47 | fails=function(){ 48 | tagSchema.validateField(field,1234); 49 | } 50 | expect(fails).to.throw(validationError); 51 | 52 | }); 53 | it('Should validate complex rules',function(){ 54 | var field='url'; 55 | var ok=tagSchema.validateField(field,'http://google.com'); 56 | expect(ok).to.be.true; 57 | ok=tagSchema.validateField(field,'https://google.com'); 58 | expect(ok).to.be.true; 59 | /*var fail=tagSchema.validateField(field,'notAUrl'); 60 | expect(fail).to.be.false;*/ 61 | }); 62 | it('Should validate a batch of values',function(){ 63 | var fields={ 64 | 'numHits':3, 65 | 'name':'foo', 66 | 'url':'http://www.google.com' 67 | }; 68 | expect(tagSchema.validate(fields)).to.be.true; 69 | var failed=false; 70 | try{ 71 | var fields={ 72 | 'numHits':'saasas', 73 | 'name':'foo', 74 | 'url':'http://www.google.com' 75 | }; 76 | tagSchema.validate(fields); 77 | 78 | } 79 | catch(err){ 80 | failed=true; 81 | expect(err).to.exit; 82 | expect(err.code).to.equal('VALIDATION'); 83 | } 84 | expect(failed).to.be.true; 85 | }); 86 | }); 87 | after(function(done){ 88 | utils.endServer(done); 89 | }); 90 | }); -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | #*GingerJs* 2 | --- 3 | A HMVC,fully configurable and overwritable Node JS framework. 4 | See the example application on tests for a overview of how it works 5 | 6 | #Configuration 7 | The config/defaultApp.js gives an overview of how the ginger configuration is made. You can select gateways,put general data and configure components by it. 8 | #Inheritance And association 9 | The inheritance of classes can be set by the parent property of the class. 10 | By default all controllers inherit ginger.mvc.AbstractController and all modules inherits ginger.mvc.AbstractModel(Unless they have a schema) 11 | Models with the same name of a controller are associated to them. 12 | If there is a schema and a controller the controller will Inherit AbstractCrudController,if there is a model it's model will inherits AbastractCrudModel. 13 | If there is a schema and no controller or model , they are automatically created unless it's specified to not to. 14 | #Structures 15 | --- 16 | ##The engine 17 | The engine is the main structure to access the components, it is commanded by a config file. 18 | 19 | ##Models 20 | --- 21 | ###General Models 22 | General models are the business logic of the application. 23 | ###CRUD Models 24 | Crud models use schemas to make create,read,update and delete actions on a database 25 | ###Schemas 26 | Schemas are the representation of a mongo database using mongoose 27 | 28 | ##Controllers 29 | --- 30 | ###General Controllers 31 | General controllers receive requests from the gateway and respond to them 32 | ###CRUD Controllers 33 | Crud controllers have default methos fore create,read,update and delete 34 | 35 | ##Gateways 36 | Gateways are the way that a request enter an application. 37 | ###JSON RPC 38 | The json RPC takes a JSONRPC 2.0 request, mask it as an express request and send it to controller. 39 | By default the way the routes are made are: 40 | For each Controller a post route is created. 41 | This post route listen for a method param and send it to the controller action if it exists 42 | ###HTTP (Not fully implemented) 43 | THe HTTP Gateway receives comon HTTP request and send it. 44 | By default the way the routes are made are: 45 | For each controller and for each action a route on the form controller/action is created 46 | ###SocketIo (Not Implemented) 47 | 48 | ##Bootstraps 49 | Bootstraps are basic factories with one exception the app bootStrap 50 | ###App Bootstrap 51 | The app bootstrap loop trough folders initializing the app architecture.It finds controllers,models,schemas ,components and modules.Create authomatic crud controllers and crud models if needed and initialize components. 52 | 53 | ##Modules 54 | Every module have the same folder structure as the application 55 | ##Components 56 | --- 57 | Components are general single purpose structures 58 | ###Middlewares 59 | Middlewares can run before a request , they are express middlewares. 60 | ###Routers 61 | Routers are associated with each gateway and can change how it handles it's urls, which verbs it should use and etc. 62 | ###Default components 63 | ####Session 64 | Session by default set sessions and the cookie parser for expres 65 | ####Log 66 | The log component is a facade for the jsnlog component 67 | ####SocketIO (Not Implemented) 68 | ####Database 69 | The database Compoenent connects to a mongo database using mongoose, creates schema structures and is the final part for executing crud or general DB operations 70 | ####Authentication 71 | The authentication component sets up the passport package 72 | ####Authorization 73 | The authorization component executes rule for access to routes, these things can be configured on the config file 74 | ####Express 75 | THe express component initializes and sets up a new express listening port 76 | 77 | -------------------------------------------------------------------------------- /tests/core/mvc/AbstractCRUDModel.js: -------------------------------------------------------------------------------- 1 | var databaseComponent; 2 | var createObjectId = require('pow-mongodb-fixtures').createObjectId; 3 | var _=require('lodash'); 4 | var async=require('async'); 5 | var Utils=require('../../tools/utils'); 6 | var utils=new Utils(); 7 | var expect=utils.expect; 8 | var should = utils.should; 9 | var fixtures=utils.fixtures; 10 | var dbFixtureData=utils.getFixtureData('categories','login'); 11 | 12 | describe('AbstractCRUDModel',function(){ 13 | var ginger; 14 | //Initializing the app by path first 15 | before(function(done){ 16 | var cb=function(err){ 17 | if(err){ 18 | done(err); 19 | return; 20 | } 21 | ginger=utils.getServer(); 22 | var fixtures=utils.fixtures; 23 | databaseComponent=ginger.getComponent('DataBase'); 24 | 25 | fixtures.clearAndLoad(dbFixtureData,function(err,data){ 26 | done(); 27 | 28 | }); 29 | } 30 | utils.initServer(cb); 31 | }); 32 | describe('Read',function(){ 33 | describe('Populations',function(){ 34 | it.skip('Should be able to set fields to populate on the fields part',function(done){ 35 | var fields={'user.email':true,'user.active':true}; 36 | var id=dbFixtureData.categories.forDevelopers._id.toString(); 37 | var model=ginger.getModel('categories'); 38 | var readCb=function(err,data){ 39 | expect(err).to.not.exist; 40 | done(); 41 | } 42 | model.readById(id,readCb,fields); 43 | }); 44 | }); 45 | }); 46 | describe('List',function(){ 47 | it('Should be able to set the default Fields to list',function(done){ 48 | var model=ginger.getModel('categories'); 49 | var checkIfDefaultFieldsApplyed=function(err,result){ 50 | expect(err).to.not.exist; 51 | expect(result.total).to.be.above(0); 52 | expect(result).to.exist; 53 | expect(result.results).to.exist; 54 | var fields=model._getDefaultFields(); 55 | result.results.forEach(function(r){ 56 | for(var k in fields){ 57 | if(fields[k]){ 58 | expect(r[k]).to.exist; 59 | 60 | } 61 | else{ 62 | expect(r[k]).to.not.exist; 63 | } 64 | 65 | } 66 | }); 67 | done(); 68 | 69 | } 70 | model.list({},false,null,null,null,checkIfDefaultFieldsApplyed); 71 | 72 | }); 73 | }); 74 | describe('AbstractCRUDModel',function(){ 75 | describe('Save',function(){ 76 | before(function(done){ 77 | fixtures.clearAndLoad(dbFixtureData,done); 78 | }); 79 | it('Should save a new data if no primary key is specified',function(done){ 80 | var data={ 81 | 'email':'theEmail@gmail.com', 82 | 'active':true, 83 | 'name':'theName', 84 | 'password':'thePassword', 85 | }; 86 | var model=ginger.getModel('login'); 87 | model.save(data,function(err,saveData){ 88 | for(var x in data){ 89 | expect(saveData[x]).to.equal(data[x]); 90 | } 91 | expect(saveData._id).to.exist; 92 | done(); 93 | }); 94 | }); 95 | it('Should update data the existing data if the primary key is specified',function(done){ 96 | var model=ginger.getModel('login'); 97 | var updateData=dbFixtureData.login.user1; 98 | var newEmail='newEmailForSaved@gmail.com'; 99 | var data={ 100 | 'email':newEmail, 101 | '_id':updateData._id.toString() 102 | }; 103 | var sameKeys=['active','name','password']; 104 | model.save(data,function(err,saveData){ 105 | sameKeys.forEach(function(s){ 106 | expect(saveData[s]).to.equal(updateData[s]); 107 | }); 108 | 109 | expect(saveData._id.toString()).to.equal(updateData._id.toString()); 110 | expect(saveData.email).to.equal(newEmail); 111 | done(); 112 | }); 113 | }); 114 | 115 | }); 116 | 117 | describe('Validate',function(){ 118 | /*it('Should validate data before inserting',function(done){ 119 | 120 | }); 121 | it('Should validate data before updating');*/ 122 | }); 123 | }); 124 | after(function(done){ 125 | utils.endServer(done); 126 | }); 127 | }); -------------------------------------------------------------------------------- /src/mvc/AbstractCRUDModel.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | module.exports= { 3 | parent:'ginger.mvc.AbstractModel', 4 | init: function(engine) { 5 | this._super(engine); 6 | this._dataBase=engine.getComponent('DataBase'); 7 | 8 | }, 9 | insert:function(data,cb){ 10 | this._dataBase.insert(this._schemaName,data,cb); 11 | }, 12 | create:function(data,cb){ 13 | 14 | this._dataBase.create(this._schemaName,data,cb) 15 | }, 16 | update:function(data,search,cb){ 17 | if(_.isEmpty(search)){ 18 | cb(this._engine.getError('InvalidParams','Search can not be empty')); 19 | return; 20 | } 21 | search=this._buildSearch(search); 22 | this._dataBase.update(this._schemaName,data,search,cb) 23 | }, 24 | updateOne:function(data,search,cb){ 25 | if(_.isEmpty(search)){ 26 | cb(this._engine.getError('InvalidParams','Search can not be empty')); 27 | return; 28 | } 29 | search=this._buildSearch(search); 30 | this._dataBase.updateOne(this._schemaName,data,search,cb) 31 | }, 32 | updateById:function(id,data,cb){ 33 | this._dataBase.updateById(this._schemaName,id,data,cb) 34 | }, 35 | read:function(search,cb,fields,options,populate){ 36 | options=this._getDefaultOptionsIfEmpty(options); 37 | populate=this._getDefaultPopulateIfEmpty(populate); 38 | fields=this._getDefaultFieldsIfEmpty(fields); 39 | 40 | this._dataBase.read(this._schemaName,search,cb,fields,options,populate); 41 | }, 42 | readOne:function(search,cb,fields,options,populate){ 43 | options=this._getDefaultOptionsIfEmpty(options); 44 | populate=this._getDefaultPopulateIfEmpty(populate); 45 | fields=this._getDefaultFieldsIfEmpty(fields); 46 | search=this._buildSearch(search); 47 | this._dataBase.readOne(this._schemaName,search,cb,fields,options,populate); 48 | }, 49 | readById:function(id,cb,fields,options,populate){ 50 | options=this._getDefaultOptionsIfEmpty(options); 51 | populate=this._getDefaultPopulateIfEmpty(populate); 52 | fields=this._getDefaultFieldsIfEmpty(fields); 53 | 54 | this._dataBase.readById(this._schemaName,id,cb,fields,options,populate); 55 | }, 56 | destroyById:function(id,cb){ 57 | 58 | this._dataBase.destroyById(this._schemaName,id,cb) 59 | }, 60 | destroy:function(search,cb){ 61 | if(_.isEmpty(search)){ 62 | cb(this._engine.getError('InvalidParams','Search can not be empty')); 63 | return; 64 | } 65 | search=this._buildSearch(search); 66 | this._dataBase.destroy(this._schemaName,search,cb) 67 | }, 68 | _getDefaultOptions:function(){ 69 | return {} 70 | }, 71 | _getDefaultPopulate:function(){ 72 | return null; 73 | }, 74 | _getDefaultPopulateIfEmpty:function(populate){ 75 | if(populate==false){ 76 | return {}; 77 | } 78 | if(_.isEmpty(populate)){ 79 | return this._getDefaultPopulate(); 80 | } 81 | return populate; 82 | }, 83 | _getDefaultOptionsIfEmpty:function(options){ 84 | if(_.isEmpty(options)){ 85 | return this._getDefaultOptions(); 86 | } 87 | return options; 88 | }, 89 | _getDefaultFields:function(){ 90 | return null; 91 | }, 92 | 93 | _getDefaultFieldsIfEmpty:function(fields){ 94 | if(_.isEmpty(fields)){ 95 | return this._getDefaultFields(); 96 | } 97 | return fields; 98 | }, 99 | count:function(search,cb){ 100 | this._dataBase.count(this._schemaName,search,cb); 101 | }, 102 | list:function(search,limit,page,fields,options,cb,populate){ 103 | search=this._buildSearch(search); 104 | fields=this._getDefaultFieldsIfEmpty(fields); 105 | options=this._getDefaultOptionsIfEmpty(options); 106 | populate=this._getDefaultPopulateIfEmpty(populate); 107 | this._dataBase.list(this._schemaName,search,limit,page,fields,options,cb,populate); 108 | }, 109 | save:function(data,cb){ 110 | if(this._hasPrimaryKey(data)){ 111 | var pk=this._getPrimaryKey(data); 112 | delete data[this._getPrimaryKeyField()]; 113 | this.updateById(pk,data,cb); 114 | } 115 | else{ 116 | this.create(data,cb); 117 | } 118 | }, 119 | _buildSearch:function(search){ 120 | return search; 121 | }, 122 | _getPrimaryKeyField:function(){ 123 | return '_id'; 124 | }, 125 | _getPrimaryKey:function(data){ 126 | return data[this._getPrimaryKeyField()]; 127 | }, 128 | _hasPrimaryKey:function(data){ 129 | var pk=this._getPrimaryKey(data); 130 | return !_.isEmpty(pk); 131 | } 132 | }; -------------------------------------------------------------------------------- /src/mvc/DefaultQuery.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | module.exports= { 3 | init: function(engine,search,fields,options,populate) { 4 | this._search={}; 5 | this._field={}; 6 | this._option={}; 7 | this._populate={}; 8 | this._engine=engine; 9 | this._setPopulateFromInput(populate); 10 | this._setSearchFromInput(search); 11 | this._setFieldsFromInput(fields); 12 | this._setOptionsFromInput(options); 13 | }, 14 | getPopulate:function(){ 15 | if(_.isEmpty(this._populate)){ 16 | return []; 17 | } 18 | this._addPopulatePartsToFieldsIfFieldsSet(); 19 | return _.values(this._populate); 20 | }, 21 | getPopulateAsMap:function(){ 22 | if(_.isEmpty(this._populate)){ 23 | return {}; 24 | } 25 | this._addPopulatePartsToFieldsIfFieldsSet(); 26 | return this._populate; 27 | }, 28 | getOptions:function(){ 29 | return this._option; 30 | }, 31 | getFields:function(){ 32 | this._addPopulatePartsToFieldsIfFieldsSet(); 33 | return this._field; 34 | }, 35 | getSearch:function(){ 36 | return this._search; 37 | }, 38 | _isKeyAPopulation:function(key){ 39 | return key.indexOf('+')>-1; 40 | }, 41 | _setPopulateProperty:function(key,value,property){ 42 | var parts=key.split('+'); 43 | var populateKey=parts[0]; 44 | var valueKey=parts[1]; 45 | if(!this._populate[populateKey]){ 46 | this._populate[populateKey]={'path':populateKey}; 47 | } 48 | if(!this._populate[populateKey][property]){ 49 | this._populate[populateKey][property]={}; 50 | } 51 | this._populate[populateKey][property][valueKey]=value; 52 | }, 53 | _setOptionsFromInput:function(options){ 54 | if(!options){ 55 | this._option={}; 56 | return; 57 | } 58 | var parts; 59 | var populateKey; 60 | var optionKey; 61 | var value; 62 | for(var key in options){ 63 | value=options[key]; 64 | if(this._isKeyAPopulation(key)){ 65 | this._setPopulateProperty(key,value,'options'); 66 | } 67 | else{ 68 | this._option[key]=options[key]; 69 | } 70 | } 71 | }, 72 | _setFieldsFromInput:function(fields){ 73 | if(!fields){ 74 | if(!this._field){ 75 | this._field={}; 76 | } 77 | return; 78 | } 79 | if(typeof(fields)=='string'){ 80 | fields=this._getSelectPartFromString(fields); 81 | } 82 | var parts; 83 | var populateKey; 84 | var fieldKey; 85 | var value; 86 | for(var key in fields){ 87 | value=fields[key]; 88 | 89 | if(value){ 90 | if(this._isKeyAPopulation(key)){ 91 | this._setPopulateProperty(key,value,'select'); 92 | } 93 | else{ 94 | this._field[key]=value; 95 | } 96 | } 97 | else{ 98 | this._field[key]=value; 99 | } 100 | } 101 | }, 102 | _getSelectPartFromString:function(str){ 103 | var parts=str.split(' '); 104 | var ret={}; 105 | parts.forEach(function(p){ 106 | ret[p]=true; 107 | }); 108 | return ret; 109 | }, 110 | _setPopulateFromInput:function(populate){ 111 | if(!populate){ 112 | this._populate={}; 113 | return; 114 | } 115 | if(typeof(populate)=='string'){ 116 | this._populate={}; 117 | this._populate[populate]={'path':populate}; 118 | return; 119 | } 120 | for(var k in populate){ 121 | 122 | if(_.isObject(populate[k])){ 123 | this._populate[k]=populate[k]; 124 | if(!this._populate[k].path){ 125 | this._populate[k].path=k; 126 | } 127 | } 128 | else{ 129 | var selectPart=this._getSelectPartFromString(populate[k]); 130 | 131 | this._populate[k]={ 132 | path:k, 133 | select:selectPart 134 | } 135 | 136 | } 137 | } 138 | }, 139 | _addPopulatePartsToFieldsIfFieldsSet:function(){ 140 | if(_.isEmpty(this._field) || _.isEmpty(this._populate)){ 141 | return; 142 | } 143 | //Check if ther isnt a false 144 | for(var x in this._field){ 145 | if(this._field[x]===false){ 146 | return; 147 | } 148 | } 149 | for(populateKey in this._populate){ 150 | if(!(populateKey in this._field)){ 151 | this._field[populateKey]=true; 152 | } 153 | } 154 | }, 155 | _setSearchFromInput:function(search){ 156 | if(!search){ 157 | this._search={}; 158 | } 159 | var parts; 160 | var populateKey; 161 | var searchKey; 162 | var value; 163 | for(var key in search){ 164 | value=search[key]; 165 | if(this._isKeyAPopulation(key)){ 166 | this._setPopulateProperty(key,value,'match'); 167 | } 168 | else{ 169 | this._search[key]=value; 170 | } 171 | } 172 | }, 173 | 174 | }; 175 | -------------------------------------------------------------------------------- /src/components/Authorization.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | var async=require('async'); 3 | module.exports= { 4 | init:function(engine,params) { 5 | this._super(engine,params); 6 | this._permissionResolvers={}; 7 | this._roleResolvers={}; 8 | }, 9 | _parseRules:function(rules){ 10 | var theRule; 11 | var include; 12 | var exclude; 13 | var rulesToAdd=[]; 14 | rules.forEach(function(r){ 15 | theRule=r; 16 | if(Array.isArray(theRule.include)){ 17 | include=theRule.include; 18 | theRule.include={}; 19 | include.forEach(function(i){ 20 | theRule.include[i]=true; 21 | }); 22 | } 23 | else if(theRule.include){ 24 | var obj={}; 25 | obj[theRule.include]=true; 26 | theRule.include=obj; 27 | } 28 | else{ 29 | theRule.include=[]; 30 | } 31 | 32 | if(Array.isArray(theRule.exclude)){ 33 | exclude=theRule.exclude; 34 | theRule.exclude={}; 35 | exclude.forEach(function(i){ 36 | theRule.exclude[i]=true; 37 | }); 38 | } 39 | else if(theRule.exclude){ 40 | var obj={}; 41 | obj[theRule.exclude]=true; 42 | theRule.exclude=obj; 43 | } 44 | else{ 45 | theRule.exclude=[]; 46 | } 47 | 48 | 49 | if(!theRule.role){ 50 | theRule.role=[]; 51 | } 52 | else if(!Array.isArray(theRule.role)){ 53 | theRule.role=[theRule.role]; 54 | } 55 | if(!theRule.permission){ 56 | theRule.permission=[]; 57 | } 58 | else if(!Array.isArray(theRule.permission)){ 59 | theRule.permission=[theRule.permission]; 60 | } 61 | rulesToAdd.push(theRule); 62 | }); 63 | return rulesToAdd; 64 | }, 65 | addRoleResolver:function(role,resolver){ 66 | this._roleResolvers[role]=resolver; 67 | }, 68 | addPermissionResolver:function(permission,resolver){ 69 | this._permissionResolvers[permission]=resolver; 70 | }, 71 | setRules:function(rules){ 72 | this._rules=this._parseRules(rules); 73 | }, 74 | getAccessRequirements:function(controller,action){ 75 | var ret={role:[],permission:[]}; 76 | var self=this; 77 | if(this._rules){ 78 | this._rules.forEach(function(r){ 79 | //First we see if it's included 80 | if(self._isRuleApplicable(r,controller,action)){ 81 | ret.role=ret.role.concat(r.role); 82 | ret.permission=ret.permission.concat(r.permission); 83 | } 84 | 85 | }); 86 | } 87 | return ret; 88 | }, 89 | _isRuleApplicable:function(rule,controller,action){ 90 | var isIncluded=rule.include['*'] || rule.include[controller] || rule.include[controller+'#'+action]; 91 | if(!isIncluded){ 92 | return false; 93 | } 94 | var isExcluded=rule.exclude['*'] || rule.exclude[controller] || rule.exclude[controller+'#'+action]; 95 | if(isExcluded){ 96 | return false; 97 | } 98 | return true; 99 | }, 100 | isUserAllowed:function(user,req,controller,action,cb){ 101 | var requirements=this.getAccessRequirements(controller,action); 102 | 103 | var funcsToExecute=[]; 104 | var self=this; 105 | if(!_.isEmpty(requirements.role)){ 106 | requirements.role.forEach(function(r){ 107 | funcsToExecute.push(function(asyncCb){ 108 | self._checkIfUserHasRole(user,req,r,asyncCb); 109 | }); 110 | }); 111 | } 112 | if(!_.isEmpty(requirements.permission)){ 113 | requirements.permission.forEach(function(p){ 114 | funcsToExecute.push(function(asyncCb){ 115 | self._checkIfUserHasPermission(user,req,p,asyncCb); 116 | }); 117 | }); 118 | } 119 | if(_.isEmpty(funcsToExecute)){ 120 | cb(null,true); 121 | return; 122 | }; 123 | async.parallel(funcsToExecute,function(err,data){ 124 | if(err){ 125 | cb(err); 126 | return; 127 | } 128 | cb(null,true); 129 | }); 130 | 131 | }, 132 | _checkIfUserHasRole:function(user,req,role,cb){ 133 | if(this._roleResolvers[role]){ 134 | this._roleResolvers[role](user,req,role,cb); 135 | return; 136 | } 137 | if(!!user && !!user.roles && user.roles.indexOf(role)>=0){ 138 | cb(null,true); 139 | return; 140 | } 141 | cb(this._engine.getError('Forbidden')); 142 | }, 143 | _checkIfUserHasPermission:function(user,req,permission,cb){ 144 | if(this._permissionResolvers[permission]){ 145 | this._permissionResolvers[permission](user,req,permission,cb); 146 | return; 147 | } 148 | if(!!user && !!user.permissions && user.permissions.indexOf(permission)>=0){ 149 | cb(null,true); 150 | return; 151 | } 152 | cb(this._engine.getError('Forbidden')); 153 | }, 154 | 155 | }; 156 | 157 | -------------------------------------------------------------------------------- /src/gateways/AbstractGateway.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | module.exports={ 3 | _engine:null, 4 | _controllerFactory:null, 5 | _params:null, 6 | _name:null, 7 | 8 | init: function(engine,params) { 9 | this._configParams(engine,params); 10 | this._log=engine.getComponent('Log'); 11 | this._authorization=engine.getComponent('Authorization'); 12 | }, 13 | start:function(cb){ 14 | this._initExpress(); 15 | this.buildRoutes(cb); 16 | 17 | }, 18 | _getRouterHandlerComponent:function(){ 19 | return this._engine.getRouterHandler('Default'); 20 | 21 | }, 22 | _configParams:function(engine,params){ 23 | this._controllerFactory=engine.getBootstrap('ControllerFactory'); 24 | this._engine=engine; 25 | this._params=params; 26 | this._routerHandlerComponent=this._getRouterHandlerComponent(); 27 | }, 28 | end:function(cb){ 29 | cb(); 30 | }, 31 | _handleControllerRoutes:function(controllerData){ 32 | var controllerObj=this._createController(controllerData); 33 | var actions=controllerObj.getActions(); 34 | for(var x in actions){ 35 | this._handleControllerAction(x,controllerObj,controllerData); 36 | } 37 | }, 38 | _handleControllerAction:function(action,controllerObj,controllerData){ 39 | var prefix=this._params.prefix || ''; 40 | 41 | var url=this._routerHandlerComponent.buildUrl(prefix,controllerData.name,action); 42 | this._addRouteToApp(action,url,controllerObj,controllerData); 43 | 44 | }, 45 | _createController:function(controllerData){ 46 | return this._controllerFactory.create(controllerData.name,this._engine); 47 | }, 48 | buildRoutes:function(cb) { 49 | var controllerList=this._controllerFactory._getNameMap(); 50 | for(var index in controllerList){ 51 | this._handleControllerRoutes(controllerList[index]); 52 | } 53 | cb(); 54 | }, 55 | _getHTTPVerb:function(routeVerb){ 56 | if(routeVerb){ 57 | return routeVerb; 58 | } 59 | return 'get'; 60 | }, 61 | _getDefaultMiddlewares:function(){ 62 | return []; 63 | }, 64 | _addRouteToApp:function(action,url,controllerObj,controllerData){ 65 | var routeData=this._routerHandlerComponent.getRouteData(controllerData.name,action); 66 | var verb=this._getHTTPVerb(routeData.verb); 67 | var middlewares=this._getDefaultMiddlewares(); 68 | var self=this; 69 | var controllerFunction=this._getControllerFunction(controllerData.name,action,controllerObj); 70 | if(routeData.middlewares){ 71 | middlewares=middlewares.concat(routeData.middlewares); 72 | } 73 | if(middlewares.length==0){ 74 | this._app[verb](url,function(req,res){ 75 | controllerFunction(req,res); 76 | }); 77 | 78 | } 79 | else{ 80 | var argsToAdd=[url]; 81 | middlewares.forEach(function(m){ 82 | if(typeof(m)=='function'){ 83 | argsToAdd.push(m); 84 | } 85 | else{ 86 | var middleware=this._engine.getComponent('middleware.'+m); 87 | argsToAdd.push(middleware.getMiddleware(controllerObj,self,controllerData)); 88 | 89 | } 90 | },this); 91 | 92 | argsToAdd.push(controllerFunction); 93 | this._app[verb].apply(this._app,argsToAdd); 94 | } 95 | }, 96 | 97 | _sendError:function(req,res,error){ 98 | res.send(error); 99 | }, 100 | _canUserAccessAction:function(req,res,controller,action,cb){ 101 | this._authorization.isUserAllowed(req.user,req,controller,action,cb); 102 | }, 103 | _getControllerFunction:function(controller,action,controllerObj){ 104 | var actionFunctionName=controllerObj.getActionFunctionByName(action); 105 | var actionFunction=controllerObj[actionFunctionName].bind(controllerObj); 106 | var self=this; 107 | 108 | return function(req,res){ 109 | try{ 110 | self._canUserAccessAction(req,res,controller,action,function(err,canAccess){ 111 | if(err){ 112 | self._sendError(req,res,err); 113 | return; 114 | } 115 | actionFunction(req,res); 116 | }); 117 | } 118 | catch(err){ 119 | self._sendError(req,res,err) 120 | } 121 | } 122 | }, 123 | _initExpress:function(){ 124 | var self=this; 125 | var express=this._engine.getComponent('Express'); 126 | if(!express){ 127 | throw new Error('The express component is required to run the HTTP gateway'); 128 | } 129 | self._expressComponent=express; 130 | self._app=self._expressComponent.getApp(); 131 | 132 | } 133 | } -------------------------------------------------------------------------------- /src/mvc/AbstractSchema.js: -------------------------------------------------------------------------------- 1 | var mongoose = require('mongoose'); 2 | var validator=require('validator'); 3 | var _=require('lodash'); 4 | module.exports={ 5 | 6 | _engine:null, 7 | _isAuto:true, 8 | schemaName:null, 9 | schemaDefinitions:null, 10 | init: function(engine) { 11 | this._engine=engine; 12 | this._name=this.schemaName; 13 | this._isLoaded=false; 14 | this._dbObject=null; 15 | this._schemaDefinitions=this.schemaDefinitions; 16 | this._validatorRules=this.getValidators(); 17 | }, 18 | setup:function(){ 19 | if(!this._isLoaded){ 20 | this._createDatabaseObject(); 21 | } 22 | }, 23 | validateField:function(field,value){ 24 | var validationFunc=this._getRuleForField(field); 25 | if(!validationFunc){ 26 | return true; 27 | } 28 | return validationFunc(value); 29 | }, 30 | _getRuleForField:function(field){ 31 | var ruleData=this._validatorRules[field] 32 | if(ruleData){ 33 | return this._buildRule(ruleData,field); 34 | 35 | } 36 | return null; 37 | }, 38 | _buildValidationError:function(field,ruleName,value){ 39 | return this._engine.getError('Validation',null,null,{'field':field,rule:ruleName,value:value}); 40 | }, 41 | _buildSingleErroFromErrorArray:function(errors){ 42 | var fieldArray=[]; 43 | var data={}; 44 | errors.forEach(function(e){ 45 | fieldArray.push() 46 | 47 | if(!data[e.data.field]){ 48 | data[e.data.field]=[]; 49 | } 50 | fieldArray.push(e.data.field); 51 | data[e.data.field].push({'rule':e.data.rule,'value':e.data.value}); 52 | }); 53 | var fieldsNames=fieldArray.join(','); 54 | var err= this._engine.getError('Validation','The following fields did not pass validation '+fieldsNames,null,{'fieldMap':data}); 55 | return err; 56 | }, 57 | _buildRule:function(ruleData,field){ 58 | var self=this; 59 | var type=typeof(ruleData); 60 | if(Array.isArray(ruleData)){ 61 | var funcsToExecute=[]; 62 | ruleData.forEach(function(r){ 63 | funcsToExecute.push(self._buildRule(r,field)); 64 | }); 65 | return function(value){ 66 | var success=true; 67 | var errors=[]; 68 | for(var x in funcsToExecute){ 69 | try{ 70 | funcsToExecute[x](value); 71 | } 72 | catch(err){ 73 | errors.push(err); 74 | } 75 | 76 | } 77 | if(!_.isEmpty(errors)){ 78 | throw self._buildSingleErroFromErrorArray(errors); 79 | } 80 | return true; 81 | } 82 | }; 83 | switch(type) { 84 | case 'string': 85 | return function(value){ 86 | if(!validator[ruleData](value)){ 87 | throw self._buildValidationError(field,ruleData ,value); 88 | } 89 | return true; 90 | } 91 | break; 92 | case 'object':{ 93 | return function(value){ 94 | var success=true; 95 | for(var ruleName in ruleData){ 96 | var params=_.clone(ruleData[ruleName]); 97 | params.unshift(value); 98 | if(!validator[ruleName].apply(validator,params)){ 99 | throw self._buildValidationError(field,ruleName,value); 100 | // success=false; 101 | break; 102 | } 103 | } 104 | return success; 105 | 106 | 107 | } 108 | } 109 | 110 | } 111 | }, 112 | validate:function(input){ 113 | var errors=[]; 114 | for(var field in input){ 115 | try{ 116 | this.validateField(field,input[field]) 117 | } 118 | catch(err){ 119 | errors.push(err); 120 | } 121 | 122 | } 123 | 124 | if(errors.length>0){ 125 | throw this._buildSingleErroFromErrorArray(errors); 126 | } 127 | return true; 128 | }, 129 | isAuto:function(){ 130 | return !!this._isAuto; 131 | }, 132 | getStructure:function(schema){ 133 | throw this._engine.getError('NotImplemented','Get structure is not implemented for '+this._name+' pleas implement it'); 134 | }, 135 | getValidators:function(){ 136 | return {}; 137 | }, 138 | getDbObject:function(){ 139 | if(!this._isLoaded){ 140 | this.setup(); 141 | } 142 | return this._dbObject; 143 | }, 144 | _createDatabaseObject:function(){ 145 | var structure=this.getStructure(this._schemaDefinitions); 146 | try{ 147 | 148 | this._dbObject= mongoose.model(this._name,structure,this._name); 149 | } 150 | catch(err){ 151 | this._dbObject= mongoose.model(this._name); 152 | } 153 | this._isLoaded=true; 154 | this._engine.addFunctionToCloseQueue(this.clearSchema.bind(this)); 155 | 156 | }, 157 | clearSchema:function(cb){ 158 | if(this._isLoaded){ 159 | delete mongoose.models[this._name]; 160 | delete mongoose.modelSchemas[this._name]; 161 | } 162 | this._isLoaded=false; 163 | cb(); 164 | } 165 | }; -------------------------------------------------------------------------------- /tests/core/engine.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Suite of tests for the ginger 3 | */ 4 | var chai=require('chai'); 5 | chai.config.includeStack =true; 6 | var expect=chai.expect; 7 | var Ginger=require(__dirname+'/../../src/Ginger.js'); 8 | var should = chai.should(); 9 | 10 | describe('engine',function(){ 11 | describe('vanilla setup',function(){ 12 | var ginger=new Ginger(); 13 | it('Should go up smoothly',function(done){ 14 | ginger.up(done); 15 | 16 | }); 17 | it('should have a default config',function(){ 18 | expect(ginger._config).not.to.be.empty; 19 | describe('gateway',function(){ 20 | it('Should have the default gateways',function(){ 21 | var gateway=ginger.getGateway('HTTP'); 22 | expect(gateway).to.exist; 23 | gateway=ginger.getGateway('JSONRPC'); 24 | expect(gateway).to.exist; 25 | gateway=ginger.getGateway('SocketIO'); 26 | expect(gateway).to.exist; 27 | }); 28 | }); 29 | }); 30 | describe('component',function(){ 31 | it('Should Have the default componets',function(){ 32 | var component=ginger.getComponent('Express'); 33 | expect(component).to.exist; 34 | }); 35 | 36 | }); 37 | it('Should go down smoothly',function(done){ 38 | ginger.down(done); 39 | }); 40 | 41 | }); 42 | describe('preLaunch',function(){ 43 | it('Should not run if preLaunch throws an error',function(done){ 44 | var ginger=new Ginger(); 45 | var preLaunch=function(ginger,cb){ 46 | cb(new Error('don\'t start')); 47 | }; 48 | ginger.setPreLaunchFunction(preLaunch); 49 | ginger.up(function(err){ 50 | expect(err).to.exist; 51 | ginger.down(done); 52 | }); 53 | 54 | }); 55 | it('Should run if prelaunch allows',function(done){ 56 | var ginger=new Ginger(); 57 | var preLaunch=function(ginger,cb){ 58 | cb(); 59 | }; 60 | ginger.setPreLaunchFunction(preLaunch); 61 | ginger.up(function(err){ 62 | expect(err).to.not.exist; 63 | ginger.down(done); 64 | }); 65 | 66 | }); 67 | }); 68 | 69 | 70 | // it('Should fail if you give a required connection'); 71 | describe('#config',function(){ 72 | describe('#custom',function(){ 73 | var ginger=new Ginger(); 74 | 75 | var exampleConfig={ 76 | 'myVar':'foo', 77 | 'components':{ 78 | 'DataBase':false 79 | }, 80 | 'gateways':{ 81 | 'HTTP':false 82 | } 83 | 84 | 85 | }; 86 | before(function(done){ 87 | ginger.setConfig(exampleConfig); 88 | ginger.up(done); 89 | 90 | }); 91 | it('Add data to the config',function(){ 92 | expect(ginger._config).not.to.be.empty; 93 | expect(ginger._config['myVar']).to.equal('foo'); 94 | }); 95 | 96 | describe('#component',function(){ 97 | it('Should be able to remove a component by setting it to false',function(){ 98 | expect(ginger.isComponentCancelled('DataBase')).to.be.true; 99 | }); 100 | }); 101 | 102 | describe('#gateway',function(){ 103 | it('Should be able to remove a gateway by setting it to false',function(){ 104 | var gateway=ginger.isGatewayCancelled('HTTP'); 105 | expect(gateway).to.be.true; 106 | }); 107 | }); 108 | 109 | after(function(done){ 110 | ginger.down(done); 111 | }); 112 | 113 | }); 114 | 115 | }); 116 | 117 | describe('component',function(){ 118 | var ginger=new Ginger(); 119 | before(function(done){ 120 | ginger.up(done); 121 | 122 | }); 123 | it.skip('Should overwritte a component after it\'s loaded by setting the component',function(done){ 124 | 125 | var getComponentCb=function(err,component){ 126 | if(err){ 127 | done(err); 128 | return; 129 | } 130 | expect(component).to.exist; 131 | ginger.setComponent('Log',{'foo':'bar'}); 132 | ginger.getComponent('Log',function(err,component){ 133 | expect(component.foo).to.equal('bar'); 134 | done(err); 135 | }); 136 | 137 | } 138 | ginger.getComponent('Log',getComponentCb); 139 | 140 | }); 141 | after(function(done){ 142 | ginger.down(done); 143 | }); 144 | }); 145 | describe('Error',function(){ 146 | var ginger=new Ginger(); 147 | before(function(done){ 148 | ginger.up(done); 149 | 150 | }); 151 | it('Should have the default errors',function(){ 152 | var errors=['Default','Internal','Parse','InvalidParams','InvalidRequest','NotFound','Validation']; 153 | for(var x in errors){ 154 | expect(ginger.getError(errors[x])).not.empty; 155 | expect(ginger.getError(errors[x]).code).not.empty; 156 | expect(ginger.getError(errors[x]).message).not.empty; 157 | 158 | } 159 | }); 160 | after(function(done){ 161 | ginger.down(done); 162 | }); 163 | }); 164 | 165 | 166 | }); -------------------------------------------------------------------------------- /src/gateways/JSONRPC.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | var ERRORS_MAP={ 3 | 'PARSE_ERROR':'-32700', 4 | 'INVALID_REQUEST':'-32600', 5 | 'NOT_FOUND':'-32001', 6 | 'INVALID_PARAMS':'-32602', 7 | 'INTERNAL':'-32603', 8 | 'VALIDATION':'-32003', 9 | 'FORBIDDEN':'-32002', 10 | 'METHOD_NOT_FOUND':'-32601', 11 | 'SERVER_ERROR':'-32000'//-32000 to -32099 default implementations errors 12 | }; 13 | module.exports = { 14 | parent: 'ginger.gateways.HTTP', 15 | _buildRoute: function(controllerName, action, controllerData) {}, 16 | _buildError:function(error,id){ 17 | var code=ERRORS_MAP[error.code]; 18 | if(!code){ 19 | code=ERRORS_MAP['INTERNAL']; 20 | } 21 | message=error.message; 22 | var data=error.data; 23 | if(!error.data){ 24 | data=error.message; 25 | } 26 | var jsonError= {code:code,message:message,data:data} 27 | return {id:id,error:jsonError,"version":"2.0",isError:true}; 28 | }, 29 | _buildResult:function(result,id){ 30 | return {id:id,result:result,"version":"2.0"}; 31 | }, 32 | _getDefaultMiddlewares:function(){ 33 | return ['ValidJSONRPC','JSONMiddlewareProxy']; 34 | }, 35 | _sendError:function(req,res,error){ 36 | var statusCode=200; 37 | var response=this.buildResponse(req.query.id,error); 38 | res.setHeader('Content-Type', 'application/json'); 39 | if(res.originalSend){ 40 | res.originalSend.call(res,response); 41 | return; 42 | } 43 | else{ 44 | res.status(200).send(response); 45 | } 46 | }, 47 | _getRouterHandlerComponent:function(){ 48 | return this._engine.getRouterHandler('JSONRPC'); 49 | }, 50 | _getSendProxy:function(res,id){ 51 | var self=this; 52 | var oldSend=res.send; 53 | res.originalSend=oldSend; 54 | return function(body){ 55 | if (2 == arguments.length) { 56 | // res.send(body, status) backwards compat 57 | if ('number' != typeof body && 'number' == typeof arguments[1]) { 58 | statusCode = arguments[1]; 59 | } else { 60 | statusCode = body; 61 | body = arguments[1]; 62 | } 63 | } 64 | var response; 65 | var result; 66 | var error; 67 | if(body){ 68 | /** 69 | * @todo find better way to identify error 70 | */ 71 | if(!!body.name && body.name.indexOf('Error')!=-1){ 72 | error=body; 73 | error.data=body.errors; 74 | } 75 | else if(body.isError===true){ 76 | delete body.isError; 77 | error=body; 78 | } 79 | else{ 80 | result= body; 81 | if(!result){ 82 | result=true; 83 | } 84 | 85 | } 86 | } 87 | else{ 88 | result=true; 89 | } 90 | var statusCode=200; 91 | response=self.buildResponse(id,error,result); 92 | res.setHeader('Content-Type', 'application/json'); 93 | oldSend.call(res,response); 94 | } 95 | }, 96 | _getHTTPVerb:function(routeVerb){ 97 | return 'post'; 98 | }, 99 | _handleControllerRoutes:function(controllerData){ 100 | var controllerObj=this._createController(controllerData); 101 | this._handleControllerAction(null,controllerObj,controllerData); 102 | }, 103 | 104 | buildResponse:function(id,error,result){ 105 | var resp; 106 | if(error){ 107 | 108 | resp= this._buildError(error,id); 109 | 110 | } 111 | else{ 112 | resp=this._buildResult(result,id); 113 | } 114 | return JSON.stringify(resp); 115 | }, 116 | _addRouteToApp:function(action,url,controllerObj,controllerData){ 117 | var verb='post';//always post 118 | //Just the default middlewares the remaining are handled by the proxy middleware 119 | var middlewares=this._getDefaultMiddlewares(); 120 | var argsToAdd=[url]; 121 | var self=this; 122 | middlewares.forEach(function(m){ 123 | var middleware=this._engine.getComponent('middleware.'+m); 124 | argsToAdd.push(middleware.getMiddleware(controllerObj,self,controllerData)); 125 | },this); 126 | 127 | 128 | 129 | argsToAdd.push(function(req,res){ 130 | var action=req.JSONRPC.method; 131 | var controllerFunction=self._getControllerFunction(controllerData.name,action,controllerObj); 132 | controllerFunction(req,res); 133 | }); 134 | this._app[verb].apply(this._app,argsToAdd); 135 | 136 | }, 137 | 138 | } -------------------------------------------------------------------------------- /tests/core/gateways/JSONRpc.js: -------------------------------------------------------------------------------- 1 | var chai=require('chai'); 2 | var expect=chai.expect; 3 | var should = chai.should(); 4 | var http=require('http'); 5 | var host='localhost'; 6 | var port=3000; 7 | var Ginger=require(__dirname+'/../../../src/Ginger'); 8 | 9 | describe('Gateway JsonRPC',function(){ 10 | var httpHelper=require(__dirname+'/../../tools/http')(host,port); 11 | before(function(done){ 12 | ginger=new Ginger(); 13 | ginger.setAppPath(__dirname+'/../../exampleApplication/'); 14 | ginger.up(done); 15 | }); 16 | 17 | it('Should execute the request sucessfully',function(done){ 18 | 19 | httpHelper.sendPost('/JSONRPC/hello',{'method':'hello','id':'1234'},function(err,data){ 20 | if(err){ 21 | done(err); 22 | return; 23 | } 24 | var response=JSON.parse(data.body); 25 | var statusCode=data.status; 26 | 27 | expect(response.id).to.equal('1234'); 28 | 29 | 30 | expect(response.result).to.equal('Hello'); 31 | expect(statusCode).to.equal(200); 32 | done(err); 33 | }); 34 | 35 | }); 36 | 37 | it('Should send a view as JSONRPC'); 38 | describe('Errors',function(){ 39 | it('Should respond to error -32700 on Parse Error'); 40 | it('Should respond to error -32600 on Invalid Request',function(done){ 41 | 42 | httpHelper.sendPost('/JSONRPC/hello',{'method':'hello'},function(err,data){ 43 | if(err){ 44 | done(err); 45 | return; 46 | } 47 | var response=JSON.parse(data.body); 48 | var statusCode=data.status; 49 | expect(response.error).to.exist 50 | expect(response.error.code).to.equal('-32600'); 51 | expect(statusCode).to.equal(200); 52 | done(err); 53 | }); 54 | 55 | 56 | }); 57 | it('Should respond to error -32601 on Method Not found',function(done){ 58 | var postData={'method':'dontExist',"id":"1234"}; 59 | httpHelper.sendPost('/JSONRPC/hello',postData,function(err,data){ 60 | if(err){ 61 | done(err); 62 | return; 63 | } 64 | console.log(data.body); 65 | var response=JSON.parse(data.body); 66 | var statusCode=data.status; 67 | expect(response.error).to.exist 68 | expect(response.error.code).to.equal('-32601'); 69 | expect(statusCode).to.equal(200); 70 | done(err); 71 | }); 72 | 73 | 74 | }); 75 | it('Should respond to error -32002 on Forbidden',function(done){ 76 | var postData={'method':'hello',"id":"1234"}; 77 | httpHelper.sendPost('/JSONRPC/restricted',postData,function(err,data){ 78 | if(err){ 79 | done(err); 80 | return; 81 | } 82 | var response=JSON.parse(data.body); 83 | var statusCode=data.status; 84 | expect(response.error).to.exist; 85 | expect(response.error.code).to.equal('-32002'); 86 | expect(statusCode).to.equal(200); 87 | done(err); 88 | }); 89 | 90 | 91 | }); 92 | 93 | it('Should respond to error -32602 on Invalid Params'); 94 | it('Should respond to error -32603 on Internal error',function(done){ 95 | var postData={'method':'error',"id":"1234"}; 96 | httpHelper.sendPost('/JSONRPC/ExceptionTrhower',postData,function(err,data){ 97 | if(err){ 98 | done(err); 99 | return; 100 | } 101 | var response=JSON.parse(data.body); 102 | var statusCode=data.status; 103 | expect(response.error).to.exist; 104 | expect(response.error.code).to.equal('-32603'); 105 | expect(statusCode).to.equal(200); 106 | done(err); 107 | }); 108 | 109 | }); 110 | 111 | 112 | }); 113 | describe('Login',function(){ 114 | it('Should not have access to a area without the propper login',function(done){ 115 | var cb=function(err,data){ 116 | expect(err).to.exist; 117 | done(); 118 | } 119 | httpHelper.sendJSONRpc('/JSONRPC/restricted','hello',1234,{},cb); 120 | }); 121 | it('Should not be able to login with the wrong password',function(done){ 122 | var data={ 123 | '[user]':'notTheRightUser', 124 | '[password]':'irrelevant' 125 | }; 126 | var cb=function(err,data){ 127 | expect(err).to.exist; 128 | expect(err.code).to.equal('-32002'); 129 | done(); 130 | } 131 | httpHelper.sendJSONRpc('/JSONRPC/authentication','login',1234,data,cb); 132 | }); 133 | it('Should be able to login with the right password',function(done){ 134 | var data={ 135 | '[user]':'foo', 136 | '[password]':'bar' 137 | }; 138 | var cb=function(err,data){ 139 | expect(err).to.not.exist; 140 | expect(data.name).to.equal('johnson'); 141 | 142 | expect(data.email).to.equal('johnson@johnson.com'); 143 | done(); 144 | } 145 | httpHelper.sendJSONRpc('/JSONRPC/authentication','login',1234,data,cb); 146 | }); 147 | it('Should not access to a area with the propper login',function(done){ 148 | var cb=function(err,data){ 149 | expect(err).to.not.exist; 150 | expect(data).to.equal('Acessing restricted page'); 151 | done(); 152 | } 153 | httpHelper.sendJSONRpc('/JSONRPC/restricted','hello',1234,{},cb); 154 | }); 155 | it('Should be able to logout',function(done){ 156 | var cb=function(err,data){ 157 | expect(err).to.not.exist; 158 | expect(data).to.equal('success'); 159 | done(); 160 | } 161 | httpHelper.sendJSONRpc('/JSONRPC/authentication','logout',1234,{},cb); 162 | }); 163 | }); 164 | after(function(done){ 165 | ginger.down(done); 166 | }); 167 | 168 | }); 169 | -------------------------------------------------------------------------------- /tests/core/mvc/DefaultQuery.js: -------------------------------------------------------------------------------- 1 | var databaseComponent; 2 | var createObjectId = require('pow-mongodb-fixtures').createObjectId; 3 | var _=require('lodash'); 4 | var async=require('async'); 5 | var Utils=require('../../tools/utils'); 6 | var utils=new Utils(); 7 | var expect=utils.expect; 8 | var should = utils.should; 9 | var fixtures=utils.fixtures; 10 | var dbFixtureData=utils.getFixtureData('categories','login'); 11 | var ginger; 12 | var queryFactory; 13 | var comparePopulate=function(query,inputPopulate){ 14 | var populate=query.getPopulateAsMap(); 15 | 16 | if(typeof(inputPopulate)=='string'){ 17 | expect(populate[inputPopulate]).to.exist; 18 | expect(populate[inputPopulate].path).to.equal(inputPopulate); 19 | return; 20 | } 21 | for(var k in inputPopulate){ 22 | if(typeof(inputPopulate[k])=='string'){ 23 | expect(populate[k].path).to.equal(k); 24 | var populateParts=query._getSelectPartFromString(inputPopulate[k]); 25 | for(var populateKey in populateParts){ 26 | expect(populate[k].select[populateKey]).to.equal(populateParts[populateKey]); 27 | } 28 | } 29 | else{ 30 | for(fieldKey in inputPopulate[k]){ 31 | if(_.isObject(inputPopulate[k][fieldKey])){ 32 | var isEqual=_.isEqual(populate[k][fieldKey],inputPopulate[k][fieldKey]); 33 | expect(isEqual).to.be.true; 34 | } 35 | else{ 36 | 37 | expect(populate[k][fieldKey]).to.equal(inputPopulate[k][fieldKey]); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | describe('DefaultQuery',function(){ 44 | before(function(done){ 45 | utils.initServer(function(err){ 46 | if(err){ 47 | done(err); 48 | return; 49 | } 50 | ginger=utils.getServer(); 51 | done(); 52 | }); 53 | }); 54 | 55 | it('Should be able to set the normal options ',function(done){ 56 | var search={'fieldA':'equal comething'}; 57 | var field={'fieldB':true,'fieldC':true}; 58 | var option={'sort':{'name':1}}; 59 | var populate={'populated':'name _id','fullPopulated':{'path':'fullPopulated','select':{'bar':true}}}; 60 | 61 | var query=ginger.getQuery(search,field,option,populate); 62 | expect(_.isEqual(query._search,search)).to.be.true; 63 | var sentFieldKeys=Object.keys(field); 64 | var fieldKeysIntersection=_.intersection(Object.keys(query._field),sentFieldKeys); 65 | expect(_.isEqual(fieldKeysIntersection,sentFieldKeys)).to.be.true; 66 | expect(_.isEqual(query._option,option)).to.be.true; 67 | comparePopulate(query,populate); 68 | done(); 69 | }); 70 | describe('Population',function(){ 71 | it('Should be able to set populate as a string',function(done){ 72 | var populate='something'; 73 | var query=ginger.getQuery(null,null,null,populate); 74 | comparePopulate(query,populate); 75 | done(); 76 | }); 77 | it('Should be able to set population from a field,while respecting both field and population',function(done){ 78 | var field={ 79 | 'populated+someField':true, 80 | 'populated+foo':true, 81 | 'normalField':true 82 | } 83 | var populate={ 84 | 'anotherPopulated':'name _id enabled', 85 | 'populated':'someOtherField' 86 | 87 | } 88 | var query=ginger.getQuery(null,field,null,populate); 89 | var populate=query.getPopulateAsMap(); 90 | for(var k in field){ 91 | if(k.indexOf('+')>-1){ 92 | var parts=k.split('+'); 93 | expect(query._field[parts[0]]).to.equal(true); 94 | expect(populate[parts[0]]).to.exist; 95 | expect(populate[parts[0]].select[parts[1]]).to.equal(field[k]); 96 | } 97 | else{ 98 | 99 | expect(query._field[k]).to.equal(field[k]); 100 | } 101 | } 102 | comparePopulate(query,populate); 103 | done(); 104 | }); 105 | it('Should be able to set population from a option, while respecting both option and population',function(done){ 106 | var options={ 107 | 'sort':{'name':1}, 108 | 'populated+limit':5, 109 | 'populated+sort':{'name':-1}, 110 | 'someOtherField+limit':10 111 | } 112 | var populate={ 113 | 'populated':'someOtherField', 114 | 'yetAnotherPopulation':'foo bar baz' 115 | } 116 | var query=ginger.getQuery(null,null,options,populate); 117 | var populate=query.getPopulateAsMap(); 118 | 119 | for(var k in options){ 120 | var value=options[k]; 121 | if(k.indexOf('+')>-1){ 122 | var parts=k.split('+'); 123 | expect(populate[parts[0]]).to.exist; 124 | if(_.isObject(value)){ 125 | expect(_.isEqual(populate[parts[0]].options[parts[1]],value)).to.be.true; 126 | } 127 | else{ 128 | expect(populate[parts[0]].options[parts[1]]).to.equal(value); 129 | } 130 | } 131 | else{ 132 | 133 | expect(_.isEqual(query._option[k],value)).to.be.true; 134 | } 135 | } 136 | comparePopulate(query,populate); 137 | done(); 138 | }); 139 | it('Should be able to set population from a search, while respecting both search and population',function(done){ 140 | var search={'fieldA':'equal comething','populated+fieldB':14}; 141 | var populate={ 142 | 'populated':'someOtherField' 143 | } 144 | var query=ginger.getQuery(search,null,null,populate); 145 | var populate=query.getPopulateAsMap(); 146 | 147 | for(var k in search){ 148 | var value=search[k]; 149 | if(k.indexOf('+')>-1){ 150 | var parts=k.split('+'); 151 | expect(populate[parts[0]]).to.exist; 152 | if(_.isObject(value)){ 153 | expect(_.isEqual(populate[parts[0]].match[parts[1]],value)).to.be.true; 154 | } 155 | else{ 156 | expect(populate[parts[0]].match[parts[1]]).to.equal(value); 157 | } 158 | } 159 | else{ 160 | 161 | expect(_.isEqual(query._search[k],value)).to.be.true; 162 | } 163 | } 164 | comparePopulate(query,populate); 165 | 166 | done(); 167 | }); 168 | 169 | }); 170 | after(function(done){ 171 | utils.endServer(done); 172 | }); 173 | }); -------------------------------------------------------------------------------- /tests/core/components/Authorization.js: -------------------------------------------------------------------------------- 1 | var authorizationComponent; 2 | var async=require('async'); 3 | var _=require('lodash'); 4 | var Utils=require('../../tools/utils'); 5 | var utils=new Utils(); 6 | var expect=utils.expect; 7 | var should = utils.should; 8 | var fixtures=utils.fixtures; 9 | var rulesToParse=[ 10 | //Apply to everybody except all actions of login and #action of someController 11 | { 12 | include:'*', 13 | exclude:['login','someController#action'], 14 | role:'authenticated', 15 | permission:'permissionC' 16 | 17 | }, 18 | //This controller should have multiple permissions 19 | { 20 | include:['someController#action'], 21 | permission:['permissionA','permissionB'] 22 | }, 23 | { 24 | include:['someController','anotherController'], 25 | exclude:['someController#action'], 26 | role:'manager' 27 | }, 28 | { 29 | include:['someController#dynamicAction'], 30 | permission:'dynamicPermission', 31 | role:'dynamicRole' 32 | }, 33 | ]; 34 | describe('Component Authorization',function(){ 35 | var ginger; 36 | //Initializing the app by path first 37 | before(function(done){ 38 | var cb=function(err){ 39 | if(err){ 40 | done(err); 41 | return; 42 | } 43 | ginger=utils.getServer(); 44 | authorizationComponent=ginger.getComponent('Authorization'); 45 | authorizationComponent.setRules(rulesToParse); 46 | authorizationComponent.addPermissionResolver('dynamicPermission',function(user,req,role,cb){ 47 | if(req.query.a='b'){ 48 | cb(null,true); 49 | return; 50 | } 51 | cb(ginger.getError('Forbidden')); 52 | }); 53 | authorizationComponent.addRoleResolver('dynamicRole',function(user,req,role,cb){ 54 | if(req.query.a='b'){ 55 | cb(null,true); 56 | return; 57 | } 58 | cb(ginger.getError('Forbidden')); 59 | }); 60 | done(); 61 | 62 | } 63 | utils.initServer(cb); 64 | }); 65 | describe('Initialization',function(){ 66 | it('Should be able to get the rules for a given controller action',function(){ 67 | var access=authorizationComponent.getAccessRequirements('anyController','anyAction'); 68 | 69 | expect(access.role.indexOf('authenticated')).to.be.at.least(0); 70 | expect(access.permission.indexOf('permissionC')).to.be.at.least(0); 71 | 72 | access=authorizationComponent.getAccessRequirements('login','anyAction'); 73 | expect(access.role.indexOf('authenticated')).to.equal(-1); 74 | expect(access.permission.indexOf('permissionC')).to.equal(-1); 75 | 76 | access=authorizationComponent.getAccessRequirements('someController','action'); 77 | expect(access.role.indexOf('authenticated')).to.equal(-1); 78 | expect(access.role.indexOf('manager')).to.equal(-1); 79 | expect(access.permission.indexOf('permissionC')).to.equal(-1); 80 | expect(access.permission.indexOf('permissionA')).to.be.at.least(0); 81 | expect(access.permission.indexOf('permissionB')).to.be.at.least(0); 82 | 83 | access=authorizationComponent.getAccessRequirements('someController','anotherAction'); 84 | expect(access.role.indexOf('manager')).to.be.at.least(0); 85 | }); 86 | it('Should be able to check static permissions',function(done){ 87 | var user={ 88 | 'permissions':['permissionC'] 89 | } 90 | authorizationComponent._checkIfUserHasPermission(user,null,'permissionC',function(err,hasPermission){ 91 | expect(hasPermission).to.be.true; 92 | authorizationComponent._checkIfUserHasPermission(user,null,'permissionD',function(err,hasPermission){ 93 | expect(err).to.be.exist; 94 | expect(err.code).to.equal('FORBIDDEN'); 95 | done(); 96 | }); 97 | }); 98 | }); 99 | it('Should be able to check static roles',function(done){ 100 | var user={ 101 | 'roles':['roleA'] 102 | } 103 | authorizationComponent._checkIfUserHasRole(user,null,'roleA',function(err,hasPermission){ 104 | expect(hasPermission).to.be.true; 105 | authorizationComponent._checkIfUserHasRole(user,null,'roleB',function(err,hasPermission){ 106 | expect(err).to.be.exist; 107 | expect(err.code).to.equal('FORBIDDEN'); 108 | done(); 109 | }); 110 | }); 111 | }); 112 | 113 | it('Should be able to check dynamic permissions',function(done){ 114 | var user={} 115 | authorizationComponent._checkIfUserHasPermission(user,{'query':{'a':'b'}},'dynamicPermission',function(err,hasPermission){ 116 | expect(hasPermission).to.be.true; 117 | done(); 118 | }); 119 | }); 120 | it('Should be able to check dynamic roles',function(done){ 121 | var user={} 122 | authorizationComponent._checkIfUserHasRole(user,{'query':{'a':'b'}},'dynamicRole',function(err,hasPermission){ 123 | expect(hasPermission).to.be.true; 124 | done(); 125 | }); 126 | }); 127 | it('Should be able to check if a user has access to controllers and actions',function(done){ 128 | var user={ 129 | 'permissions':['permissionC'], 130 | 'roles':['manager','authenticated'] 131 | } 132 | var req={'query':{'a':'b'}}; 133 | var expectError=function(asyncCb){ 134 | return function(err,data){ 135 | expect(err).to.exist; 136 | expect(err.code).to.equal('FORBIDDEN'); 137 | asyncCb(); 138 | } 139 | } 140 | var expectSuccess=function(asyncCb){ 141 | return function(err,data){ 142 | expect(err).to.not.exist; 143 | expect(data).to.be.true; 144 | asyncCb(); 145 | } 146 | } 147 | var userb={ 148 | 'permissions':['permissionA','permissionB'], 149 | } 150 | var functionsToExecute=[ 151 | function(asyncCb){ 152 | authorizationComponent.isUserAllowed(user,req,'someController','dynamicAction',expectSuccess(asyncCb)); 153 | }, 154 | function(asyncCb){ 155 | authorizationComponent.isUserAllowed(user,req,'someController','action',expectError(asyncCb)); 156 | }, 157 | function(asyncCb){ 158 | authorizationComponent.isUserAllowed(userb,req,'someController','action',expectSuccess(asyncCb)); 159 | } 160 | ]; 161 | async.parallel(functionsToExecute,function(err,data){ 162 | done(); 163 | }) 164 | }); 165 | }); 166 | 167 | after(function(done){ 168 | utils.endServer(done); 169 | }); 170 | }); -------------------------------------------------------------------------------- /tests/core/application.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @todo move to a fixture data 3 | * @type {Object} 4 | */ 5 | var fixtureData={ 6 | 'schemas':{ 7 | 'auto':['sum.sum','login','categories'], 8 | 'nonAuto':['posts','tags'] 9 | } 10 | }; 11 | describe('Application',function(){ 12 | var chai=require('chai'); 13 | chai.config.includeStack =true; 14 | var expect=chai.expect; 15 | var Ginger=require(__dirname+'/../../src/Ginger.js'); 16 | var should = chai.should 17 | var ginger; 18 | describe('Config',function(){ 19 | it('Should infer the config by environment',function(done){ 20 | done(); 21 | 22 | }); 23 | }); 24 | describe('up() \'by path\'',function(){ 25 | //Initializing the app by path first 26 | before(function(done){ 27 | ginger=new Ginger(); 28 | ginger.setAppPath(__dirname+'/../exampleApplication/'); 29 | ginger.up(function(err,data){ 30 | done(err); 31 | }); 32 | }); 33 | it('Should have the config by the application',function(){ 34 | expect(ginger.getConfig().name).to.equal("Example"); 35 | }); 36 | 37 | it('Should have loaded all the modules',function(){ 38 | expect(ginger.hasModule('sum')).to.be.true; 39 | expect(ginger.hasModule('sum.multiplication')).to.be.true; 40 | }); 41 | 42 | it('Should overwrite a gateway without needing to config',function(){ 43 | expect(ginger.getGateway('HTTP').iAmOverwritten).to.be.true; 44 | }); 45 | it('Should be able to overwride a component',function(){ 46 | ginger.getComponent('Log',function(err,component){ 47 | expect(component.iAmOverwritten).to.be.true; 48 | }); 49 | }); 50 | 51 | describe('Error',function(){ 52 | 53 | it('Should be able to overwritte errors',function(){ 54 | var error=ginger.getError('NotFound'); 55 | expect(error.iAmOverwritten).to.be.true; 56 | }); 57 | 58 | }); 59 | describe('Model',function(){ 60 | it('Should have loaded all models',function(){ 61 | expect(ginger.hasModel('hello')).to.be.true; 62 | expect(ginger.hasModel('sum.index')).to.be.true; 63 | expect(ginger.hasModel('sum.multiplication.index')).to.be.true; 64 | 65 | }); 66 | it('Should be able to get a model by name',function(){ 67 | var model=ginger.getModel('Hello'); 68 | expect(model).to.be.an('object'); 69 | expect(model).to.not.be.an('undefined'); 70 | expect(model).to.exist; 71 | expect(model.sayHello()).to.equal('Hello'); 72 | }); 73 | it('Should be able to get a nested model by name',function(){ 74 | var model=ginger.getModel('sum.index'); 75 | expect(model).to.be.an('object'); 76 | expect(model).to.not.be.an('undefined'); 77 | expect(model).to.exist; 78 | expect(model.sum(1,2)).to.equal(3); 79 | }); 80 | it('Should be able to get a model as a non singleton instance',function(done){ 81 | var model=ginger.getModel('Hello'); 82 | model.someProperty=10; 83 | model=ginger.getModel('Hello'); 84 | expect(model.someProperty).to.be.empty; 85 | done(); 86 | }); 87 | }); 88 | describe('Controller',function(){ 89 | it('Should be able to inherits the default controller',function(){ 90 | var type=typeof(ginger.getController('Hello').getActions); 91 | expect(type).to.equal('function'); 92 | }); 93 | it('Should have a defaultModel if a model with the same name exists',function(){ 94 | var controller=ginger.getController('hello'); 95 | expect(controller).to.exist; 96 | var model=controller.getModel(); 97 | expect(model).to.exist; 98 | expect(model.sayHello()).to.equal('Hello'); 99 | 100 | }); 101 | it('Should create the avaiable actions',function(){ 102 | var controller=ginger.getController('hello'); 103 | var type=typeof(ginger.getController('Hello').helloAction); 104 | expect(type).to.equal('function'); 105 | 106 | 107 | }); 108 | it('Should have loaded all controllers',function(){ 109 | expect(ginger.hasController('hello')).to.be.true; 110 | expect(ginger.hasController('sum.index')).to.be.true; 111 | expect(ginger.hasController('sum.multiplication.index')).to.be.true; 112 | 113 | }); 114 | }); 115 | describe('Middleware',function(){ 116 | it('Should be able to get a middleware',function(done){ 117 | expect(ginger.getComponent('middleware.SomeMiddleware')).to.not.be.empty; 118 | done(); 119 | }); 120 | }); 121 | describe('Schema',function(){ 122 | it('Should have loaded all schemas',function(){ 123 | fixtureData.schemas.auto.forEach(function(s){ 124 | expect(ginger.hasSchema(s)).to.be.true; 125 | }); 126 | fixtureData.schemas.nonAuto.forEach(function(s){ 127 | expect(ginger.hasSchema(s)).to.be.true; 128 | }); 129 | 130 | }); 131 | it('Should have created non existend models for automatic cruds',function(){ 132 | fixtureData.schemas.auto.forEach(function(s){ 133 | expect(ginger.hasModel(s)).to.be.true; 134 | }); 135 | 136 | }); 137 | it('Should not have created non existents models for non automatic cruds',function(){ 138 | fixtureData.schemas.nonAuto.forEach(function(s){ 139 | expect(ginger.hasModel(s)).to.be.false; 140 | }); 141 | 142 | }); 143 | it('Should have created non existent controllers for automatic cruds',function(){ 144 | fixtureData.schemas.auto.forEach(function(s){ 145 | expect(ginger.hasController(s)).to.be.true; 146 | }); 147 | }); 148 | it('Should not have created non existent controllers for non automatic cruds',function(){ 149 | fixtureData.schemas.nonAuto.forEach(function(s){ 150 | expect(ginger.hasController(s)).to.be.false; 151 | }); 152 | }); 153 | it('Should have promoted existent controllers to CRUD controllers',function(){ 154 | var controllerFactory=ginger.getBootstrap('ControllerFactory'); 155 | var el=controllerFactory.getElementByName('sum.Sum'); 156 | expect(el.pojo.parent).to.equal('ginger.mvc.AbstractCRUDController'); 157 | 158 | }); 159 | it('Should have promoted existent models to CRUD models',function(){ 160 | var el=ginger.getBootstrap('ModelFactory').getElementByName('sum.Sum'); 161 | expect(el.pojo.parent).to.equal('ginger.mvc.AbstractCRUDModel'); 162 | 163 | }); 164 | 165 | }); 166 | describe('gateway',function(){ 167 | it('Should give the same Reponse content independant of the gateway'); 168 | }); 169 | after(function(done){ 170 | ginger.down(done); 171 | }); 172 | }); 173 | }); 174 | -------------------------------------------------------------------------------- /src/components/DataBase.js: -------------------------------------------------------------------------------- 1 | var mongoose=require('mongoose'); 2 | var _=require('lodash'); 3 | var async=require('async'); 4 | var DEFAULT_LIST_LIMIT=5; 5 | module.exports={ 6 | init: function(engine,params) { 7 | this._isConnected=false; 8 | this._isClosed=false; 9 | this._engine=engine; 10 | this._params=params; 11 | this._schemaFactory=engine.getBootstrap('SchemaFactory'); 12 | this._mongoose=mongoose; 13 | 14 | }, 15 | getMongoose:function(){ 16 | return this._mongoose; 17 | }, 18 | up:function(cb){ 19 | if(this._isConnected){ 20 | cb(); 21 | return; 22 | } 23 | this._isConnected=true; 24 | this._isClosed=false; 25 | var mongoConfig=this._params['mongo']; 26 | var uri=this._buildConection(mongoConfig.url,mongoConfig.port,mongoConfig.base); 27 | mongoose.connect(uri,function(err,data){ 28 | cb(err); 29 | }); 30 | this._addFunctionToClose(); 31 | 32 | }, 33 | _buildConection:function(url,port,base){ 34 | return url+':'+port+'/'+base; 35 | 36 | }, 37 | getSchema:function(schemaName){ 38 | return this._schemaFactory.create(schemaName); 39 | }, 40 | getSchemaDbObject:function(schemaName){ 41 | return this._schemaFactory.createSchema(schemaName); 42 | }, 43 | update:function(schemaName,data,searchData,cb){ 44 | try{ 45 | this._validateDataToSave(schemaName,data,'update'); 46 | var Schema=this.getSchemaDbObject(schemaName); 47 | options = { multi: true }; 48 | 49 | Schema.update(searchData,data,options,cb); 50 | } 51 | catch(err){ 52 | cb(err); 53 | } 54 | }, 55 | updateOne:function(schemaName,data,searchData,cb){ 56 | try{ 57 | this._validateDataToSave(schemaName,data,'update'); 58 | var Schema=this.getSchemaDbObject(schemaName); 59 | Schema.findOneAndUpdate(searchData,data,cb); 60 | } 61 | catch(err){ 62 | cb(err); 63 | } 64 | }, 65 | 66 | updateById:function(schemaName,id,data,cb){ 67 | try{ 68 | this._validateDataToSave(schemaName,data,'update'); 69 | var plainCb=this._getPlainObjectCb(cb,true); 70 | this.updateRawById(schemaName,data,id,plainCb); 71 | } 72 | catch(err){ 73 | cb(err); 74 | } 75 | 76 | }, 77 | updateRawById:function(schemaName,data,id,cb){ 78 | var Schema=this.getSchemaDbObject(schemaName); 79 | var self=this; 80 | Schema.findByIdAndUpdate(id,data, function (err, schemaObj) { 81 | if (err) { 82 | 83 | cb(err); 84 | return; 85 | } 86 | cb(null,schemaObj); 87 | }); 88 | 89 | }, 90 | _validateDataToSave:function(schemaName,data,context){ 91 | var Schema=this.getSchema(schemaName); 92 | Schema.validate(data); 93 | }, 94 | insert:function(schemaName,data,cb){ 95 | var SchemaDbObject=this.getSchemaDbObject(schemaName); 96 | SchemaDbObject.collection.insert(data,cb); 97 | }, 98 | create:function(schemaName,data,cb){ 99 | var self=this; 100 | try{ 101 | this._validateDataToSave(schemaName,data,'create'); 102 | var SchemaDbObject=this.getSchemaDbObject(schemaName); 103 | var schemaObj=new SchemaDbObject(data); 104 | schemaObj.save(function(err){ 105 | self._getPlainObjectCb(cb)(err,schemaObj); 106 | }); 107 | } 108 | catch(err){ 109 | cb(err); 110 | } 111 | }, 112 | _getPlainObjectCb:function(cb,notFoundIfEmpty){ 113 | var self=this; 114 | return function(err,data){ 115 | if(err){ 116 | return cb(self._getErroFromMongooseError(err)); 117 | } 118 | if(_.isEmpty(data)){ 119 | if(notFoundIfEmpty){ 120 | 121 | return cb(self._engine.getError('NotFound')); 122 | } 123 | return cb(null,data); 124 | } 125 | if(Array.isArray(data)){ 126 | var ret=[]; 127 | data.forEach(function(d){ 128 | ret.push(d.toObject()); 129 | }); 130 | cb(null,ret); 131 | } 132 | 133 | if(data.toObject){ 134 | return cb(null,data.toObject()); 135 | } 136 | } 137 | }, 138 | readRawById:function(schemaName,id,cb,fields,options,populate){ 139 | var query=this._engine.getQuery(null,fields,options,populate); 140 | var Schema=this.getSchemaDbObject(schemaName); 141 | var search=Schema.findById(id); 142 | this._executeSearch(search,query,cb); 143 | }, 144 | readRaw:function(schemaName,searchData,cb,fields,options,populate){ 145 | var query=this._engine.getQuery(searchData,fields,options,populate); 146 | var Schema=this.getSchemaDbObject(schemaName); 147 | var search=Schema.find(query.getSearch()); 148 | this._executeSearch(search,query,cb); 149 | }, 150 | readRawOne:function(schemaName,searchData,cb,fields,options,populate){ 151 | var query=this._engine.getQuery(searchData,fields,options,populate); 152 | var Schema=this.getSchemaDbObject(schemaName); 153 | var search=Schema.findOne(query.getSearch()); 154 | this._executeSearch(search,query,cb); 155 | }, 156 | _getErroFromMongooseError:function(err){ 157 | switch(err.name){ 158 | case 'CastError': 159 | return this._engine.getError('InvalidParams',err.message,null,err.errors); 160 | break; 161 | default: 162 | return this._engine.getError('Internal',err.message,null,err.errors); 163 | break; 164 | } 165 | 166 | }, 167 | _executeSearch:function(search,query,cb){ 168 | var fields=query.getFields(); 169 | if(!_.isEmpty(fields)){ 170 | search.select(fields); 171 | } 172 | var options=query.getOptions(); 173 | if(!_.isEmpty(options)){ 174 | search.setOptions(options); 175 | } 176 | var populate=query.getPopulate(); 177 | if(!_.isEmpty(populate)){ 178 | if(Array.isArray(populate)){ 179 | populate.forEach(function(p){ 180 | search.populate(p); 181 | }); 182 | } 183 | else{ 184 | switch(typeof(populate)){ 185 | case 'object': 186 | for(var k in populate){ 187 | if(_.isEmpty(populate[k])){ 188 | search.populate(k); 189 | } 190 | else{ 191 | search.populate(k,populate[k]); 192 | } 193 | } 194 | break; 195 | case 'string': 196 | search.populate(populate); 197 | break; 198 | } 199 | } 200 | }; 201 | var self=this; 202 | var searchCb=function(err,data){ 203 | if(err){ 204 | cb(err); 205 | return; 206 | } 207 | cb(err,data); 208 | } 209 | search.exec(searchCb); 210 | 211 | }, 212 | readById:function(schemaName,id,cb,fields,options,populate){ 213 | 214 | this.readRawById(schemaName,id,this._getPlainObjectCb(cb,true),fields,options,populate); 215 | }, 216 | read:function(schemaName,searchData,cb,fields,options,populate){ 217 | 218 | this.readRaw(schemaName,searchData,this._getPlainObjectCb(cb),fields,options,populate); 219 | }, 220 | readOne:function(schemaName,searchData,cb,fields,options,populate){ 221 | 222 | this.readRawOne(schemaName,searchData,this._getPlainObjectCb(cb,true),fields,options,populate); 223 | }, 224 | destroy:function(schemaName,searchData,cb){ 225 | var Schema=this.getSchemaDbObject(schemaName); 226 | Schema.remove(searchData,cb); 227 | }, 228 | destroyById:function(schemaName,id,cb){ 229 | var Schema=this.getSchemaDbObject(schemaName); 230 | Schema.findByIdAndRemove(id,cb); 231 | }, 232 | 233 | _addFunctionToClose:function(){ 234 | var self=this 235 | var func=function(cb){ 236 | self.end(cb); 237 | } 238 | this._engine.addFunctionToCloseQueue(func); 239 | }, 240 | end:function(cb){ 241 | if(this._isConnected && !this._isClosed){ 242 | 243 | this._isConnected=false; 244 | 245 | mongoose.connection.close(); 246 | this._isClosed=true; 247 | } 248 | cb(); 249 | 250 | }, 251 | list:function(schemaName,search,limit,page,fields,options,cb,populate){ 252 | 253 | if(parseInt(limit)===-1 || limit==='false' || limit===false){ 254 | limit=this._params.maxAllowedLimit; 255 | } 256 | else if(!limit || typeof(limit)=='undefined'){ 257 | limit=DEFAULT_LIST_LIMIT; 258 | } 259 | if(!page || typeof(page)=='undefined'){ 260 | page=0; 261 | } 262 | if(limit){ 263 | if(!options){ 264 | options={}; 265 | } 266 | options.limit=limit; 267 | options.skip=limit*page; 268 | } 269 | var self=this; 270 | var read=function(asyncCb){ 271 | self.read(schemaName,search,asyncCb,fields,options,populate); 272 | 273 | }; 274 | var total=function(asyncCb){ 275 | self.count(schemaName,search,asyncCb); 276 | }; 277 | async.parallel({ 278 | 'total':total, 279 | 'results':read 280 | },cb); 281 | 282 | }, 283 | count:function(schemaName,search,cb){ 284 | var Schema=this.getSchemaDbObject(schemaName); 285 | if(search){ 286 | Schema.count(search,cb); 287 | } 288 | else{ 289 | Schema.count(cb); 290 | } 291 | }, 292 | ObjectId:function(value){ 293 | if(!value){ 294 | return mongoose.Types.ObjectId(); 295 | } 296 | if(typeof(value)=='object'){ 297 | return value; 298 | } 299 | return mongoose.Types.ObjectId(value); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/bootstraps/Element.js: -------------------------------------------------------------------------------- 1 | var _=require('lodash'); 2 | module.exports={ 3 | parent:'ginger.bootstraps.Default', 4 | _defaultEngineNamespace:null, 5 | _defaulAppNamespace:null, 6 | _configValue:null, 7 | _isSingleton:true, 8 | _defaultEngineParent:null, 9 | _defaultAppParent:null, 10 | _isLazy:false, 11 | /** 12 | * Whether if this behaves as a name index to the app element OR the framework element as fallback 13 | * @type {Boolean} 14 | */ 15 | _indexedByName:true, 16 | init : function(engine,params) { 17 | this._super(engine,params); 18 | this._nameMap={}; 19 | }, 20 | addNamespace:function(namespace,path){ 21 | if(namespace){ 22 | namespace=this._buildNamespace(this._defaulAppNamespace,namespace); 23 | } 24 | else{ 25 | namespace=this._defaulAppNamespace; 26 | } 27 | this._classFactory.setNamespaceDir(namespace,path); 28 | }, 29 | setEmptyAppClass:function(name,parent){ 30 | if(!parent){ 31 | parent=this._defaultAppParent; 32 | } 33 | var POJO={ 34 | parent:parent 35 | } 36 | 37 | var appNamespace=this._buildNamespace(this._defaulAppNamespace,name); 38 | this._addToIndex(name,appNamespace,POJO,true); 39 | 40 | return appNamespace; 41 | 42 | }, 43 | _sanitizeName:function(name){ 44 | if(_.isEmpty(name)){ 45 | 46 | return ''; 47 | } 48 | return name.toLowerCase(); 49 | }, 50 | setAppClass:function(name,path,parentNamespace,fullNamespace,POJO){ 51 | 52 | var defaultParent=null; 53 | name=this._buildNamespace(parentNamespace,name); 54 | 55 | if(this.hasDefaultParent()){ 56 | if(this._defaultEngineNamespace){ 57 | 58 | var engineName=this._defaultEngineNamespace+'.'+name; 59 | if(this._classFactory.classFileExists(engineName)){ 60 | if(!this._classFactory.isClassSet(engineName)){ 61 | this.setEngineClass(name); 62 | } 63 | defaultParent=engineName; 64 | } 65 | } 66 | if(!defaultParent && !!this._defaultAppParent){ 67 | defaultParent=this._defaultAppParent; 68 | } 69 | } 70 | if(!POJO){ 71 | POJO={}; 72 | if(path){ 73 | 74 | POJO=this._getPojo(path,defaultParent); 75 | } 76 | else{ 77 | POJO=this._classFactory.getClassFileContents(fullNamespace); 78 | } 79 | } 80 | 81 | if(!POJO.parent){ 82 | POJO=this._setDefaultParentOnPOJO(POJO,defaultParent); 83 | 84 | } 85 | else { 86 | var isEngineClass=POJO.parent.indexOf(this._defaultEngineNamespace)===0; 87 | //POJO=this._setDefaultParentOnPOJO(POJO,defaultParent); 88 | if(isEngineClass && !this._classFactory.isClassSet(POJO.parent)){ 89 | var engineName=POJO.parent.replace(this._defaultEngineNamespace+'.',""); 90 | this.setEngineClass(engineName); 91 | 92 | } 93 | 94 | } 95 | 96 | var appNamespace=this._buildNamespace(this._defaulAppNamespace,name); 97 | this._addToIndex(name,appNamespace,POJO,true); 98 | 99 | return appNamespace; 100 | }, 101 | _buildIndexData:function(name,namespace,POJO,isApp,isEngine){ 102 | return {namespace:namespace,isApp:!!isApp,isEngine:!!isEngine,pojo:POJO,name:name,isIndexed:true}; 103 | }, 104 | _setClassOnNamespace:function(namespace,POJO){ 105 | this._classFactory.setClassPojo(namespace,POJO,true); 106 | }, 107 | _addToIndex:function(name,namespace,POJO,isApp,isEngine){ 108 | var saneName=this._sanitizeName(name); 109 | if(this._indexedByName){ 110 | if(isApp || !this.hasElement(name)){ 111 | 112 | this._nameMap[saneName]=this._buildIndexData(name,namespace,POJO,isApp,isEngine); 113 | } 114 | POJO=this._nameMap[saneName].pojo; 115 | } 116 | this._setClassOnNamespace(namespace,POJO); 117 | }, 118 | hasDefaultParent:function(){ 119 | return !!this._defaultEngineNamespace || !!this._defaultAppParent; 120 | }, 121 | hasElement:function(name){ 122 | var saneName=this._sanitizeName(name); 123 | return !!this._nameMap[saneName]; 124 | }, 125 | setEngineClass:function(name){ 126 | var engineName=this._defaultEngineNamespace+'.'+name; 127 | if(!this._classFactory.classFileExists(engineName)){ 128 | throw new Error("The class "+engineName+" does not exist"); 129 | } 130 | /** 131 | * @todo use isClassCreatable ddTo 132 | */ 133 | if(this._classFactory.isClassPojoSet(engineName)){ 134 | return engineName; 135 | } 136 | var POJO=this._classFactory.getClassFileContents(engineName); 137 | var defaultParent=this._defaultEngineParent; 138 | if(engineName==this._defaultEngineParent){ 139 | defaultParent=null; 140 | } 141 | POJO=this._setDefaultParentOnPOJO(POJO,defaultParent); 142 | this._addToIndex(name,engineName,POJO,false,true); 143 | return engineName; 144 | }, 145 | 146 | _getParams:function(name,params){ 147 | if(!params && this._configValue){ 148 | var values=this._engine.getConfigValue(this._configValue); 149 | if(values[name]){ 150 | params=values[name]; 151 | } 152 | } 153 | return params; 154 | }, 155 | _isParentTheDefault:function(parent){ 156 | if(!this._defaultAppParent){ 157 | return false; 158 | } 159 | if(this._defaultAppParent==parent){ 160 | return true; 161 | } 162 | return false; 163 | 164 | }, 165 | 166 | mergeObjectPojo:function(name,pojo){ 167 | var element=this.getElementByName(name); 168 | var namespace=element.namespace; 169 | var originalPojo=this._classFactory.getClassPojo(namespace) 170 | pojo=_.extend(originalPojo, pojo) 171 | element.pojo=pojo; 172 | this._overwrideElementData(name,element); 173 | this._classFactory.setClassPojo(namespace,pojo,true); 174 | return true; 175 | }, 176 | changeObjectParent:function(name,newParent){ 177 | var element=this.getElementByName(name); 178 | var namespace=element.namespace; 179 | var pojo=this._classFactory.getClassPojo(namespace) 180 | if(_.isEmpty(pojo)){ 181 | throw new Error(name+' was not found!,searched on the '+namespace+' namespace'); 182 | } 183 | 184 | if(!this._isParentTheDefault(pojo.parent)){ 185 | return false; 186 | } 187 | pojo.parent=newParent; 188 | element.pojo=pojo; 189 | this._overwrideElementData(name,element); 190 | this._classFactory.setClassPojo(namespace,pojo,true); 191 | return true; 192 | }, 193 | _overwrideElementData:function(name,data){ 194 | var saneName=this._sanitizeName(name); 195 | this._nameMap[saneName]=data; 196 | }, 197 | _getObject:function(name,var_args){ 198 | var askedName=name; 199 | 200 | var newArgs=[]; 201 | newArgs.push(askedName); 202 | //By default all objects have the engine as the first argument 203 | arguments[0]=this._engine; 204 | for(var x in arguments){ 205 | newArgs.push(arguments[x]); 206 | } 207 | var classFactory=this._classFactory; 208 | 209 | if(this.isClassSingleton(askedName)){ 210 | return classFactory.getSingletonObject.apply(classFactory,newArgs); 211 | } 212 | return classFactory.createObject.apply(classFactory,newArgs); 213 | }, 214 | isClassSingleton:function(name){ 215 | var pojoData=this._classFactory.getClassPojo(name); 216 | if(!!pojoData && _.isBoolean(pojoData.isSingleton) ){ 217 | return pojoData.isSingleton; 218 | } 219 | return !!this._isSingleton; 220 | }, 221 | isConfigurable:function(){ 222 | return !! this._configValue; 223 | }, 224 | _getNameMap:function(){ 225 | return this._nameMap; 226 | }, 227 | getElementByName:function(name){ 228 | var saneName=this._sanitizeName(name); 229 | return this._nameMap[saneName]; 230 | 231 | }, 232 | _getNamespaceDataFromName:function(name){ 233 | if(this._indexedByName && this.hasElement(name)){ 234 | return this.getElementByName(name); 235 | } 236 | var appName=this._defaulAppNamespace+'.'+name; 237 | if(this._classFactory.classFileExists(appName)){ 238 | return {name:name,namespace:appName,isApp:true,isEngine:false}; 239 | } 240 | var engineName; 241 | if(this.hasDefaultParent()){ 242 | engineName=this._defaultEngineNamespace+'.'+name; 243 | if(this._classFactory.classFileExists(engineName)){ 244 | return {name:name,namespace:engineName,isApp:false,isEngine:true}; 245 | } 246 | 247 | } 248 | if(this._classFactory.classFileExists(name)){ 249 | return {name:name,namespace:name,isApp:false,isEngine:false}; 250 | } 251 | var errorMsg='The object '+name+' could not be found searched in , '+appName; 252 | if(this.hasDefaultParent()){ 253 | errorMsg+=' and '+engineName; 254 | } 255 | throw new Error(errorMsg); 256 | }, 257 | 258 | create : function (name, var_args) { 259 | var askedName=name; 260 | var namespaceData=this._getNamespaceDataFromName(name); 261 | var namespace=namespaceData.namespace; 262 | //If is configurable the first argument is the parasm from the config 263 | if(this.isConfigurable()){ 264 | var params=this._getParams(name,arguments[1]); 265 | arguments[1]=params; 266 | } 267 | arguments[0]=namespace; 268 | var notIndexed= !namespaceData.isIndexed; 269 | 270 | if(notIndexed){ 271 | 272 | if(namespaceData.isApp){ 273 | this.setAppClass(askedName,null,null,namespace); 274 | } 275 | else if(namespaceData.isEngine){ 276 | this.setEngineClass(askedName,null,null,namespace); 277 | } 278 | } 279 | return this._getObject.apply(this,arguments); 280 | 281 | 282 | } 283 | } -------------------------------------------------------------------------------- /docs/ConsolidatorBackendSequence.uxf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 10 4 | 5 | UMLGeneric 6 | 7 | 280 8 | 0 9 | 100 10 | 30 11 | 12 | _:BackEnd_ 13 | 14 | 15 | 16 | Relation 17 | 18 | 320 19 | 20 20 | 30 21 | 70 22 | 23 | lt=. 24 | 10.0;10.0;10.0;50.0 25 | 26 | 27 | UMLGeneric 28 | 29 | 320 30 | 70 31 | 20 32 | 60 33 | 34 | 35 | 36 | 37 | 38 | Relation 39 | 40 | 330 41 | 70 42 | 170 43 | 40 44 | 45 | lt=<- 46 | Add to queue 47 | 150.0;20.0;10.0;20.0 48 | 49 | 50 | UMLGeneric 51 | 52 | 450 53 | 0 54 | 100 55 | 30 56 | 57 | _:DB_ 58 | 59 | 60 | 61 | UMLGeneric 62 | 63 | 480 64 | 70 65 | 20 66 | 120 67 | 68 | 69 | 70 | 71 | 72 | Relation 73 | 74 | 480 75 | 20 76 | 30 77 | 70 78 | 79 | lt=. 80 | 10.0;10.0;10.0;50.0 81 | 82 | 83 | Relation 84 | 85 | 490 86 | 100 87 | 240 88 | 40 89 | 90 | lt=<- 91 | Retrieve queue 92 | 10.0;20.0;220.0;20.0 93 | 94 | 95 | UMLGeneric 96 | 97 | 610 98 | 0 99 | 180 100 | 30 101 | 102 | _:DataBaseConsolidator_ 103 | 104 | 105 | 106 | Relation 107 | 108 | 710 109 | 20 110 | 30 111 | 90 112 | 113 | lt=. 114 | 10.0;10.0;10.0;70.0 115 | 116 | 117 | UMLGeneric 118 | 119 | 710 120 | 90 121 | 20 122 | 460 123 | 124 | 125 | 126 | 127 | 128 | UMLSpecialState 129 | 130 | 470 131 | 80 132 | 20 133 | 20 134 | 135 | type=termination 136 | 137 | 138 | 139 | Relation 140 | 141 | 490 142 | 140 143 | 240 144 | 40 145 | 146 | lt=<- 147 | Queue 148 | 220.0;20.0;10.0;20.0 149 | 150 | 151 | Relation 152 | 153 | 670 154 | 180 155 | 250 156 | 110 157 | 158 | lt=<- 159 | Consolidate CompanyAPplicants 160 | 60.0;90.0;120.0;90.0;120.0;10.0;60.0;10.0 161 | 162 | 163 | Relation 164 | 165 | 720 166 | 300 167 | 170 168 | 110 169 | 170 | lt=<- 171 | Consolidate Applicants 172 | 10.0;90.0;80.0;90.0;80.0;10.0;10.0;10.0 173 | 174 | 175 | Relation 176 | 177 | 700 178 | 420 179 | 210 180 | 110 181 | 182 | lt=<- 183 | Consolidate Applicants 184 | WIthin Company Applicants 185 | 30.0;90.0;100.0;90.0;100.0;10.0;30.0;10.0 186 | 187 | 188 | Relation 189 | 190 | 710 191 | 540 192 | 30 193 | 240 194 | 195 | lt=. 196 | 10.0;10.0;10.0;220.0 197 | 198 | 199 | Relation 200 | 201 | 480 202 | 190 203 | 30 204 | 350 205 | 206 | lt=. 207 | 10.0;10.0;10.0;330.0 208 | 209 | 210 | Relation 211 | 212 | 320 213 | 120 214 | 30 215 | 530 216 | 217 | lt=. 218 | 10.0;10.0;10.0;510.0 219 | 220 | 221 | UMLFrame 222 | 223 | 260 224 | 600 225 | 300 226 | 100 227 | 228 | At Any moment 229 | 230 | 231 | 232 | UMLGeneric 233 | 234 | 480 235 | 520 236 | 20 237 | 60 238 | 239 | 240 | 241 | 242 | 243 | Relation 244 | 245 | 490 246 | 510 247 | 240 248 | 40 249 | 250 | lt=<- 251 | Add consolidated data 252 | 10.0;20.0;220.0;20.0 253 | 254 | 255 | UMLSpecialState 256 | 257 | 490 258 | 520 259 | 20 260 | 20 261 | 262 | type=termination 263 | 264 | 265 | 266 | Relation 267 | 268 | 330 269 | 620 270 | 170 271 | 40 272 | 273 | lt=<- 274 | Make general search 275 | 150.0;20.0;10.0;20.0 276 | 277 | 278 | Relation 279 | 280 | 480 281 | 570 282 | 30 283 | 70 284 | 285 | lt=. 286 | 10.0;10.0;10.0;50.0 287 | 288 | 289 | UMLGeneric 290 | 291 | 320 292 | 630 293 | 20 294 | 60 295 | 296 | 297 | 298 | 299 | 300 | UMLGeneric 301 | 302 | 480 303 | 620 304 | 20 305 | 60 306 | 307 | 308 | 309 | 310 | 311 | Relation 312 | 313 | 320 314 | 680 315 | 30 316 | 70 317 | 318 | lt=. 319 | 10.0;10.0;10.0;50.0 320 | 321 | 322 | Relation 323 | 324 | 480 325 | 670 326 | 30 327 | 100 328 | 329 | lt=. 330 | 10.0;10.0;10.0;80.0 331 | 332 | 333 | -------------------------------------------------------------------------------- /src/bootstraps/AppBootstrap.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs'); 2 | var util=require('util'); 3 | var CONTEXT_MODULE=4; 4 | var CONTEXT_MODEL=1; 5 | var CONTEXT_VIEW=2; 6 | var CONTEXT_CONTROLLER=3; 7 | var CONTEXT_MODULE_ROOT=5; 8 | var CONTEXT_COMPONENTS=6; 9 | var CONTEXT_GATEWAYS=7; 10 | var CONTEXT_ROOT=8; 11 | var CONTEXT_ERROR=9; 12 | var CONTEXT_SCHEMA=10; 13 | var CONTEXT_ROUTER=11; 14 | var CONTEXT_MIDDLEWARE=12; 15 | var _=require('lodash'); 16 | 17 | module.exports={ 18 | _classFactory:null, 19 | _controllerFactory:null, 20 | /** 21 | * Set the application path 22 | * @param {String} path The file type 23 | */ 24 | setApplicationPath:function(path){ 25 | //Removing leading bar 26 | this._path= path.replace(/\/+$/,''); 27 | }, 28 | /** 29 | * Check wheter the paplication has a path 30 | * @return {Boolean} [description] 31 | */ 32 | hasApplicationPath:function(){ 33 | return !!this._path; 34 | }, 35 | /** 36 | * Constructor 37 | * @param {[type]} engine [description] 38 | * @param {[type]} data [description] 39 | * @return {[type]} [description] 40 | */ 41 | init:function(engine,params){ 42 | this._engine=engine; 43 | this._params=params; 44 | this._classFactory=engine.getLib('classFactory'); 45 | 46 | 47 | this._controllerFactory=engine.getBootstrap('ControllerFactory'); 48 | this._modelFactory=engine.getBootstrap('ModelFactory'); 49 | this._moduleBootstrap=engine.getBootstrap('ModuleBootstrap'); 50 | this._gatewayFactory=engine.getBootstrap('GatewayFactory'); 51 | this._componentFactory=engine.getBootstrap('ComponentFactory'); 52 | this._errorFactory=engine.getBootstrap('ErrorFactory'); 53 | this._schemaFactory=engine.getBootstrap('SchemaFactory'); 54 | this._routerHandlerFactory=engine.getBootstrap('RouterHandlerFactory'); 55 | if(this._params['path']){ 56 | this.setApplicationPath(this._params['path']); 57 | } 58 | 59 | }, 60 | /** 61 | * Get the directory context 62 | * @param {[type]} dirName [description] 63 | * @return {[type]} [description] 64 | */ 65 | _getDirectoryContext:function(dirName,currentContext){ 66 | 67 | dirName=dirName.toLowerCase(); 68 | switch(dirName){ 69 | case 'modules': 70 | return CONTEXT_MODULE; 71 | case 'models': 72 | return CONTEXT_MODEL; 73 | case 'views': 74 | return CONTEXT_VIEW; 75 | case 'controllers': 76 | return CONTEXT_CONTROLLER; 77 | case 'components': 78 | return CONTEXT_COMPONENTS; 79 | case 'gateways': 80 | return CONTEXT_GATEWAYS; 81 | case 'errors': 82 | return CONTEXT_ERROR; 83 | case 'router': 84 | if(currentContext==CONTEXT_COMPONENTS){ 85 | return CONTEXT_ROUTER; 86 | } 87 | return false; 88 | case 'middleware': 89 | if(currentContext==CONTEXT_COMPONENTS){ 90 | return CONTEXT_MIDDLEWARE; 91 | } 92 | return false; 93 | case 'schemas': 94 | if(currentContext==CONTEXT_MODEL){ 95 | return CONTEXT_SCHEMA; 96 | } 97 | return false; 98 | default: 99 | return false; 100 | } 101 | }, 102 | _addComponent:function(path,name,parentNamespace){ 103 | this._componentFactory.setAppClass(name,path,parentNamespace); 104 | }, 105 | _handleFile:function(fullPath,fileName,context,parentModules,cb) { 106 | var name=this.removeExtension(fileName); 107 | switch(context){ 108 | case CONTEXT_CONTROLLER: 109 | this._addController(fullPath,name,parentModules); 110 | cb(); 111 | break; 112 | case CONTEXT_MODEL: 113 | this._addModel(fullPath,name,parentModules); 114 | cb(); 115 | break; 116 | case CONTEXT_VIEW: 117 | cb(); 118 | break; 119 | case CONTEXT_GATEWAYS: 120 | this._addGateway(fullPath,name,cb); 121 | break; 122 | case CONTEXT_COMPONENTS: 123 | this._addComponent(fullPath,name,parentModules); 124 | cb(); 125 | break; 126 | case CONTEXT_ERROR: 127 | this._addError(fullPath,name,parentModules); 128 | cb(); 129 | break; 130 | case CONTEXT_SCHEMA: 131 | this._addSchema(fullPath,name,parentModules); 132 | cb(); 133 | break; 134 | case CONTEXT_ROUTER: 135 | this._addRouterHandler(fullPath,name,parentModules); 136 | cb(); 137 | break; 138 | case CONTEXT_MIDDLEWARE: 139 | this._addMiddleware(fullPath,name,parentModules); 140 | cb(); 141 | break; 142 | default: 143 | cb(); 144 | break; 145 | } 146 | }, 147 | _addMiddleware:function(path,name,parentNamespace){ 148 | if(parentNamespace){ 149 | parentNamespace='middleware.'+parentNamespace; 150 | } 151 | else{ 152 | parentNamespace='middleware'; 153 | } 154 | this._addComponent(path,name,parentNamespace); 155 | }, 156 | _addRouterHandler:function(path,name,parentNamespace){ 157 | this._routerHandlerFactory.setAppClass(name,path,parentNamespace); 158 | }, 159 | _addSchema:function(path,name,parentNamespace){ 160 | 161 | this._schemaFactory.setAppClass(name,path,parentNamespace); 162 | 163 | }, 164 | _addError:function(path,name,parentNamespace){ 165 | this._errorFactory.setAppClass(name,path,parentNamespace); 166 | }, 167 | 168 | /** 169 | * Adds a gateway 170 | * @param {[type]} path [description] 171 | * @param {[type]} name [description] 172 | */ 173 | _addGateway:function(path,name,cb) { 174 | this._gatewayFactory.setAppClass(name,path); 175 | cb(); 176 | }, 177 | _addNamespace:function(moduleName,parentModules,path,context) { 178 | if(context==CONTEXT_CONTROLLER){ 179 | this._modelFactory.addNamespace(parentModules,path); 180 | return; 181 | } 182 | else if(context==CONTEXT_MODEL){ 183 | this._controllerFactory.addNamespace(parentModules,path); 184 | return; 185 | } 186 | else{ 187 | var namespace=parentModules; 188 | if(namespace===''){ 189 | namespace=moduleName; 190 | } 191 | else{ 192 | namespace+='.'+moduleName; 193 | } 194 | this._classFactory.setNamespaceDir(namespace,path); 195 | return; 196 | 197 | } 198 | 199 | }, 200 | _addModel:function(path,modelName,parentNamespace) { 201 | this._modelFactory.setAppClass(modelName,path,parentNamespace); 202 | }, 203 | _addController:function(path,controllerName,parentModules) { 204 | this._controllerFactory.setAppClass(controllerName,path,parentModules); 205 | }, 206 | removeExtension:function(name) { 207 | return name.replace(/\.js$/,''); 208 | }, 209 | /** 210 | * Recursive walk the directory structures 211 | * @param {Function} cb [description] 212 | * @param {[type]} dir [description] 213 | * @param {[type]} context [description] 214 | * @param {[type]} parentModules [description] 215 | * @return {[type]} [description] 216 | * @todo refatorar mover dir walker para outro classe 217 | */ 218 | _walkDir:function(cb,dir,context,parentModules){ 219 | if(!parentModules){ 220 | parentModules=''; 221 | } 222 | var self=this; 223 | var path; 224 | var context; 225 | var list=fs.readdirSync(dir); 226 | 227 | var asyncFunctions=[]; 228 | var functionToHandleFile=function(path,item,context,parentModules){ 229 | return function(asyncCb){ 230 | self._handleFile(path,item,context,parentModules,asyncCb); 231 | } 232 | } 233 | list.forEach(function(item){ 234 | if(name.indexOf('.')=='0'){ 235 | return; 236 | } 237 | path=dir+'/'+item; 238 | var stat=fs.statSync(path); 239 | if( stat.isDirectory()){ 240 | var childContext=self._getDirectoryContext(item,context); 241 | //We just care about directories on modules ,root and models 242 | if(context===CONTEXT_MODULE_ROOT || context===CONTEXT_ROOT ){ 243 | 244 | //If it's not on a child context we don't handle it 245 | if(childContext!==false){ 246 | 247 | self._addNamespace(item,parentModules,path,childContext); 248 | if( childContext===CONTEXT_GATEWAYS){ 249 | //Just root handle components and gateways 250 | if(context===CONTEXT_ROOT){ 251 | asyncFunctions=asyncFunctions.concat(self._walkDir(false,path,childContext,parentModules)); 252 | } 253 | } 254 | else{ 255 | 256 | //This is a directory we care change the context and keep looping 257 | asyncFunctions=asyncFunctions.concat(self._walkDir(false,path,childContext,parentModules)); 258 | } 259 | } 260 | } 261 | else if(context===CONTEXT_MODEL || childContext===CONTEXT_COMPONENTS || childContext==CONTEXT_ROUTER || childContext==CONTEXT_MIDDLEWARE){ 262 | asyncFunctions=asyncFunctions.concat(self._walkDir(false,path,childContext,parentModules)); 263 | } 264 | else if(context===CONTEXT_MODULE){ 265 | //It's a module lets put it on the map 266 | self._addNamespace(item,parentModules,path,context); 267 | var newParentmodules=self._buildNamespace(parentModules,item); 268 | self._moduleBootstrap.addToEngine(item,path,parentModules); 269 | //It's a module for all intents all directories here are module names with module structure 270 | asyncFunctions=asyncFunctions.concat(self._walkDir(false,path,CONTEXT_MODULE_ROOT,newParentmodules)); 271 | } 272 | } 273 | else{ 274 | //It is a file 275 | asyncFunctions.push(functionToHandleFile(path,item,context,parentModules)); 276 | } 277 | }); 278 | if(cb){ 279 | if(asyncFunctions.length>0){ 280 | self._engine.libs.async.parallel(asyncFunctions,function(err,res){ 281 | cb(err); 282 | }); 283 | return; 284 | } 285 | cb(); 286 | } 287 | else{ 288 | return asyncFunctions; 289 | } 290 | 291 | }, 292 | _buildNamespace:function(parentNamespace,currentDirectory){ 293 | var ret; 294 | if(parentNamespace===''){ 295 | ret=currentDirectory; 296 | } 297 | else{ 298 | ret=parentNamespace+'.'+currentDirectory; 299 | } 300 | return ret; 301 | }, 302 | 303 | _appHasConfigFile:function() { 304 | return fs.existsSync(this._path+'/config/app.js'); 305 | }, 306 | _finishedWalkingDir:function(cb){ 307 | var self=this; 308 | return function(err){ 309 | if(err){ 310 | cb(err); 311 | return; 312 | } 313 | var autoCrudSchemas=self._schemaFactory.getAutoCrudSchemas(); 314 | if(!_.isEmpty(autoCrudSchemas)){ 315 | self._controllerFactory.handleAutoSchemaCrud(autoCrudSchemas); 316 | self._modelFactory.handleAutoSchemaCrud(autoCrudSchemas); 317 | } 318 | var requiredCompenents=self._gatewayFactory.getGatewaysRequiredComponents(); 319 | if(!_.isEmpty(requiredCompenents)){ 320 | self._componentFactory.initializeComponents(requiredCompenents); 321 | } 322 | self._schemaFactory.initializeSchemas(); 323 | cb(); 324 | } 325 | }, 326 | buildApp:function(cb) { 327 | if(!this._appHasConfigFile()){ 328 | //There isn't an app here 329 | cb(); 330 | return; 331 | } 332 | try{ 333 | this._engine.setConfig(this._path+'/config/app.js'); 334 | this._walkDir(this._finishedWalkingDir(cb),this._path,CONTEXT_ROOT); 335 | } 336 | catch(e){ 337 | cb(e); 338 | } 339 | } 340 | 341 | } -------------------------------------------------------------------------------- /tests/core/components/DataBase.js: -------------------------------------------------------------------------------- 1 | var databaseComponent; 2 | var createObjectId = require('pow-mongodb-fixtures').createObjectId; 3 | var _=require('lodash'); 4 | var async=require('async'); 5 | var Utils=require('../../tools/utils'); 6 | var utils=new Utils(); 7 | var expect=utils.expect; 8 | var should = utils.should; 9 | var fixtures=utils.fixtures; 10 | var fixtureData=utils.getFixtureData('login','tags','categories'); 11 | var totalLoginData=Object.keys(fixtureData.login).length; 12 | 13 | describe('Component database',function(){ 14 | var ginger; 15 | //Initializing the app by path first 16 | before(function(done){ 17 | var cb=function(err){ 18 | if(err){ 19 | done(err); 20 | return; 21 | } 22 | ginger=utils.getServer(); 23 | databaseComponent=ginger.getComponent('DataBase'); 24 | done(); 25 | 26 | } 27 | utils.initServer(cb); 28 | }); 29 | 30 | describe('read',function(){ 31 | before(function(done){ 32 | fixtures.clearAndLoad(fixtureData,done); 33 | }); 34 | describe('By id',function(){ 35 | describe('Failure',function(){ 36 | it('Should return invalid params if the id is not valid ',function(done){ 37 | databaseComponent.readById('login','NotRelevant',function(err,data){ 38 | expect(err).to.exist; 39 | expect(err.code).to.equal('INVALID_PARAMS'); 40 | done(); 41 | }); 42 | }); 43 | it('Should return not found if the id is not found ',function(done){ 44 | databaseComponent.readById('login',createObjectId(),function(err,data){ 45 | expect(err).to.exist; 46 | expect(err.code).to.equal('NOT_FOUND'); 47 | done(); 48 | }); 49 | }); 50 | }); 51 | describe('success',function(){ 52 | it('Should find the element by id successfuly',function(done){ 53 | var id=fixtureData.login.user1._id.toString(); 54 | databaseComponent.readById('login',id,function(err,data){ 55 | 56 | expect(data._id.toString()).to.equal(id); 57 | done(); 58 | }); 59 | }); 60 | it('Should be able to restrict the fields',function(done){ 61 | var id=fixtureData.login.user1._id.toString(); 62 | var cb=function(err,data){ 63 | 64 | expect(data._id.toString()).to.equal(id); 65 | var expectKeys=['email','_id']; 66 | var keys=Object.keys(data); 67 | var intersection=_.intersection(keys,expectKeys); 68 | var sizeResponse=keys.length; 69 | var intersectionSize=intersection.length; 70 | expect(intersectionSize).to.equal(sizeResponse); 71 | done(); 72 | }; 73 | var fields='email'; 74 | databaseComponent.readById('login',id,cb,fields); 75 | }); 76 | }); 77 | it('Should be able to execute population',function(done){ 78 | var id=fixtureData.categories.forDevelopers._id.toString(); 79 | var cb=function(err,data){ 80 | 81 | expect(err).to.not.exist; 82 | expect(typeof(data.user)).to.equal('object'); 83 | done(); 84 | }; 85 | databaseComponent.readById('categories',id,cb,{},null,'user'); 86 | }); 87 | }); 88 | describe('One',function(){ 89 | }); 90 | describe(' normal ',function(){ 91 | }); 92 | }); 93 | describe('Create',function(){ 94 | beforeEach(function(done){ 95 | fixtures.clearAndLoad(fixtureData,done); 96 | }); 97 | describe('Validation',function(){ 98 | it('Should not accept data that is not validated',function(done){ 99 | var fields={ 100 | 'name':'UPPPERCASE', 101 | 'active':'1', 102 | 'numHits':'NOT A NUMBER', 103 | 'slug':'someSlug', 104 | 'url':'notAUrl' 105 | }; 106 | databaseComponent.create('tags',fields,function(err,data){ 107 | expect(err).to.exist; 108 | done(); 109 | }) 110 | }); 111 | it('Should not be able to create without required fields'); 112 | }); 113 | it('Should execute successfuly ',function(done){ 114 | var data={ 115 | 'email':'bar@baz.com', 116 | 'active':true, 117 | 'name':'barBaz', 118 | 'password':'foo', 119 | } 120 | var cb=function(err,saveData){ 121 | expect(saveData).to.exist; 122 | for(var k in data){ 123 | expect(data[k]).to.equal(saveData[k]) 124 | } 125 | done(); 126 | } 127 | databaseComponent.create('login',data,cb); 128 | }); 129 | }); 130 | describe('Update',function(){ 131 | describe('failure',function(){ 132 | it('Should return 0 if no item was updated',function(done){ 133 | var newEmail='newEmail@email.com'; 134 | var notFoundEmail='notFound@notFound.com'; 135 | var cb=function(err,numAffected){ 136 | expect(err).to.not.exist; 137 | expect(numAffected).to.equal(0); 138 | done(); 139 | } 140 | databaseComponent.update('login',{'email':newEmail},{'email':notFoundEmail},cb); 141 | }); 142 | describe('On normal update',function(){ 143 | it('description',function(done){ 144 | var fields={ 145 | 'url':'notAUrl' 146 | }; 147 | var id=fixtureData.tags.NodeJS._id.toString(); 148 | databaseComponent.update('tags',fields,{'_id':id},function(err,data){ 149 | 150 | expect(err).to.exist; 151 | done(); 152 | }); 153 | }); 154 | }); 155 | describe('On update by Id',function(){ 156 | it('Should not allow to update invalid fields',function(done){ 157 | var fields={ 158 | 'url':'notAUrl' 159 | }; 160 | var id=fixtureData.tags.NodeJS._id.toString(); 161 | databaseComponent.updateById('tags',id,fields,function(err,data){ 162 | expect(err).to.exist; 163 | done(); 164 | }) 165 | }); 166 | }); 167 | describe('On Update one',function(){ 168 | it('Should not allow to update invalid fields',function(done){ 169 | var fields={ 170 | 'url':'notAUrl' 171 | }; 172 | var id=fixtureData.tags.NodeJS._id.toString(); 173 | databaseComponent.updateOne('tags',fields,{'_id':id},function(err,data){ 174 | 175 | expect(err).to.exist; 176 | done(); 177 | }) 178 | }); 179 | }); 180 | }); 181 | describe('success',function(){ 182 | beforeEach(function(done){ 183 | fixtures.clearAndLoad(fixtureData,done); 184 | }); 185 | it('Should happen by mixed params',function(done){ 186 | var email=fixtureData.login.user1.email; 187 | var newEmail='newEmail@email.com'; 188 | var name=fixtureData.login.user1.name; 189 | var cb=function(err,data){ 190 | expect(data).to.equal(1); 191 | done(); 192 | } 193 | databaseComponent.update('login',{'email':newEmail},{'email':email,'name':name},cb); 194 | }); 195 | it('Should update multiple instances if they all match',function(done){ 196 | var name=fixtureData.login.user1.name; 197 | var sizeOfName=0; 198 | for(var k in fixtureData.login){ 199 | if(fixtureData.login[k].name==name){ 200 | sizeOfName++; 201 | } 202 | } 203 | var newName='new name'; 204 | var cb=function(err,data){ 205 | expect(data).to.equal(sizeOfName); 206 | done(); 207 | } 208 | databaseComponent.update('login',{'name':newName},{'name':name},cb); 209 | }); 210 | }); 211 | describe('One',function(){ 212 | beforeEach(function(done){ 213 | fixtures.clearAndLoad(fixtureData,done); 214 | }); 215 | it('Shopuld be able to update a single record and return it',function(done){ 216 | 217 | var cb=function(err,data){ 218 | expect(data.active).to.be.false; 219 | done(); 220 | } 221 | databaseComponent.updateOne('login',{active:false},{'email':'email@gmail.com'},cb); 222 | }); 223 | }); 224 | describe('By id',function(){ 225 | beforeEach(function(done){ 226 | fixtures.clearAndLoad(fixtureData,done); 227 | }); 228 | it('Should execute successfuly',function(done){ 229 | var id=fixtureData.login.user1._id.toString(); 230 | var newName='new name'; 231 | var cb=function(err,data){ 232 | expect(data).to.exist 233 | expect(data._id.toString()).to.equal(id); 234 | expect(data.name).to.equal(newName); 235 | 236 | done(); 237 | } 238 | databaseComponent.updateById('login',id,{'name':newName},cb); 239 | }); 240 | it('Should return InvalidParams if the id is not valid',function(done){ 241 | var id='InvalidId'; 242 | var newName='new name'; 243 | var cb=function(err,data){ 244 | 245 | expect(err).to.exist; 246 | expect(err.code).to.equal('INVALID_PARAMS'); 247 | 248 | done(); 249 | } 250 | databaseComponent.updateById('login',id,{'name':newName},cb); 251 | 252 | }); 253 | it('Should return not found if the id is not found',function(done){ 254 | var id=createObjectId(); 255 | var newName='new name'; 256 | var cb=function(err,data){ 257 | expect(err).to.exist; 258 | expect(err.code).to.equal('NOT_FOUND'); 259 | 260 | done(); 261 | } 262 | databaseComponent.updateById('login',id,{'name':newName},cb); 263 | }); 264 | 265 | }); 266 | }); 267 | describe('Count',function(){ 268 | before(function(done){ 269 | fixtures.clearAndLoad(fixtureData,done); 270 | }); 271 | it('Should be able to get the total data of a collection',function(done){ 272 | var cb=function(err,data){ 273 | expect(err).to.not.exist; 274 | expect(data).to.equal(totalLoginData); 275 | done(); 276 | } 277 | databaseComponent.count('login',null,cb); 278 | }); 279 | it('Should be able to get the total data of a collection passing a search param',function(done){ 280 | var activeTotal=0; 281 | for(var x in fixtureData.login){ 282 | if(fixtureData.login[x].active){ 283 | activeTotal++; 284 | } 285 | } 286 | var cb=function(err,data){ 287 | expect(err).to.not.exist; 288 | expect(data).to.equal(activeTotal); 289 | done(); 290 | } 291 | databaseComponent.count('login',{'active':true},cb) 292 | }); 293 | }); 294 | describe('List',function(){ 295 | before(function(done){ 296 | fixtures.clearAndLoad(fixtureData,done); 297 | }); 298 | it('Should be able to list all data limitless',function(done){ 299 | var cb=function(err,data){ 300 | expect(err).to.not.exist; 301 | expect(data.total).to.equal(totalLoginData); 302 | expect(data.results.length).to.equal(totalLoginData); 303 | done(); 304 | } 305 | databaseComponent.list('login',null,-1,null,null,null,cb); 306 | }); 307 | it('Should be able to paginate the data',function(done){ 308 | var cb=function(err,data){ 309 | expect(err).to.not.exist; 310 | expect(data.total).to.equal(totalLoginData); 311 | expect(data.results.length).to.equal(5); 312 | done(); 313 | } 314 | databaseComponent.list('login',null,5,null,null,null,cb); 315 | }); 316 | it('Should be able to set which fields are wanted'); 317 | it('Should be able to set which fields are not wanted with -1'); 318 | it('Should be able to set which fields are not wanted with \'-1\''); 319 | it('Should be able to set which fields are not wanted with false'); 320 | it('Should be able to set which fields are not wanted with \'false\''); 321 | it('Should be able to sort data'); 322 | }); 323 | describe('Destroy ',function(){ 324 | it('Should to delete a component by params'); 325 | describe('byId',function(){ 326 | it('Should execute successfuly'); 327 | it('Should return not found if the id is not valid'); 328 | it('Should return not found if the id is not found'); 329 | }); 330 | }); 331 | 332 | after(function(done){ 333 | utils.endServer(done); 334 | }); 335 | }); -------------------------------------------------------------------------------- /src/Ginger.js: -------------------------------------------------------------------------------- 1 | var async = require('async'); 2 | var util = require('util'); 3 | var fs = require('fs'); 4 | var _ = require('lodash'); 5 | /** 6 | * @type {Object} 7 | */ 8 | var libs = { 9 | async: async, 10 | util: util, 11 | fs: fs, 12 | _: _, 13 | }; 14 | /** 15 | * The application main engine 16 | */ 17 | function Ginger() { 18 | this._closeQueue=[]; 19 | this._launchQueue=[]; 20 | /** 21 | * Bootstrapers are used to launch the application, they are factories for the application startup 22 | * @type {Object} 23 | */ 24 | this._bootstrap = {}; 25 | /** 26 | * gateWays to access the application like socketIO,JSONRPC ,HTTP etcng 27 | */ 28 | this._gateways = {}; 29 | /** 30 | * The configuration file to use, it can be set and optionally merged with the default 31 | * @type {Object} 32 | */ 33 | this._config = {}; 34 | this._isSetup=false; 35 | /** 36 | * Just a map of all modules that we have 37 | * @type {Object} 38 | */ 39 | this.moduleMap = { 40 | 41 | }; 42 | 43 | /** 44 | * The path to the application.It can set programmatically,by config data or if everything file it will be proccess.cwd 45 | * @type {String} 46 | */ 47 | this._appPath = null; 48 | 49 | /** 50 | * Function to check if we can launch the application 51 | * @param {Ginger} ginger 52 | * @return {[type]} [description] 53 | */ 54 | this._preLaunchFunction = function (ginger, cb) { 55 | cb(); 56 | } 57 | this.libs = libs; 58 | /** 59 | * Config relative to the engine initialization, you should be able to overwrite it but should not 60 | */ 61 | this._engineConfig; 62 | 63 | 64 | } 65 | 66 | /** 67 | * Closes the application 68 | * @param {Function} cb CallBack for completition 69 | */ 70 | Ginger.prototype.down = function (cb) { 71 | async.series(this._closeQueue,cb); 72 | 73 | }; 74 | 75 | Ginger.getDefaultConfig = function () { 76 | return require(__dirname + '/config/defaultApp.js'); 77 | }; 78 | Ginger.prototype._setupEngineConfig = function() { 79 | this._engineConfig = require(__dirname + '/config/defaultEngine.js'); 80 | }; 81 | Ginger.prototype._setConfigAsDefaultIfNoneSet = function() { 82 | if (_.isEmpty(this._config)) { 83 | this._config = Ginger.getDefaultConfig(); 84 | } 85 | }; 86 | Ginger.prototype._getNamespaceFunctionFromEngineConfig = function(name,configValue) { 87 | var self=this; 88 | var classFactory=this.libs.classFactory; 89 | var dir=this.getEngineConfigValue(configValue); 90 | return function(asyncCb){ 91 | classFactory.setRecursiveNamespaceDir(name,dir,asyncCb); 92 | } 93 | }; 94 | Ginger.prototype._setClassFactory = function(first_argument) { 95 | var OliveOil=require('olive_oil')(); 96 | var oliveOil=new OliveOil(null); 97 | this.libs.classFactory=oliveOil; 98 | }; 99 | Ginger.prototype._setDefaultNamespaces = function(cb) { 100 | this.libs.classFactory.setNamespaceDir('ginger',this.getEngineConfigValue('rootDir')); 101 | var namespaceMap= { 102 | 'ginger.bootstraps':'bootstrapsDir', 103 | 'ginger.components':'componentsDir', 104 | 'ginger.gateways':'gatewaysDir', 105 | 'ginger.mvc':'mvcDir', 106 | 'ginger.errors':'errorsDir'}; 107 | var namespaceFunctions=[]; 108 | var self=this; 109 | for(var x in namespaceMap){ 110 | namespaceFunctions.push(self._getNamespaceFunctionFromEngineConfig(x,namespaceMap[x])); 111 | } 112 | async.series(namespaceFunctions,cb); 113 | }; 114 | Ginger.prototype._setGatewaysClasses = function() { 115 | for (var name in this._config.gateways) { 116 | this._gatewayFactory.setEngineClass(name); 117 | } 118 | }; 119 | Ginger.prototype.addFunctionToLaunchQueue = function(functionToUp) { 120 | this._launchQueue.push(functionToUp); 121 | }; 122 | Ginger.prototype.addFunctionToCloseQueue = function(functionToClose) { 123 | this._closeQueue.push(functionToClose); 124 | }; 125 | Ginger.prototype._getFactories = function() { 126 | this._gatewayFactory=this.getBootstrap('GatewayFactory'); 127 | this._componentFactory=this.getBootstrap('ComponentFactory'); 128 | this._errorFactory=this.getBootstrap('ErrorFactory'); 129 | this._queryFactory=this.getBootstrap('QueryFactory'); 130 | this._controllerFactory=this.getBootstrap('ControllerFactory'); 131 | this._modelFactory=this.getBootstrap('ModelFactory'); 132 | this._schemaFactory=this.getBootstrap('SchemaFactory'); 133 | this._routerHandlerFactory=this.getBootstrap('RouterHandlerFactory'); 134 | 135 | }; 136 | /** 137 | * Starts the application 138 | * @param {Mixed} config String of the config file or config data 139 | * @return {[type]} [description] 140 | */ 141 | Ginger.prototype.up = function (cb) { 142 | var self=this; 143 | var startAppCb = function (err) { 144 | if (err) { 145 | cb(err); 146 | return; 147 | } else { 148 | var preLaunchCb = function (err) { 149 | //If the preLaunch didn't work we call the callback with the error 150 | if (err) { 151 | cb(err); 152 | return; 153 | } 154 | //The preLaunch worked try to launch 155 | self._launch(cb); 156 | } 157 | if (self._preLaunchFunction) { 158 | //Calling prelaunch function if any 159 | self._preLaunchFunction(self, preLaunchCb) 160 | 161 | } else { 162 | self._launch(cb); 163 | } 164 | } 165 | }; 166 | var setNamespacesCb=function(err){ 167 | if(err){ 168 | cb(err); 169 | return; 170 | } 171 | self._getFactories(); 172 | self._setupApp(startAppCb); 173 | }; 174 | this._setup(setNamespacesCb); 175 | }; 176 | Ginger.prototype._setup = function(cb) { 177 | 178 | if(this._isSetup){ 179 | cb(); 180 | return; 181 | } 182 | var self=this; 183 | this._setConfigAsDefaultIfNoneSet(); 184 | this._setupEngineConfig(); 185 | this._setClassFactory(); 186 | this._setDefaultNamespaces(function(err){ 187 | if(err){ 188 | cb(err); 189 | return; 190 | } 191 | self._isSetup=true; 192 | cb(); 193 | }); 194 | }; 195 | Ginger.prototype.setPreLaunchFunction = function (prelaunchFunction) { 196 | this._preLaunchFunction = prelaunchFunction; 197 | } 198 | Ginger.prototype._setNoNamespaceDirToAppRoot = function() { 199 | this.libs.classFactory.setNoNamespaceDir(this._appPath); 200 | }; 201 | 202 | Ginger.prototype._setupApp = function (cb) { 203 | //Trying to get the app params if any 204 | var appInit = this.getBootstrap('AppBootstrap'); 205 | //There is no app path set one 206 | if (!this._appPath) { 207 | this.setAppPath(); 208 | } 209 | this._setNoNamespaceDirToAppRoot(); 210 | appInit.setApplicationPath(this._appPath); 211 | try { 212 | 213 | appInit.buildApp(cb); 214 | 215 | 216 | } catch (err) { 217 | cb(err); 218 | } 219 | } 220 | 221 | Ginger.prototype.loadAppFile = function (name) { 222 | return require(this._config['app']['dir'] + name); 223 | } 224 | Ginger.prototype.getQuery = function ( params) { 225 | var queryFactory=this._queryFactory; 226 | return queryFactory.create.apply(queryFactory,arguments); 227 | 228 | }; 229 | /** 230 | * Get a model on the app model directory 231 | * @return {[type]} [description] 232 | */ 233 | Ginger.prototype.getModel = function (name,var_args) { 234 | var modelFactory=this.getBootstrap('ModelFactory'); 235 | return modelFactory.create.apply(modelFactory,arguments); 236 | } 237 | /** 238 | * Get a view starting from the view directory 239 | * @param {[type]} name [description] 240 | * @param {[type]} inherits [description] 241 | * @return {[type]} [description] 242 | */ 243 | Ginger.prototype.getView = function (name) { 244 | 245 | } 246 | /** 247 | * Get a value from the config 248 | * @param {[type]} name [description] 249 | * @return {[type]} [description] 250 | */ 251 | Ginger.prototype.getConfigValue = function (name) { 252 | if (!!this._config && this._config[name]) { 253 | return this._config[name]; 254 | } 255 | 256 | return null; 257 | }; 258 | 259 | Ginger.prototype.getEngineConfigValue = function (name) { 260 | if (!!this._engineConfig && this._engineConfig[name]) { 261 | return this._engineConfig[name]; 262 | } 263 | 264 | return null; 265 | }; 266 | 267 | Ginger.prototype.getConfig = function () { 268 | return this._config; 269 | }; 270 | /** 271 | * Get the configuration merging it with the current config 272 | * @param {mixed} config String or Object 273 | * @return {[type]} [description] 274 | */ 275 | Ginger.prototype.setConfig = function (config, merge) { 276 | merge = merge !== false; 277 | 278 | if (typeof (config) == 'string') { 279 | this._config = require(config); 280 | } else { 281 | this._config = config; 282 | } 283 | if (merge) { 284 | this._config = _.merge({}, Ginger.getDefaultConfig(), this._config); 285 | } 286 | 287 | }; 288 | 289 | /** 290 | * Sets the application path, if there is a path this is used, if there is a config['app']['dir'] it's used, the proccess.cwd is used otherwise 291 | * @param {String} path (Optional) the path to the app 292 | */ 293 | Ginger.prototype.setAppPath = function (path) { 294 | if (path) { 295 | this._appPath = path; 296 | } else if (!!this._config && !!this._config['app'] && !!this._config['app']['dir']) { 297 | this._appPath = this._config['app']['dir']; 298 | } else { 299 | this._appPath = process.cwd(); 300 | 301 | } 302 | }; 303 | /** 304 | * Get a singleton component (if it doesn't exists it is created,it it exists the same one is used) 305 | * @param {String} name The name of the component 306 | * @param {object} params (Optional) if not given it will be used the one from the application config, if the application config doesn't implement it the abstract config will be used 307 | * @return {[type]} [description] 308 | * @todo clean 309 | */ 310 | Ginger.prototype.getComponent = function (name,params) { 311 | return this._componentFactory.create(name,params); 312 | } 313 | Ginger.prototype.getRouterHandler = function(name,params) { 314 | return this._routerHandlerFactory.create(name,params); 315 | 316 | }; 317 | 318 | Ginger.prototype.getBootstrap = function (name, params) { 319 | var fullName='ginger.bootstraps.'+name; 320 | if (!this.libs.classFactory.isObjectSet(fullName)) { 321 | this._bootstrap[name] = this._createBootstrap(fullName, params); 322 | } 323 | return this._bootstrap[name]; 324 | } 325 | 326 | 327 | /** 328 | * Set a component on the given scope, if the component is already set, it will be overwritten 329 | * @param {[type]} name [description] 330 | * @param {[type]} component [description] 331 | */ 332 | Ginger.prototype.setComponent = function (name, component) { 333 | return this._componentFactory.setElement(name,component); 334 | }; 335 | 336 | /** 337 | * Checks whether a ocmponent has already been loaded 338 | * @param {[type]} name [description] 339 | * @return {[type]} [description] 340 | */ 341 | Ginger.prototype.isComponentLoaded = function (name) { 342 | return this._componentFactory.isLoaded(name); 343 | } 344 | Ginger.prototype.isGatewayLoaded = function (name) { 345 | return this._gatewayFactory.isGatewayLoaded(name); 346 | } 347 | 348 | /** 349 | * Creates a component based on the name 350 | * @param {[type]} name [description] 351 | * @return {[type]} [description] 352 | */ 353 | Ginger.prototype._createBootstrap = function (name, params) { 354 | if (!params) { 355 | //Trying to get params configuration if none is passed 356 | if (this._engineConfig.bootstrap && !!this._engineConfig.bootstrap[name]) { 357 | params = this._engineConfig.bootstrap[name]; 358 | } else { 359 | params = {}; 360 | } 361 | } 362 | var ret=this.libs.classFactory.getSingletonObject(name,this,params); 363 | return ret; 364 | }; 365 | Ginger.prototype.isGatewayCancelled =function (name) { 366 | return this._gatewayFactory.isGatewayCancelled(name); 367 | }; 368 | Ginger.prototype.isComponentCancelled = function (name) { 369 | return this._componentFactory.isCancelled(name); 370 | }; 371 | 372 | 373 | 374 | /** 375 | * Makes the application go live 376 | * @return {[type]} [description] 377 | */ 378 | Ginger.prototype._launch = function (cb) { 379 | var self=this; 380 | var startGateways=function(err){ 381 | if(err){ 382 | return cb(err); 383 | } 384 | self._gatewayFactory.startGateways(cb); 385 | } 386 | async.series(this._launchQueue,startGateways); 387 | 388 | } 389 | /** 390 | * Return the gateway given by the name 391 | * @param {String} name the name of the gateway 392 | * @return {[type]} [description] 393 | */ 394 | Ginger.prototype.getGateway = function (name) { 395 | 396 | return this._gatewayFactory.getGateway(name); 397 | }; 398 | 399 | /** 400 | * Checks if we have a controller 401 | * @param {String} name index of the controller 402 | * @return {Boolean} [description] 403 | */ 404 | Ginger.prototype.hasController = function (name) { 405 | return this._controllerFactory.hasElement(name); 406 | }; 407 | Ginger.prototype.getController = function(name) { 408 | return this._controllerFactory.create(name); 409 | }; 410 | /** 411 | * @param {String} name index of the model 412 | * @return {Boolean} [description] 413 | */ 414 | Ginger.prototype.hasModel = function (name) { 415 | return this._modelFactory.hasElement(name); 416 | } 417 | Ginger.prototype.hasSchema = function(name) { 418 | return this._schemaFactory.hasElement(name); 419 | }; 420 | Ginger.prototype.getSchema = function(name) { 421 | return this._schemaFactory.create(name); 422 | }; 423 | /** 424 | * @param {String} name index of the module 425 | * @return {Boolean} [description] 426 | */ 427 | Ginger.prototype.hasModule = function (name) { 428 | var moduleFactory=this.getBootstrap('ModuleBootstrap'); 429 | return moduleFactory.hasElement(name); 430 | } 431 | Ginger.prototype.getLib = function(name) { 432 | return this.libs[name]; 433 | }; 434 | Ginger.prototype.getError = function(name,message,code,data) { 435 | return this._errorFactory.create(name,message,code,data); 436 | 437 | }; 438 | module.exports = Ginger; -------------------------------------------------------------------------------- /docs/sequenceDiagram_appUp.uxf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 7 4 | 5 | com.umlet.element.Class 6 | 7 | 259 8 | 0 9 | 70 10 | 21 11 | 12 | _:ginger_ 13 | 14 | 15 | 16 | com.umlet.element.Class 17 | 18 | 595 19 | 0 20 | 70 21 | 21 22 | 23 | _:Application_ 24 | 25 | 26 | 27 | com.umlet.element.Class 28 | 29 | 140 30 | 0 31 | 70 32 | 21 33 | 34 | _:app.js_ 35 | 36 | 37 | 38 | com.umlet.element.Relation 39 | 40 | 154 41 | 0 42 | 34 43 | 2183 44 | 45 | lt=. 46 | 21;21;21;2170 47 | 48 | 49 | com.umlet.element.custom.InteractionFrame 50 | 51 | 77 52 | 63 53 | 315 54 | 98 55 | 56 | optional 57 | 58 | 59 | 60 | 61 | com.umlet.element.Relation 62 | 63 | 273 64 | 0 65 | 34 66 | 2176 67 | 68 | lt=. 69 | 21;21;21;2163 70 | 71 | 72 | com.umlet.element.Class 73 | 74 | 700 75 | 0 76 | 112 77 | 21 78 | 79 | _:ControllerFactory_ 80 | 81 | 82 | 83 | com.umlet.element.Class 84 | 85 | 847 86 | 0 87 | 112 88 | 21 89 | 90 | _:ModelFactory_ 91 | 92 | 93 | 94 | com.umlet.element.custom.SeqObjectActive 95 | 96 | 168 97 | 70 98 | 14 99 | 70 100 | 101 | 102 | 103 | 104 | 105 | com.umlet.element.custom.SeqObjectActive 106 | 107 | 287 108 | 70 109 | 14 110 | 70 111 | 112 | 113 | 114 | 115 | 116 | com.umlet.element.Relation 117 | 118 | 161 119 | 42 120 | 139 121 | 49 122 | 123 | lt=<<- 124 | setAppPath(path) 125 | 126 | 126;35;21;35 127 | 128 | 129 | com.umlet.element.Relation 130 | 131 | 161 132 | 112 133 | 139 134 | 34 135 | 136 | lt=<<- 137 | 138 | 139 | 21;21;126;21 140 | 141 | 142 | com.umlet.element.custom.SeqObjectActive 143 | 144 | 287 145 | 203 146 | 14 147 | 70 148 | 149 | 150 | 151 | 152 | 153 | com.umlet.element.Relation 154 | 155 | 161 156 | 245 157 | 139 158 | 34 159 | 160 | lt=<<- 161 | 162 | 163 | 21;21;126;21 164 | 165 | 166 | com.umlet.element.custom.SeqObjectActive 167 | 168 | 168 169 | 203 170 | 14 171 | 70 172 | 173 | 174 | 175 | 176 | 177 | com.umlet.element.Relation 178 | 179 | 161 180 | 175 181 | 139 182 | 49 183 | 184 | lt=<<- 185 | setAppBootstrap(obj) 186 | 187 | 126;35;21;35 188 | 189 | 190 | com.umlet.element.custom.InteractionFrame 191 | 192 | 77 193 | 189 194 | 315 195 | 98 196 | 197 | optional 198 | 199 | 200 | 201 | 202 | com.umlet.element.custom.SeqObjectActive 203 | 204 | 168 205 | 315 206 | 14 207 | 70 208 | 209 | 210 | 211 | 212 | 213 | com.umlet.element.Relation 214 | 215 | 161 216 | 287 217 | 139 218 | 49 219 | 220 | lt=<<- 221 | setConfig(obj) 222 | 223 | 126;35;21;35 224 | 225 | 226 | com.umlet.element.custom.InteractionFrame 227 | 228 | 77 229 | 301 230 | 315 231 | 98 232 | 233 | optional 234 | 235 | 236 | 237 | com.umlet.element.custom.SeqObjectActive 238 | 239 | 287 240 | 315 241 | 14 242 | 70 243 | 244 | 245 | 246 | 247 | 248 | com.umlet.element.Relation 249 | 250 | 161 251 | 357 252 | 139 253 | 34 254 | 255 | lt=<<- 256 | 257 | 258 | 21;21;126;21 259 | 260 | 261 | com.umlet.element.Relation 262 | 263 | 609 264 | 0 265 | 41 266 | 2162 267 | 268 | lt=. 269 | 21;21;28;2149 270 | 271 | 272 | com.umlet.element.Relation 273 | 274 | 735 275 | 0 276 | 41 277 | 2155 278 | 279 | lt=. 280 | 21;21;28;2142 281 | 282 | 283 | com.umlet.element.Relation 284 | 285 | 889 286 | 0 287 | 55 288 | 2169 289 | 290 | lt=. 291 | 21;21;42;2156 292 | 293 | 294 | com.umlet.element.Relation 295 | 296 | 427 297 | 0 298 | 34 299 | 2155 300 | 301 | lt=. 302 | 21;21;21;1540;21;2142 303 | 304 | 305 | com.umlet.element.Class 306 | 307 | 413 308 | 0 309 | 70 310 | 21 311 | 312 | _:AppBootstrap_ 313 | 314 | 315 | 316 | com.umlet.element.custom.SeqObjectActive 317 | 318 | 168 319 | 518 320 | 14 321 | 1540 322 | 323 | 324 | 325 | 326 | 327 | com.umlet.element.Relation 328 | 329 | 161 330 | 497 331 | 139 332 | 49 333 | 334 | lt=<<- 335 | Up() 336 | 337 | 126;35;21;35 338 | 339 | 340 | com.umlet.element.custom.SeqObjectActive 341 | 342 | 287 343 | 518 344 | 14 345 | 1526 346 | 347 | 348 | 349 | 350 | 351 | com.umlet.element.custom.InteractionFrame 352 | 353 | 189 354 | 560 355 | 455 356 | 420 357 | 358 | alt 359 | appPath=null 360 | 361 | 362 | 363 | com.umlet.element.Relation 364 | 365 | 280 366 | 812 367 | 163 368 | 69 369 | 370 | lt=<<- 371 | setAppPath( proccess.cwd()) 372 | 373 | 21;56;91;56;91;21;21;21 374 | 375 | 376 | com.umlet.element.Relation 377 | 378 | 280 379 | 973 380 | 174 381 | 49 382 | 383 | lt=<<- 384 | bootstrap() 385 | 386 | 161;35;21;35 387 | 388 | 389 | com.umlet.element.custom.SeqObjectActive 390 | 391 | 441 392 | 973 393 | 14 394 | 959 395 | 396 | 397 | 398 | 399 | 400 | com.umlet.element.custom.InteractionFrame 401 | 402 | 238 403 | 1029 404 | 490 405 | 168 406 | 407 | alt 408 | 409 | ginger.config==null 410 | 411 | 412 | 413 | com.umlet.element.Relation 414 | 415 | 434 416 | 1022 417 | 209 418 | 49 419 | 420 | lt=<<- 421 | checkConfig() 422 | 423 | 196;35;21;35 424 | 425 | 426 | com.umlet.element.custom.SeqObjectActive 427 | 428 | 630 429 | 1043 430 | 14 431 | 70 432 | 433 | 434 | 435 | 436 | 437 | com.umlet.element.Relation 438 | 439 | 434 440 | 1064 441 | 209 442 | 49 443 | 444 | lt=<<- 445 | config 446 | 447 | 21;35;196;35 448 | 449 | 450 | com.umlet.element.Relation 451 | 452 | 280 453 | 1092 454 | 174 455 | 49 456 | 457 | lt=<<- 458 | setConfig(obj) 459 | 460 | 21;35;161;35 461 | 462 | 463 | com.umlet.element.custom.InteractionFrame 464 | 465 | 343 466 | 1372 467 | 679 468 | 483 469 | 470 | loop[readModule] 471 | 472 | 473 | 474 | com.umlet.element.Relation 475 | 476 | 434 477 | 1183 478 | 209 479 | 49 480 | 481 | lt=<<- 482 | getComponents() 483 | 484 | 196;35;21;35 485 | 486 | 487 | com.umlet.element.Relation 488 | 489 | 434 490 | 1204 491 | 209 492 | 49 493 | 494 | lt=<<- 495 | [components] 496 | 497 | 21;35;196;35 498 | 499 | 500 | com.umlet.element.custom.SeqObjectActive 501 | 502 | 630 503 | 1204 504 | 14 505 | 49 506 | 507 | 508 | 509 | 510 | 511 | 512 | com.umlet.element.Relation 513 | 514 | 434 515 | 1386 516 | 209 517 | 49 518 | 519 | lt=<<- 520 | [controllers] 521 | 522 | 21;35;196;35 523 | 524 | 525 | com.umlet.element.Relation 526 | 527 | 434 528 | 1358 529 | 209 530 | 49 531 | 532 | lt=<<- 533 | getController() 534 | 535 | 196;35;21;35 536 | 537 | 538 | com.umlet.element.custom.SeqObjectActive 539 | 540 | 630 541 | 1386 542 | 14 543 | 63 544 | 545 | 546 | 547 | 548 | 549 | com.umlet.element.custom.SeqObjectActive 550 | 551 | 756 552 | 1463 553 | 14 554 | 63 555 | 556 | 557 | 558 | 559 | 560 | com.umlet.element.Relation 561 | 562 | 441 563 | 1435 564 | 321 565 | 49 566 | 567 | lt=<<- 568 | create(controllers) 569 | 570 | 308;35;21;35 571 | 572 | 573 | com.umlet.element.Relation 574 | 575 | 434 576 | 1498 577 | 335 578 | 34 579 | 580 | lt=<<- 581 | 582 | 583 | 21;21;322;21 584 | 585 | 586 | com.umlet.element.Relation 587 | 588 | 434 589 | 1498 590 | 209 591 | 49 592 | 593 | lt=<<- 594 | getModels() 595 | 596 | 196;35;21;35 597 | 598 | 599 | com.umlet.element.Relation 600 | 601 | 434 602 | 1526 603 | 209 604 | 49 605 | 606 | lt=<<- 607 | [models] 608 | 609 | 21;35;196;35 610 | 611 | 612 | com.umlet.element.custom.SeqObjectActive 613 | 614 | 630 615 | 1526 616 | 14 617 | 63 618 | 619 | 620 | 621 | 622 | 623 | com.umlet.element.Relation 624 | 625 | 441 626 | 1568 627 | 482 628 | 49 629 | 630 | lt=<<- 631 | create(Models) 632 | 633 | 469;35;21;35 634 | 635 | 636 | com.umlet.element.Relation 637 | 638 | 434 639 | 1631 640 | 496 641 | 34 642 | 643 | lt=<<- 644 | 645 | 646 | 21;21;483;21 647 | 648 | 649 | com.umlet.element.custom.SeqObjectActive 650 | 651 | 917 652 | 1596 653 | 14 654 | 63 655 | 656 | 657 | 658 | 659 | 660 | 661 | com.umlet.element.Relation 662 | 663 | 280 664 | 1218 665 | 167 666 | 49 667 | 668 | lt=<<- 669 | setComponentMap() 670 | 671 | 21;35;154;35 672 | 673 | 674 | com.umlet.element.custom.SeqObjectActive 675 | 676 | 630 677 | 1274 678 | 14 679 | 49 680 | 681 | 682 | 683 | 684 | 685 | com.umlet.element.Relation 686 | 687 | 434 688 | 1274 689 | 209 690 | 49 691 | 692 | lt=<<- 693 | [gateways] 694 | 695 | 21;35;196;35 696 | 697 | 698 | com.umlet.element.Relation 699 | 700 | 434 701 | 1253 702 | 209 703 | 49 704 | 705 | lt=<<- 706 | getGateways() 707 | 708 | 196;35;21;35 709 | 710 | 711 | com.umlet.element.Relation 712 | 713 | 280 714 | 1246 715 | 174 716 | 34 717 | 718 | lt=<<- 719 | 720 | 721 | 161;21;21;21 722 | 723 | 724 | com.umlet.element.Relation 725 | 726 | 280 727 | 1281 728 | 167 729 | 49 730 | 731 | lt=<<- 732 | setGatewayMap() 733 | 734 | 21;35;154;35 735 | 736 | 737 | com.umlet.element.Relation 738 | 739 | 280 740 | 1323 741 | 167 742 | 34 743 | 744 | lt=<<- 745 | 746 | 747 | 154;21;21;21 748 | 749 | 750 | com.umlet.element.custom.InteractionFrame 751 | 752 | 196 753 | 987 754 | 987 755 | 1120 756 | 757 | Bootstraping 758 | bg=blue 759 | 760 | 761 | 762 | com.umlet.element.Relation 763 | 764 | 280 765 | 1897 766 | 174 767 | 34 768 | 769 | lt=<<- 770 | 771 | 772 | 21;21;161;21 773 | 774 | 775 | com.umlet.element.Relation 776 | 777 | 154 778 | 1925 779 | 146 780 | 49 781 | 782 | lt=<<- 783 | true 784 | 785 | 21;35;133;35 786 | 787 | 788 | com.umlet.element.Note 789 | 790 | 518 791 | 28 792 | 98 793 | 56 794 | 795 | This is a folder, 796 | all method invokations are AppBootstrap 797 | methods applied to the folder structure 798 | 799 | 800 | 801 | com.umlet.element.custom.InteractionFrame 802 | 803 | 196 804 | 609 805 | 245 806 | 140 807 | 808 | alt 809 | config['app']['dir']!=null 810 | 811 | 812 | 813 | com.umlet.element.Relation 814 | 815 | 280 816 | 658 817 | 165 818 | 69 819 | 820 | lt=<<- 821 | setAppPath(config['app']['dir']) 822 | 823 | 21;56;91;56;91;21;21;21 824 | 825 | 826 | com.umlet.element.custom.InteractionFrame 827 | 828 | 196 829 | 770 830 | 245 831 | 140 832 | 833 | alt 834 | config['app']['dir']==null 835 | 836 | 837 | 838 | com.umlet.element.custom.SeqObjectActive 839 | 840 | 168 841 | 434 842 | 14 843 | 70 844 | 845 | 846 | 847 | 848 | 849 | com.umlet.element.Relation 850 | 851 | 161 852 | 406 853 | 139 854 | 49 855 | 856 | lt=<<- 857 | setPreLaunch(func) 858 | 859 | 126;35;21;35 860 | 861 | 862 | com.umlet.element.Relation 863 | 864 | 161 865 | 476 866 | 139 867 | 34 868 | 869 | lt=<<- 870 | 871 | 872 | 21;21;126;21 873 | 874 | 875 | com.umlet.element.custom.SeqObjectActive 876 | 877 | 287 878 | 434 879 | 14 880 | 70 881 | 882 | 883 | 884 | 885 | 886 | com.umlet.element.custom.InteractionFrame 887 | 888 | 77 889 | 420 890 | 315 891 | 98 892 | 893 | optional 894 | 895 | 896 | 897 | com.umlet.element.custom.InteractionFrame 898 | 899 | 0 900 | 1925 901 | 441 902 | 63 903 | 904 | alt 905 | preLaunch()=true 906 | 907 | 908 | 909 | com.umlet.element.custom.InteractionFrame 910 | 911 | 0 912 | 2002 913 | 448 914 | 63 915 | 916 | alt 917 | preLaunch()=false 918 | 919 | 920 | 921 | com.umlet.element.Relation 922 | 923 | 161 924 | 1981 925 | 139 926 | 49 927 | 928 | lt=<<- 929 | false 930 | 931 | 21;35;126;35 932 | 933 | 934 | com.umlet.element.Relation 935 | 936 | 280 937 | 1911 938 | 100 939 | 55 940 | 941 | lt=<<- 942 | launch() 943 | 944 | 21;42;70;42;70;28;21;28 945 | 946 | 947 | --------------------------------------------------------------------------------