diff --git a/README.md b/README.md index 4b762bfb..773f7daa 100644 --- a/README.md +++ b/README.md @@ -1,69 +1,72 @@ -LoopBack is a highly extensible, open source Node.js framework that enables you to: +LoopBack is a highly-extensible, open-source Node.js framework that enables you to: - * Create dynamic end-to-end REST APIs with little or no coding - * Easily access data from Oracle, MySQL, PostgreSQL, MS SQL Server, MongoDB, SOAP and other REST APIs - * Incorporate model relationships and access controls for complex APIs - * Run your application on-premises or in the cloud - * Use built-in push, geolocation, and file services for mobile use cases - * Easily create client apps using Android, iOS, and JavaScript SDKs + * Create dynamic end-to-end REST APIs with little or no coding. + * Access data from Oracle, MySQL, PostgreSQL, MS SQL Server, MongoDB, SOAP and other REST APIs. + * Incorporate model relationships and access controls for complex APIs. + * Use built-in push, geolocation, and file services for mobile apps. + * Easily create client apps using Android, iOS, and JavaScript SDKs. + * Run your application on-premises or in the cloud. LoopBack consists of: + * A library of Node.js modules. - * A command line tool, `slc`, for creating and working with LoopBack applications. - * Client SDKs for native and web-based mobile clients. + * [Yeoman](http://yeoman.io/) generators for scaffolding applications. + * Client SDKs for iOS, Android, and web clients. For more details, see http://loopback.io/. ## LoopBack modules -In addition to the [main LoopBack module](https://github.com/strongloop/loopback), LoopBack consists of numerous other modules that implement specific functionality, -as illustrated below: +The LoopBack framework includes of a set of Node.js modules that you can use independently or together. ![LoopBack modules](https://github.com/strongloop/loopback/raw/master/docs/assets/lb-modules.png "LoopBack modules") -* Frameworks - * [loopback](https://github.com/strongloop/loopback) - * [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler) - * [strong-remoting](https://github.com/strongloop/strong-remoting) +### Core +* [loopback](https://github.com/strongloop/loopback) +* [loopback-datasource-juggler](https://github.com/strongloop/loopback-datasource-juggler) +* [strong-remoting](https://github.com/strongloop/strong-remoting) -* Enterprise Connectors - * [loopback-connector-mongodb](https://github.com/strongloop/loopback-connector-mongodb) - * [loopback-connector-mysql](https://github.com/strongloop/loopback-connector-mysql) - * [loopback-connector-oracle](https://github.com/strongloop/loopback-connector-oracle) - * [loopback-connector-mssql](https://github.com/strongloop/loopback-connector-mssql) - * [loopback-connector-postgresql](https://github.com/strongloop/loopback-connector-postgresql) - * [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest) - * [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap) +### Connectors +* [loopback-connector-mongodb](https://github.com/strongloop/loopback-connector-mongodb) +* [loopback-connector-mysql](https://github.com/strongloop/loopback-connector-mysql) +* [loopback-connector-postgresql](https://github.com/strongloop/loopback-connector-postgresql) +* [loopback-connector-rest](https://github.com/strongloop/loopback-connector-rest) -* Mobile Components - * [loopback-component-push](https://github.com/strongloop/loopback-component-push) - * [loopback-component-storage](https://github.com/strongloop/loopback-component-storage) +### Enterprise Connectors +* [loopback-connector-oracle](https://github.com/strongloop/loopback-connector-oracle) +* [loopback-connector-mssql](https://github.com/strongloop/loopback-connector-mssql) +* [loopback-connector-soap](https://github.com/strongloop/loopback-connector-soap) +* [loopback-connector-atg](https://github.com/strongloop/loopback-connector-atg) -* Security Components - * [loopback-component-passport](https://github.com/strongloop/loopback-component-passport) +### Components +* [loopback-component-push](https://github.com/strongloop/loopback-component-push) +* [loopback-component-storage](https://github.com/strongloop/loopback-component-storage) +* [loopback-component-passport](https://github.com/strongloop/loopback-component-passport) -* Clients - * [loopback-sdk-ios](https://github.com/strongloop/loopback-sdk-ios) - * [loopback-sdk-android](https://github.com/strongloop/loopback-sdk-android) - * [loopback-sdk-angular](https://github.com/strongloop/loopback-sdk-angular) +### Client SDKs +* [loopback-sdk-ios](https://github.com/strongloop/loopback-sdk-ios) +* [loopback-sdk-android](https://github.com/strongloop/loopback-sdk-android) +* [loopback-sdk-angular](https://github.com/strongloop/loopback-sdk-angular) * [loopback-sdk-angular-cli](https://github.com/strongloop/loopback-sdk-angular-cli) * [grunt-loopback-sdk-angular](https://github.com/strongloop/grunt-loopback-sdk-angular) -* Tools - * [loopback-explorer](https://github.com/strongloop/loopback-explorer) - * [loopback-workspace](https://github.com/strongloop/loopback-workspace) - * [strong-cli](https://github.com/strongloop/strong-cli) +### Tools +* [loopback-explorer](https://github.com/strongloop/loopback-explorer) +* [loopback-workspace](https://github.com/strongloop/loopback-workspace) +* [generator-loopback](https://github.com/strongloop/generator-loopback) -* Examples - * [loopback-example-database](https://github.com/strongloop/loopback-example-database) - * [loopback-example-datagraph](https://github.com/strongloop/loopback-example-datagraph) - * [loopback-example-full-stack](https://github.com/strongloop/loopback-example-full-stack) - * [loopback-example-office-supplies](https://github.com/strongloop/loopback-example-office-supplies) - * [loopback-example-todo](https://github.com/strongloop/loopback-example-todo) - * [loopback-example-access-control](https://github.com/strongloop/loopback-example-access-control) - * [loopback-example-proxy](https://github.com/strongloop/loopback-example-proxy) - * [strongloop-community/loopback-examples-ios](https://github.com/strongloop-community/loopback-examples-ios) - * [loopback-example-ssl](https://github.com/strongloop/loopback-example-ssl) +### Examples + +* [loopback-example-app](https://github.com/strongloop/loopback-example-app) +* [loopback-example-database](https://github.com/strongloop/loopback-example-database) +* [loopback-example-datagraph](https://github.com/strongloop/loopback-example-datagraph) +* [loopback-example-full-stack](https://github.com/strongloop/loopback-example-full-stack) +* [loopback-example-office-supplies](https://github.com/strongloop/loopback-example-office-supplies) +* [loopback-example-todo](https://github.com/strongloop/loopback-example-todo) +* [loopback-example-access-control](https://github.com/strongloop/loopback-example-access-control) +* [loopback-example-proxy](https://github.com/strongloop/loopback-example-proxy) +* [strongloop-community/loopback-examples-ios](https://github.com/strongloop-community/loopback-examples-ios) +* [loopback-example-ssl](https://github.com/strongloop/loopback-example-ssl) ## Resources @@ -71,7 +74,6 @@ as illustrated below: * [API documentation](http://apidocs.strongloop.com/loopback). * [LoopBack Google Group](https://groups.google.com/forum/#!forum/loopbackjs). * [GitHub issues](https://github.com/strongloop/loopback/issues). - * Read more about the [LoopBack's features](https://github.com/strongloop/loopback/wiki/Features). ## Contributing diff --git a/docs/assets/lb-modules.png b/docs/assets/lb-modules.png index 966c30e1..b392bd0c 100644 Binary files a/docs/assets/lb-modules.png and b/docs/assets/lb-modules.png differ diff --git a/lib/application.js b/lib/application.js index 84884098..9943f0a9 100644 --- a/lib/application.js +++ b/lib/application.js @@ -152,9 +152,11 @@ app.model = function (Model, config) { if (isPublic && Model.sharedClass) { this.remotes().addClass(Model.sharedClass); - if (Model.settings.trackChanges && Model.Change) + if (Model.settings.trackChanges && Model.Change) { this.remotes().addClass(Model.Change.sharedClass); + } clearHandlerCache(this); + this.emit('modelRemoted', Model.sharedClass); } Model.shared = isPublic; diff --git a/lib/browser-express.js b/lib/browser-express.js index 82aba2fa..2a7dbe91 100644 --- a/lib/browser-express.js +++ b/lib/browser-express.js @@ -1,3 +1,6 @@ +var EventEmitter = require('events').EventEmitter; +var util = require('util'); + module.exports = browserExpress; function browserExpress() { @@ -10,6 +13,8 @@ function BrowserExpress() { this.settings = {}; } +util.inherits(BrowserExpress, EventEmitter); + BrowserExpress.prototype.set = function(key, value) { if (arguments.length == 1) { return this.get(key); diff --git a/lib/connectors/mail.js b/lib/connectors/mail.js index 1e9b346a..3e6e0d39 100644 --- a/lib/connectors/mail.js +++ b/lib/connectors/mail.js @@ -59,10 +59,17 @@ MailConnector.prototype.setupTransport = function(setting) { connector.transports = connector.transports || []; connector.transportsIndex = connector.transportsIndex || {}; - var transportModuleName = 'nodemailer-' + (setting.type || 'STUB').toLowerCase() + '-transport'; - var transportModule = require(transportModuleName); - - var transport = mailer.createTransport(transportModule(setting)); + var transport; + var transportType = (setting.type || 'STUB').toLowerCase(); + if (transportType === 'direct') { + transport = mailer.createTransport(); + } else if (transportType === 'smtp') { + transport = mailer.createTransport(setting); + } else { + var transportModuleName = 'nodemailer-' + transportType + '-transport'; + var transportModule = require(transportModuleName); + transport = mailer.createTransport(transportModule(setting)); + } connector.transportsIndex[setting.type] = transport; connector.transports.push(transport); diff --git a/lib/models/model.js b/lib/models/model.js index cc860e61..c5fb92c2 100644 --- a/lib/models/model.js +++ b/lib/models/model.js @@ -136,7 +136,8 @@ Model.setup = function () { var idDesc = ModelCtor.modelName + ' id'; ModelCtor.sharedCtor.accepts = [ - {arg: 'id', type: 'any', http: {source: 'path'}, description: idDesc} + {arg: 'id', type: 'any', required: true, http: {source: 'path'}, + description: idDesc} // {arg: 'instance', type: 'object', http: {source: 'body'}} ]; @@ -347,6 +348,7 @@ Model.belongsToRemoting = function(relationName, relation, define) { } Model.hasManyRemoting = function (relationName, relation, define) { + var toModelName = relation.modelTo.modelName; var findByIdFunc = this.prototype['__findById__' + relationName]; define('__findById__' + relationName, { isStatic: false, @@ -355,7 +357,7 @@ Model.hasManyRemoting = function (relationName, relation, define) { description: 'Foreign key for ' + relationName, required: true, http: {source: 'path'}}, description: 'Find a related item by id for ' + relationName, - returns: {arg: 'result', type: relation.modelTo.modelName, root: true} + returns: {arg: 'result', type: toModelName, root: true} }, findByIdFunc); var destroyByIdFunc = this.prototype['__destroyById__' + relationName]; @@ -373,11 +375,14 @@ Model.hasManyRemoting = function (relationName, relation, define) { define('__updateById__' + relationName, { isStatic: false, http: {verb: 'put', path: '/' + relationName + '/:fk'}, - accepts: {arg: 'fk', type: 'any', - description: 'Foreign key for ' + relationName, required: true, - http: {source: 'path'}}, + accepts: [ + {arg: 'fk', type: 'any', + description: 'Foreign key for ' + relationName, required: true, + http: {source: 'path'}}, + {arg: 'data', type: toModelName, http: {source: 'body'}} + ], description: 'Update a related item by id for ' + relationName, - returns: {arg: 'result', type: relation.modelTo.modelName, root: true} + returns: {arg: 'result', type: toModelName, root: true} }, updateByIdFunc); if (relation.modelThrough) { @@ -432,7 +437,7 @@ Model.scopeRemoting = function(relationName, relation, define) { define('__create__' + relationName, { isStatic: false, http: {verb: 'post', path: '/' + relationName}, - accepts: {arg: 'data', type: 'object', http: {source: 'body'}}, + accepts: {arg: 'data', type: toModelName, http: {source: 'body'}}, description: 'Creates a new instance in ' + relationName + ' of this model.', returns: {arg: 'data', type: toModelName, root: true} }); diff --git a/package.json b/package.json index 05fd3e78..668e0f66 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "loopback", - "description": "LoopBack: Open API Framework for Node.js", + "description": "LoopBack: Open Source Framework for Node.js", "homepage": "http://loopback.io", "keywords": [ "restful", @@ -26,7 +26,7 @@ "mobile", "mBaaS" ], - "version": "2.0.0", + "version": "2.0.1", "scripts": { "test": "grunt mocha-and-karma" }, diff --git a/test/app.test.js b/test/app.test.js index 8bed1cb2..699f175d 100644 --- a/test/app.test.js +++ b/test/app.test.js @@ -47,6 +47,18 @@ describe('app', function() { expect(app.models.Color).to.equal(Color); }); + it("emits a `modelRemoted` event", function() { + var Color = PersistedModel.extend('color', {name: String}); + Color.shared = true; + var remotedClass; + app.on('modelRemoted', function(sharedClass) { + remotedClass = sharedClass; + }); + app.model(Color); + expect(remotedClass).to.exist; + expect(remotedClass).to.eql(Color.sharedClass); + }); + it.onServer('updates REST API when a new model is added', function(done) { app.use(loopback.rest()); request(app).get('/colors').expect(404, function(err, res) { diff --git a/test/email.test.js b/test/email.test.js index 325330a0..690ed1c2 100644 --- a/test/email.test.js +++ b/test/email.test.js @@ -1,6 +1,30 @@ var loopback = require('../'); var MyEmail; var assert = require('assert'); +var MailConnector = require('../lib/connectors/mail'); + +describe('Email connector', function () { + it('should set up SMTP', function () { + var connector = new MailConnector({transports: [ + {type: 'smtp', service: 'gmail'} + ]}); + assert(connector.transportForName('smtp')); + }); + + it('should set up DIRECT', function () { + var connector = new MailConnector({transports: [ + {type: 'direct', name: 'localhost'} + ]}); + assert(connector.transportForName('direct')); + }); + + it('should set up STUB', function () { + var connector = new MailConnector({transports: [ + {type: 'stub', service: 'gmail'} + ]}); + assert(connector.transportForName('stub')); + }); +}); describe('Email and SMTP', function () { beforeEach(function() {