2016-05-03 22:50:21 +00:00
|
|
|
// Copyright IBM Corp. 2013,2016. All Rights Reserved.
|
|
|
|
// Node module: loopback
|
|
|
|
// This file is licensed under the MIT License.
|
|
|
|
// License text available at https://opensource.org/licenses/MIT
|
|
|
|
|
2014-01-06 23:52:08 +00:00
|
|
|
/*!
|
2013-05-01 19:11:43 +00:00
|
|
|
* Module dependencies.
|
|
|
|
*/
|
|
|
|
|
2016-11-15 21:46:23 +00:00
|
|
|
'use strict';
|
2016-09-16 19:31:48 +00:00
|
|
|
var g = require('./globalize');
|
2014-10-30 20:49:47 +00:00
|
|
|
var DataSource = require('loopback-datasource-juggler').DataSource;
|
2015-04-01 21:50:36 +00:00
|
|
|
var Registry = require('./registry');
|
2014-10-30 20:49:47 +00:00
|
|
|
var assert = require('assert');
|
|
|
|
var fs = require('fs');
|
|
|
|
var extend = require('util')._extend;
|
|
|
|
var RemoteObjects = require('strong-remoting');
|
2015-03-25 13:02:16 +00:00
|
|
|
var classify = require('underscore.string/classify');
|
|
|
|
var camelize = require('underscore.string/camelize');
|
2014-10-30 20:49:47 +00:00
|
|
|
var path = require('path');
|
2015-09-19 02:13:30 +00:00
|
|
|
var util = require('util');
|
2013-05-01 19:11:43 +00:00
|
|
|
|
|
|
|
/**
|
2014-01-06 23:52:08 +00:00
|
|
|
* The `App` object represents a Loopback application.
|
2014-10-16 22:54:40 +00:00
|
|
|
*
|
2014-01-06 23:52:08 +00:00
|
|
|
* The App object extends [Express](http://expressjs.com/api.html#express) and
|
2014-10-15 07:07:30 +00:00
|
|
|
* supports Express middleware. See
|
|
|
|
* [Express documentation](http://expressjs.com/) for details.
|
2014-10-16 22:54:40 +00:00
|
|
|
*
|
2014-01-06 23:52:08 +00:00
|
|
|
* ```js
|
|
|
|
* var loopback = require('loopback');
|
|
|
|
* var app = loopback();
|
2014-10-16 22:54:40 +00:00
|
|
|
*
|
2014-01-06 23:52:08 +00:00
|
|
|
* app.get('/', function(req, res){
|
|
|
|
* res.send('hello world');
|
|
|
|
* });
|
2014-10-16 22:54:40 +00:00
|
|
|
*
|
2014-01-06 23:52:08 +00:00
|
|
|
* app.listen(3000);
|
|
|
|
* ```
|
2014-10-16 22:54:40 +00:00
|
|
|
*
|
2014-01-06 23:52:08 +00:00
|
|
|
* @class LoopBackApplication
|
2014-01-13 16:02:32 +00:00
|
|
|
* @header var app = loopback()
|
2014-01-06 23:52:08 +00:00
|
|
|
*/
|
|
|
|
function App() {
|
2014-01-13 16:02:32 +00:00
|
|
|
// this is a dummy placeholder for jsdox
|
2014-01-06 23:52:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/*!
|
2013-05-01 19:11:43 +00:00
|
|
|
* Export the app prototype.
|
|
|
|
*/
|
|
|
|
|
2014-11-04 07:13:21 +00:00
|
|
|
var app = module.exports = {};
|
2013-05-01 19:11:43 +00:00
|
|
|
|
|
|
|
/**
|
2014-01-06 23:52:08 +00:00
|
|
|
* Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).
|
|
|
|
*
|
2014-04-02 22:15:21 +00:00
|
|
|
* **NOTE:** Calling `app.remotes()` more than once returns only a single set of remote objects.
|
2014-01-06 23:52:08 +00:00
|
|
|
* @returns {RemoteObjects}
|
2013-05-23 16:53:42 +00:00
|
|
|
*/
|
2013-06-17 15:01:22 +00:00
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
app.remotes = function() {
|
|
|
|
if (this._remotes) {
|
2013-05-23 16:53:42 +00:00
|
|
|
return this._remotes;
|
|
|
|
} else {
|
2014-04-14 19:25:41 +00:00
|
|
|
var options = {};
|
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
if (this.get) {
|
2014-04-14 19:25:41 +00:00
|
|
|
options = this.get('remoting');
|
|
|
|
}
|
2014-10-16 22:54:40 +00:00
|
|
|
|
2014-02-19 23:14:31 +00:00
|
|
|
return (this._remotes = RemoteObjects.create(options));
|
2013-05-23 16:53:42 +00:00
|
|
|
}
|
2014-10-16 22:54:40 +00:00
|
|
|
};
|
2013-05-23 16:53:42 +00:00
|
|
|
|
2014-01-06 23:52:08 +00:00
|
|
|
/*!
|
2013-05-23 16:53:42 +00:00
|
|
|
* Remove a route by reference.
|
2013-05-01 19:11:43 +00:00
|
|
|
*/
|
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
app.disuse = function(route) {
|
|
|
|
if (this.stack) {
|
2013-05-01 19:11:43 +00:00
|
|
|
for (var i = 0; i < this.stack.length; i++) {
|
2014-10-30 20:49:47 +00:00
|
|
|
if (this.stack[i].route === route) {
|
2013-05-01 19:11:43 +00:00
|
|
|
this.stack.splice(i, 1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2014-10-16 22:54:40 +00:00
|
|
|
};
|
2013-05-01 19:11:43 +00:00
|
|
|
|
2013-05-24 14:59:23 +00:00
|
|
|
/**
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
* Attach a model to the app. The `Model` will be available on the
|
2014-01-06 23:52:08 +00:00
|
|
|
* `app.models` object.
|
2013-05-01 19:11:43 +00:00
|
|
|
*
|
2014-10-15 07:07:30 +00:00
|
|
|
* Example - Attach an existing model:
|
|
|
|
```js
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
* var User = loopback.User;
|
|
|
|
* app.model(User);
|
2014-10-15 07:07:30 +00:00
|
|
|
*```
|
|
|
|
* Example - Attach an existing model, alter some aspects of the model:
|
|
|
|
* ```js
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
* var User = loopback.User;
|
|
|
|
* app.model(User, { dataSource: 'db' });
|
2014-10-15 07:07:30 +00:00
|
|
|
*```
|
2014-10-16 22:54:40 +00:00
|
|
|
*
|
2016-06-03 20:51:48 +00:00
|
|
|
* @param {Object} Model The model to attach.
|
2014-04-02 22:15:21 +00:00
|
|
|
* @options {Object} config The model's configuration.
|
2014-10-15 07:07:30 +00:00
|
|
|
* @property {String|DataSource} dataSource The `DataSource` to which to attach the model.
|
|
|
|
* @property {Boolean} [public] Whether the model should be exposed via REST API.
|
|
|
|
* @property {Object} [relations] Relations to add/update.
|
2014-01-06 23:52:08 +00:00
|
|
|
* @end
|
|
|
|
* @returns {ModelConstructor} the model class
|
2013-05-01 19:11:43 +00:00
|
|
|
*/
|
2013-05-24 14:59:23 +00:00
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
app.model = function(Model, config) {
|
2014-06-10 06:53:01 +00:00
|
|
|
var isPublic = true;
|
2015-04-01 21:50:36 +00:00
|
|
|
var registry = this.registry;
|
|
|
|
|
2016-06-03 20:51:48 +00:00
|
|
|
if (typeof Model === 'string') {
|
|
|
|
var msg = 'app.model(modelName, settings) is no longer supported. ' +
|
|
|
|
'Use app.registry.createModel(modelName, definition) and ' +
|
|
|
|
'app.model(ModelCtor, config) instead.';
|
|
|
|
throw new Error(msg);
|
|
|
|
}
|
|
|
|
|
2014-06-10 06:53:01 +00:00
|
|
|
if (arguments.length > 1) {
|
|
|
|
config = config || {};
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
configureModel(Model, config, this);
|
2014-06-10 06:53:01 +00:00
|
|
|
isPublic = config.public !== false;
|
|
|
|
} else {
|
2015-04-01 21:50:36 +00:00
|
|
|
assert(Model.prototype instanceof Model.registry.getModel('Model'),
|
2014-07-16 18:04:23 +00:00
|
|
|
Model.modelName + ' must be a descendant of loopback.Model');
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
var modelName = Model.modelName;
|
2013-10-29 21:12:23 +00:00
|
|
|
this.models[modelName] =
|
2014-06-10 06:53:01 +00:00
|
|
|
this.models[classify(modelName)] =
|
|
|
|
this.models[camelize(modelName)] = Model;
|
2013-10-29 21:12:23 +00:00
|
|
|
|
2014-06-10 06:53:01 +00:00
|
|
|
this.models().push(Model);
|
|
|
|
|
2014-06-13 08:09:25 +00:00
|
|
|
if (isPublic && Model.sharedClass) {
|
2016-08-31 13:25:08 +00:00
|
|
|
this.remotes().defineObjectType(Model.modelName, function(data) {
|
|
|
|
return new Model(data);
|
|
|
|
});
|
2014-06-13 08:09:25 +00:00
|
|
|
this.remotes().addClass(Model.sharedClass);
|
2014-07-25 00:00:27 +00:00
|
|
|
if (Model.settings.trackChanges && Model.Change) {
|
2014-07-15 07:31:16 +00:00
|
|
|
this.remotes().addClass(Model.Change.sharedClass);
|
2014-07-25 00:00:27 +00:00
|
|
|
}
|
2014-06-10 06:53:01 +00:00
|
|
|
clearHandlerCache(this);
|
2014-07-25 00:00:27 +00:00
|
|
|
this.emit('modelRemoted', Model.sharedClass);
|
2013-11-18 20:52:00 +00:00
|
|
|
}
|
2013-11-01 19:53:30 +00:00
|
|
|
|
2016-04-29 23:36:46 +00:00
|
|
|
var self = this;
|
2016-04-27 11:15:24 +00:00
|
|
|
Model.on('remoteMethodDisabled', function(model, methodName) {
|
|
|
|
self.emit('remoteMethodDisabled', model, methodName);
|
|
|
|
});
|
2017-03-29 15:25:56 +00:00
|
|
|
Model.on('remoteMethodAdded', function(model) {
|
|
|
|
self.emit('remoteMethodAdded', model);
|
|
|
|
});
|
2016-04-27 11:15:24 +00:00
|
|
|
|
2014-06-13 08:09:25 +00:00
|
|
|
Model.shared = isPublic;
|
2014-06-10 06:53:01 +00:00
|
|
|
Model.app = this;
|
|
|
|
Model.emit('attached', this);
|
2013-10-29 21:12:23 +00:00
|
|
|
return Model;
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
};
|
|
|
|
|
2013-05-24 14:59:23 +00:00
|
|
|
/**
|
2014-04-02 22:15:21 +00:00
|
|
|
* Get the models exported by the app. Returns only models defined using `app.model()`
|
2014-01-13 16:02:32 +00:00
|
|
|
*
|
2014-04-02 22:15:21 +00:00
|
|
|
* There are two ways to access models:
|
2014-01-13 16:02:32 +00:00
|
|
|
*
|
2014-04-02 22:15:21 +00:00
|
|
|
* 1. Call `app.models()` to get a list of all models.
|
2014-01-13 16:02:32 +00:00
|
|
|
*
|
|
|
|
* ```js
|
|
|
|
* var models = app.models();
|
|
|
|
*
|
2014-10-30 20:49:47 +00:00
|
|
|
* models.forEach(function(Model) {
|
2014-01-13 16:02:32 +00:00
|
|
|
* console.log(Model.modelName); // color
|
|
|
|
* });
|
|
|
|
* ```
|
|
|
|
*
|
2016-06-03 20:51:48 +00:00
|
|
|
* 2. Use `app.models` to access a model by name.
|
2015-10-21 20:47:36 +00:00
|
|
|
* `app.models` has properties for all defined models.
|
2014-01-13 16:02:32 +00:00
|
|
|
*
|
2014-04-02 22:15:21 +00:00
|
|
|
* The following example illustrates accessing the `Product` and `CustomerReceipt` models
|
|
|
|
* using the `models` object.
|
2014-01-13 16:02:32 +00:00
|
|
|
*
|
|
|
|
* ```js
|
|
|
|
* var loopback = require('loopback');
|
|
|
|
* var app = loopback();
|
|
|
|
* app.boot({
|
|
|
|
* dataSources: {
|
|
|
|
* db: {connector: 'memory'}
|
|
|
|
* }
|
|
|
|
* });
|
|
|
|
*
|
2016-06-03 20:51:48 +00:00
|
|
|
* var productModel = app.registry.createModel('product');
|
|
|
|
* app.model(productModel, {dataSource: 'db'});
|
|
|
|
* var customerReceiptModel = app.registry.createModel('customer-receipt');
|
|
|
|
* app.model(customerReceiptModel, {dataSource: 'db'});
|
2014-01-13 16:02:32 +00:00
|
|
|
*
|
|
|
|
* // available based on the given name
|
|
|
|
* var Product = app.models.Product;
|
|
|
|
*
|
|
|
|
* // also available as camelCase
|
|
|
|
* var product = app.models.product;
|
|
|
|
*
|
|
|
|
* // multi-word models are avaiable as pascal cased
|
|
|
|
* var CustomerReceipt = app.models.CustomerReceipt;
|
|
|
|
*
|
|
|
|
* // also available as camelCase
|
|
|
|
* var customerReceipt = app.models.customerReceipt;
|
|
|
|
* ```
|
2014-01-06 23:52:08 +00:00
|
|
|
*
|
2014-04-02 22:15:21 +00:00
|
|
|
* @returns {Array} Array of model classes.
|
2013-05-24 14:59:23 +00:00
|
|
|
*/
|
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
app.models = function() {
|
2013-11-15 04:19:46 +00:00
|
|
|
return this._models || (this._models = []);
|
2014-10-16 22:54:40 +00:00
|
|
|
};
|
2013-05-01 19:11:43 +00:00
|
|
|
|
2013-10-31 17:06:43 +00:00
|
|
|
/**
|
|
|
|
* Define a DataSource.
|
2014-01-06 23:52:08 +00:00
|
|
|
*
|
|
|
|
* @param {String} name The data source name
|
2014-07-31 04:57:08 +00:00
|
|
|
* @param {Object} config The data source config
|
2013-10-31 17:06:43 +00:00
|
|
|
*/
|
2014-10-30 20:49:47 +00:00
|
|
|
app.dataSource = function(name, config) {
|
2016-02-26 13:20:07 +00:00
|
|
|
try {
|
|
|
|
var ds = dataSourcesFromConfig(name, config, this.connectors, this.registry);
|
|
|
|
this.dataSources[name] =
|
|
|
|
this.dataSources[classify(name)] =
|
|
|
|
this.dataSources[camelize(name)] = ds;
|
2016-08-26 10:53:38 +00:00
|
|
|
ds.app = this;
|
2016-02-26 13:20:07 +00:00
|
|
|
return ds;
|
|
|
|
} catch (err) {
|
|
|
|
if (err.message) {
|
2016-06-07 14:48:28 +00:00
|
|
|
err.message = g.f('Cannot create data source %s: %s',
|
|
|
|
JSON.stringify(name), err.message);
|
2016-02-26 13:20:07 +00:00
|
|
|
}
|
|
|
|
throw err;
|
|
|
|
}
|
2014-10-16 22:54:40 +00:00
|
|
|
};
|
2013-10-31 17:06:43 +00:00
|
|
|
|
2014-05-28 13:02:55 +00:00
|
|
|
/**
|
|
|
|
* Register a connector.
|
|
|
|
*
|
|
|
|
* When a new data-source is being added via `app.dataSource`, the connector
|
|
|
|
* name is looked up in the registered connectors first.
|
|
|
|
*
|
|
|
|
* Connectors are required to be explicitly registered only for applications
|
|
|
|
* using browserify, because browserify does not support dynamic require,
|
|
|
|
* which is used by LoopBack to automatically load the connector module.
|
|
|
|
*
|
|
|
|
* @param {String} name Name of the connector, e.g. 'mysql'.
|
|
|
|
* @param {Object} connector Connector object as returned
|
|
|
|
* by `require('loopback-connector-{name}')`.
|
|
|
|
*/
|
|
|
|
app.connector = function(name, connector) {
|
|
|
|
this.connectors[name] =
|
|
|
|
this.connectors[classify(name)] =
|
|
|
|
this.connectors[camelize(name)] = connector;
|
|
|
|
};
|
|
|
|
|
2013-05-24 22:08:23 +00:00
|
|
|
/**
|
2013-07-17 21:30:38 +00:00
|
|
|
* Get all remote objects.
|
2014-04-02 22:15:21 +00:00
|
|
|
* @returns {Object} [Remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).
|
2013-05-24 22:08:23 +00:00
|
|
|
*/
|
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
app.remoteObjects = function() {
|
2013-07-17 21:30:38 +00:00
|
|
|
var result = {};
|
2014-05-19 22:56:26 +00:00
|
|
|
|
|
|
|
this.remotes().classes().forEach(function(sharedClass) {
|
|
|
|
result[sharedClass.name] = sharedClass.ctor;
|
2013-07-17 21:30:38 +00:00
|
|
|
});
|
2014-10-16 22:54:40 +00:00
|
|
|
|
2013-07-17 21:30:38 +00:00
|
|
|
return result;
|
2014-10-16 22:54:40 +00:00
|
|
|
};
|
2013-07-25 23:24:00 +00:00
|
|
|
|
|
|
|
/*!
|
|
|
|
* Get a handler of the specified type from the handler cache.
|
2014-10-16 22:54:40 +00:00
|
|
|
* @triggers `mounted` events on shared class constructors (models)
|
2013-07-25 23:24:00 +00:00
|
|
|
*/
|
2014-10-16 22:54:40 +00:00
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
app.handler = function(type, options) {
|
2013-07-25 23:24:00 +00:00
|
|
|
var handlers = this._handlers || (this._handlers = {});
|
2014-10-30 20:49:47 +00:00
|
|
|
if (handlers[type]) {
|
2013-07-25 23:24:00 +00:00
|
|
|
return handlers[type];
|
|
|
|
}
|
2014-10-16 22:54:40 +00:00
|
|
|
|
2013-07-25 23:24:00 +00:00
|
|
|
var remotes = this.remotes();
|
2014-10-11 14:36:29 +00:00
|
|
|
var handler = this._handlers[type] = remotes.handler(type, options);
|
2014-10-16 22:54:40 +00:00
|
|
|
|
2014-08-05 10:14:39 +00:00
|
|
|
remotes.classes().forEach(function(sharedClass) {
|
|
|
|
sharedClass.ctor.emit('mounted', app, sharedClass, remotes);
|
|
|
|
});
|
2014-10-16 22:54:40 +00:00
|
|
|
|
2013-07-25 23:24:00 +00:00
|
|
|
return handler;
|
2014-10-16 22:54:40 +00:00
|
|
|
};
|
2013-07-25 23:24:00 +00:00
|
|
|
|
2013-10-29 21:12:23 +00:00
|
|
|
/**
|
|
|
|
* An object to store dataSource instances.
|
|
|
|
*/
|
|
|
|
|
|
|
|
app.dataSources = app.datasources = {};
|
|
|
|
|
2013-11-15 04:19:46 +00:00
|
|
|
/**
|
|
|
|
* Enable app wide authentication.
|
|
|
|
*/
|
|
|
|
|
2015-04-21 11:46:17 +00:00
|
|
|
app.enableAuth = function(options) {
|
|
|
|
var AUTH_MODELS = ['User', 'AccessToken', 'ACL', 'Role', 'RoleMapping'];
|
|
|
|
|
2013-11-15 04:19:46 +00:00
|
|
|
var remotes = this.remotes();
|
2014-06-06 01:53:30 +00:00
|
|
|
var app = this;
|
2013-11-15 04:19:46 +00:00
|
|
|
|
2015-04-21 11:46:17 +00:00
|
|
|
if (options && options.dataSource) {
|
|
|
|
var appModels = app.registry.modelBuilder.models;
|
|
|
|
AUTH_MODELS.forEach(function(m) {
|
|
|
|
var Model = app.registry.findModel(m);
|
|
|
|
if (!Model) {
|
|
|
|
throw new Error(
|
2016-06-07 14:48:28 +00:00
|
|
|
g.f('Authentication requires model %s to be defined.', m));
|
2015-04-21 11:46:17 +00:00
|
|
|
}
|
|
|
|
|
2016-08-31 13:21:39 +00:00
|
|
|
if (Model.dataSource || Model.app) return;
|
2015-04-21 11:46:17 +00:00
|
|
|
|
2016-08-31 13:21:39 +00:00
|
|
|
// Find descendants of Model that are attached,
|
|
|
|
// for example "Customer" extending "User" model
|
2015-04-21 11:46:17 +00:00
|
|
|
for (var name in appModels) {
|
|
|
|
var candidate = appModels[name];
|
|
|
|
var isSubclass = candidate.prototype instanceof Model;
|
|
|
|
var isAttached = !!candidate.dataSource || !!candidate.app;
|
|
|
|
if (isSubclass && isAttached) return;
|
|
|
|
}
|
|
|
|
|
|
|
|
app.model(Model, {
|
|
|
|
dataSource: options.dataSource,
|
2016-04-01 09:14:26 +00:00
|
|
|
public: m === 'User',
|
2015-04-21 11:46:17 +00:00
|
|
|
});
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2015-05-06 20:51:14 +00:00
|
|
|
remotes.authorization = function(ctx, next) {
|
|
|
|
var method = ctx.method;
|
2013-11-15 04:19:46 +00:00
|
|
|
var req = ctx.req;
|
|
|
|
var Model = method.ctor;
|
|
|
|
var modelInstance = ctx.instance;
|
2015-01-21 18:27:53 +00:00
|
|
|
|
|
|
|
var modelId = modelInstance && modelInstance.id ||
|
|
|
|
// replacement for deprecated req.param()
|
|
|
|
(req.params && req.params.id !== undefined ? req.params.id :
|
|
|
|
req.body && req.body.id !== undefined ? req.body.id :
|
|
|
|
req.query && req.query.id !== undefined ? req.query.id :
|
|
|
|
undefined);
|
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
var modelName = Model.modelName;
|
2013-11-15 04:19:46 +00:00
|
|
|
|
2014-06-06 01:53:30 +00:00
|
|
|
var modelSettings = Model.settings || {};
|
|
|
|
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
|
2014-10-30 20:49:47 +00:00
|
|
|
if (!req.accessToken) {
|
2014-06-06 01:53:30 +00:00
|
|
|
errStatusCode = 401;
|
|
|
|
}
|
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
if (Model.checkAccess) {
|
2013-12-12 03:46:56 +00:00
|
|
|
Model.checkAccess(
|
|
|
|
req.accessToken,
|
|
|
|
modelId,
|
2014-05-31 02:29:30 +00:00
|
|
|
method,
|
2014-08-08 05:19:27 +00:00
|
|
|
ctx,
|
2013-12-12 03:46:56 +00:00
|
|
|
function(err, allowed) {
|
2014-10-30 20:49:47 +00:00
|
|
|
if (err) {
|
2013-12-12 03:46:56 +00:00
|
|
|
console.log(err);
|
|
|
|
next(err);
|
2014-10-30 20:49:47 +00:00
|
|
|
} else if (allowed) {
|
2013-12-12 03:46:56 +00:00
|
|
|
next();
|
|
|
|
} else {
|
2014-06-06 01:53:30 +00:00
|
|
|
var messages = {
|
2014-12-18 20:26:27 +00:00
|
|
|
403: {
|
2016-06-07 14:48:28 +00:00
|
|
|
message: g.f('Access Denied'),
|
2016-04-01 09:14:26 +00:00
|
|
|
code: 'ACCESS_DENIED',
|
2014-12-18 20:26:27 +00:00
|
|
|
},
|
|
|
|
404: {
|
2016-06-07 14:48:28 +00:00
|
|
|
message: (g.f('could not find %s with id %s', modelName, modelId)),
|
2016-04-01 09:14:26 +00:00
|
|
|
code: 'MODEL_NOT_FOUND',
|
2014-12-18 20:26:27 +00:00
|
|
|
},
|
|
|
|
401: {
|
2016-06-07 14:48:28 +00:00
|
|
|
message: g.f('Authorization Required'),
|
2016-04-01 09:14:26 +00:00
|
|
|
code: 'AUTHORIZATION_REQUIRED',
|
|
|
|
},
|
2014-06-06 01:53:30 +00:00
|
|
|
};
|
|
|
|
|
2014-12-18 20:26:27 +00:00
|
|
|
var e = new Error(messages[errStatusCode].message || messages[403].message);
|
2014-06-06 01:53:30 +00:00
|
|
|
e.statusCode = errStatusCode;
|
2014-12-18 20:26:27 +00:00
|
|
|
e.code = messages[errStatusCode].code || messages[403].code;
|
2013-12-12 03:46:56 +00:00
|
|
|
next(e);
|
|
|
|
}
|
2013-11-15 04:19:46 +00:00
|
|
|
}
|
2013-12-12 03:46:56 +00:00
|
|
|
);
|
|
|
|
} else {
|
|
|
|
next();
|
|
|
|
}
|
2015-05-06 20:51:14 +00:00
|
|
|
};
|
2014-02-05 17:46:22 +00:00
|
|
|
|
2017-02-24 13:07:41 +00:00
|
|
|
this._verifyAuthModelRelations();
|
|
|
|
|
2014-02-05 17:46:22 +00:00
|
|
|
this.isAuthEnabled = true;
|
|
|
|
};
|
2013-11-15 04:19:46 +00:00
|
|
|
|
2017-02-24 13:07:41 +00:00
|
|
|
app._verifyAuthModelRelations = function() {
|
|
|
|
// Allow unit-tests (but also LoopBack users) to disable the warnings
|
|
|
|
if (this.get('_verifyAuthModelRelations') === false) return;
|
|
|
|
|
|
|
|
const AccessToken = this.registry.findModel('AccessToken');
|
|
|
|
const User = this.registry.findModel('User');
|
|
|
|
this.models().forEach(Model => {
|
|
|
|
if (Model === AccessToken || Model.prototype instanceof AccessToken) {
|
|
|
|
scheduleVerification(Model, verifyAccessTokenRelations);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (Model === User || Model.prototype instanceof User) {
|
|
|
|
scheduleVerification(Model, verifyUserRelations);
|
|
|
|
}
|
|
|
|
});
|
|
|
|
|
|
|
|
function scheduleVerification(Model, verifyFn) {
|
|
|
|
if (Model.dataSource) {
|
|
|
|
verifyFn(Model);
|
|
|
|
} else {
|
|
|
|
Model.on('attached', () => verifyFn(Model));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function verifyAccessTokenRelations(Model) {
|
|
|
|
const belongsToUser = Model.relations && Model.relations.user;
|
|
|
|
if (belongsToUser) return;
|
|
|
|
|
|
|
|
const relationsConfig = Model.settings.relations || {};
|
|
|
|
const userName = (relationsConfig.user || {}).model;
|
|
|
|
if (userName) {
|
|
|
|
console.warn(
|
|
|
|
'The model %j configures "belongsTo User-like models" relation ' +
|
|
|
|
'with target model %j. However, the model %j is not attached to ' +
|
|
|
|
'the application and therefore cannot be used by this relation. ' +
|
|
|
|
'This typically happens when the application has a custom ' +
|
|
|
|
'custom User subclass, but does not fix AccessToken relations ' +
|
|
|
|
'to use this new model.\n' +
|
|
|
|
'Learn more at http://ibm.biz/setup-loopback-auth',
|
|
|
|
Model.modelName, userName, userName);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.warn(
|
|
|
|
'The model %j does not have "belongsTo User-like model" relation ' +
|
|
|
|
'configured.\n' +
|
|
|
|
'Learn more at http://ibm.biz/setup-loopback-auth',
|
|
|
|
Model.modelName);
|
|
|
|
}
|
|
|
|
|
|
|
|
function verifyUserRelations(Model) {
|
|
|
|
const hasManyTokens = Model.relations && Model.relations.accessTokens;
|
|
|
|
if (hasManyTokens) return;
|
|
|
|
|
|
|
|
const relationsConfig = Model.settings.relations || {};
|
|
|
|
const accessTokenName = (relationsConfig.accessTokens || {}).model;
|
|
|
|
if (accessTokenName) {
|
|
|
|
console.warn(
|
|
|
|
'The model %j configures "hasMany AccessToken-like models" relation ' +
|
|
|
|
'with target model %j. However, the model %j is not attached to ' +
|
|
|
|
'the application and therefore cannot be used by this relation. ' +
|
|
|
|
'This typically happens when the application has a custom ' +
|
|
|
|
'AccessToken subclass, but does not fix User relations to use this ' +
|
|
|
|
'new model.\n' +
|
|
|
|
'Learn more at http://ibm.biz/setup-loopback-auth',
|
|
|
|
Model.modelName, accessTokenName, accessTokenName);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
console.warn(
|
|
|
|
'The model %j does not have "hasMany AccessToken-like models" relation ' +
|
|
|
|
'configured.\n' +
|
|
|
|
'Learn more at http://ibm.biz/setup-loopback-auth',
|
|
|
|
Model.modelName);
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2013-10-29 21:12:23 +00:00
|
|
|
app.boot = function(options) {
|
2014-06-25 11:52:20 +00:00
|
|
|
throw new Error(
|
2016-08-04 21:26:01 +00:00
|
|
|
g.f('{{`app.boot`}} was removed, use the new module {{loopback-boot}} instead'));
|
2014-10-16 22:54:40 +00:00
|
|
|
};
|
2013-10-29 21:12:23 +00:00
|
|
|
|
2015-04-01 21:50:36 +00:00
|
|
|
function dataSourcesFromConfig(name, config, connectorRegistry, registry) {
|
2013-11-01 19:53:30 +00:00
|
|
|
var connectorPath;
|
|
|
|
|
2014-10-16 22:54:40 +00:00
|
|
|
assert(typeof config === 'object',
|
2016-02-19 04:36:07 +00:00
|
|
|
'can not create data source without config object');
|
2013-11-01 19:53:30 +00:00
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
if (typeof config.connector === 'string') {
|
2015-04-01 21:50:36 +00:00
|
|
|
name = config.connector;
|
2014-05-28 13:02:55 +00:00
|
|
|
if (connectorRegistry[name]) {
|
|
|
|
config.connector = connectorRegistry[name];
|
|
|
|
} else {
|
|
|
|
connectorPath = path.join(__dirname, 'connectors', name + '.js');
|
2013-11-01 19:53:30 +00:00
|
|
|
|
2014-05-28 13:02:55 +00:00
|
|
|
if (fs.existsSync(connectorPath)) {
|
|
|
|
config.connector = require(connectorPath);
|
|
|
|
}
|
2013-11-01 19:53:30 +00:00
|
|
|
}
|
2016-11-23 22:37:56 +00:00
|
|
|
if (config.connector && typeof config.connector === 'object' && !config.connector.name)
|
2016-02-26 13:20:07 +00:00
|
|
|
config.connector.name = name;
|
2013-11-01 19:53:30 +00:00
|
|
|
}
|
|
|
|
|
2014-06-06 09:47:25 +00:00
|
|
|
return registry.createDataSource(config);
|
2013-10-29 21:12:23 +00:00
|
|
|
}
|
|
|
|
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
function configureModel(ModelCtor, config, app) {
|
2015-04-01 21:50:36 +00:00
|
|
|
assert(ModelCtor.prototype instanceof ModelCtor.registry.getModel('Model'),
|
2014-07-16 18:04:23 +00:00
|
|
|
ModelCtor.modelName + ' must be a descendant of loopback.Model');
|
2014-05-25 14:27:45 +00:00
|
|
|
|
2014-03-21 19:18:00 +00:00
|
|
|
var dataSource = config.dataSource;
|
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
if (dataSource) {
|
|
|
|
if (typeof dataSource === 'string') {
|
2014-08-20 23:05:45 +00:00
|
|
|
dataSource = app.dataSources[dataSource];
|
|
|
|
}
|
2013-10-29 21:12:23 +00:00
|
|
|
|
2014-10-30 20:49:47 +00:00
|
|
|
assert(
|
|
|
|
dataSource instanceof DataSource,
|
|
|
|
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
|
|
|
|
config.dataSource + '"'
|
|
|
|
);
|
2014-08-20 23:05:45 +00:00
|
|
|
}
|
2014-05-25 14:27:45 +00:00
|
|
|
|
Add createModelFromConfig and configureModel()
Add new API allowing developers to split the model definition and
configuration into two steps:
1. Build models from JSON config, export them for re-use:
```js
var Customer = loopback.createModelFromConfig({
name: 'Customer',
base: 'User',
properties: {
address: 'string'
}
});
```
2. Attach existing models to a dataSource and a loopback app,
modify certain model aspects like relations:
```js
loopback.configureModel(Customer, {
dataSource: db,
relations: { /* ... */ }
});
```
Rework `app.model` to use `loopback.configureModel` under the hood.
Here is the new usage:
```js
var Customer = require('./models').Customer;
app.model(Customer, {
dataSource: 'db',
relations: { /* ... */ }
});
```
In order to preserve backwards compatibility with loopback 1.x,
`app.model(name, config)` calls both `createModelFromConfig`
and `configureModel`.
2014-06-05 15:41:12 +00:00
|
|
|
config = extend({}, config);
|
|
|
|
config.dataSource = dataSource;
|
2014-05-25 14:27:45 +00:00
|
|
|
|
2015-09-19 02:13:30 +00:00
|
|
|
setSharedMethodSharedProperties(ModelCtor, app, config);
|
|
|
|
|
2015-04-01 21:50:36 +00:00
|
|
|
app.registry.configureModel(ModelCtor, config);
|
2014-05-25 14:27:45 +00:00
|
|
|
}
|
|
|
|
|
2015-09-19 02:13:30 +00:00
|
|
|
function setSharedMethodSharedProperties(model, app, modelConfigs) {
|
|
|
|
var settings = {};
|
|
|
|
|
|
|
|
// apply config.json settings
|
|
|
|
var config = app.get('remoting');
|
|
|
|
var configHasSharedMethodsSettings = config &&
|
|
|
|
config.sharedMethods &&
|
|
|
|
typeof config.sharedMethods === 'object';
|
|
|
|
if (configHasSharedMethodsSettings)
|
|
|
|
util._extend(settings, config.sharedMethods);
|
|
|
|
|
|
|
|
// apply model-config.json settings
|
|
|
|
var modelConfig = modelConfigs.options;
|
|
|
|
var modelConfigHasSharedMethodsSettings = modelConfig &&
|
|
|
|
modelConfig.remoting &&
|
|
|
|
modelConfig.remoting.sharedMethods &&
|
|
|
|
typeof modelConfig.remoting.sharedMethods === 'object';
|
|
|
|
if (modelConfigHasSharedMethodsSettings)
|
|
|
|
util._extend(settings, modelConfig.remoting.sharedMethods);
|
|
|
|
|
|
|
|
// validate setting values
|
|
|
|
Object.keys(settings).forEach(function(setting) {
|
|
|
|
var settingValue = settings[setting];
|
|
|
|
var settingValueType = typeof settingValue;
|
|
|
|
if (settingValueType !== 'boolean')
|
2016-06-07 14:48:28 +00:00
|
|
|
throw new TypeError(g.f('Expected boolean, got %s', settingValueType));
|
2015-09-19 02:13:30 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
// set sharedMethod.shared using the merged settings
|
2016-11-15 21:46:23 +00:00
|
|
|
var sharedMethods = model.sharedClass.methods({includeDisabled: true});
|
2015-09-19 02:13:30 +00:00
|
|
|
sharedMethods.forEach(function(sharedMethod) {
|
|
|
|
// use the specific setting if it exists
|
|
|
|
var hasSpecificSetting = settings.hasOwnProperty(sharedMethod.name);
|
|
|
|
if (hasSpecificSetting) {
|
|
|
|
sharedMethod.shared = settings[sharedMethod.name];
|
|
|
|
} else { // otherwise, use the default setting if it exists
|
|
|
|
var hasDefaultSetting = settings.hasOwnProperty('*');
|
|
|
|
if (hasDefaultSetting)
|
|
|
|
sharedMethod.shared = settings['*'];
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2014-02-18 20:40:35 +00:00
|
|
|
function clearHandlerCache(app) {
|
|
|
|
app._handlers = undefined;
|
|
|
|
}
|
|
|
|
|
2014-01-08 14:20:17 +00:00
|
|
|
/**
|
|
|
|
* Listen for connections and update the configured port.
|
|
|
|
*
|
|
|
|
* When there are no parameters or there is only one callback parameter,
|
|
|
|
* the server will listen on `app.get('host')` and `app.get('port')`.
|
|
|
|
*
|
2014-10-15 07:07:30 +00:00
|
|
|
* For example, to listen on host/port configured in app config:
|
2014-01-08 14:20:17 +00:00
|
|
|
* ```js
|
|
|
|
* app.listen();
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* Otherwise all arguments are forwarded to `http.Server.listen`.
|
|
|
|
*
|
2014-10-15 07:07:30 +00:00
|
|
|
* For example, to listen on the specified port and all hosts, and ignore app config.
|
2014-01-08 14:20:17 +00:00
|
|
|
* ```js
|
|
|
|
* app.listen(80);
|
|
|
|
* ```
|
|
|
|
*
|
|
|
|
* The function also installs a `listening` callback that calls
|
|
|
|
* `app.set('port')` with the value returned by `server.address().port`.
|
|
|
|
* This way the port param contains always the real port number, even when
|
|
|
|
* listen was called with port number 0.
|
|
|
|
*
|
2014-10-15 07:07:30 +00:00
|
|
|
* @param {Function} [cb] If specified, the callback is added as a listener
|
2014-01-08 14:20:17 +00:00
|
|
|
* for the server's "listening" event.
|
|
|
|
* @returns {http.Server} A node `http.Server` with this application configured
|
|
|
|
* as the request handler.
|
|
|
|
*/
|
|
|
|
app.listen = function(cb) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var server = require('http').createServer(this);
|
|
|
|
|
|
|
|
server.on('listening', function() {
|
|
|
|
self.set('port', this.address().port);
|
2014-10-17 07:58:02 +00:00
|
|
|
|
|
|
|
var listeningOnAll = false;
|
|
|
|
var host = self.get('host');
|
|
|
|
if (!host) {
|
|
|
|
listeningOnAll = true;
|
|
|
|
host = this.address().address;
|
|
|
|
self.set('host', host);
|
|
|
|
} else if (host === '0.0.0.0' || host === '::') {
|
|
|
|
listeningOnAll = true;
|
|
|
|
}
|
|
|
|
|
2014-07-01 12:27:02 +00:00
|
|
|
if (!self.get('url')) {
|
2017-06-15 20:29:37 +00:00
|
|
|
if (listeningOnAll) {
|
2014-10-17 07:58:02 +00:00
|
|
|
// We are replacing it with localhost to build a URL
|
|
|
|
// that can be copied and pasted into the browser.
|
|
|
|
host = 'localhost';
|
|
|
|
}
|
2014-07-01 12:27:02 +00:00
|
|
|
var url = 'http://' + host + ':' + self.get('port') + '/';
|
|
|
|
self.set('url', url);
|
|
|
|
}
|
2014-01-08 14:20:17 +00:00
|
|
|
});
|
|
|
|
|
|
|
|
var useAppConfig =
|
2014-10-16 23:40:04 +00:00
|
|
|
arguments.length === 0 ||
|
2014-01-08 14:20:17 +00:00
|
|
|
(arguments.length == 1 && typeof arguments[0] == 'function');
|
|
|
|
|
|
|
|
if (useAppConfig) {
|
2016-05-03 14:03:48 +00:00
|
|
|
var port = this.get('port');
|
|
|
|
// NOTE(bajtos) port:undefined no longer works on node@6,
|
|
|
|
// we must pass port:0 explicitly
|
|
|
|
if (port === undefined) port = 0;
|
|
|
|
server.listen(port, this.get('host'), cb);
|
2014-01-08 14:20:17 +00:00
|
|
|
} else {
|
|
|
|
server.listen.apply(server, arguments);
|
|
|
|
}
|
|
|
|
|
|
|
|
return server;
|
2014-10-16 22:54:40 +00:00
|
|
|
};
|