Merge branch 'master' into feature/remoting-connector
This commit is contained in:
commit
5298834c68
|
@ -1,5 +1,7 @@
|
||||||
# LoopBack
|
# LoopBack
|
||||||
|
|
||||||
|
For a quick introduction and overview, see http://loopback.io/.
|
||||||
|
|
||||||
## Documentation
|
## Documentation
|
||||||
|
|
||||||
[See the full documentation](http://docs.strongloop.com/display/DOC/LoopBack).
|
[See the full documentation](http://docs.strongloop.com/display/DOC/LoopBack).
|
||||||
|
|
|
@ -49,7 +49,7 @@ var oracle = loopback.createDataSource({
|
||||||
User.attachTo(oracle);
|
User.attachTo(oracle);
|
||||||
```
|
```
|
||||||
|
|
||||||
**Note:** until a model is attached to a data source it will **not** have any **attached methods**.
|
NOTE: until a model is attached to a data source it will not have any attached methods.
|
||||||
|
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
|
|
|
@ -48,8 +48,7 @@ var app = exports = module.exports = {};
|
||||||
/**
|
/**
|
||||||
* Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).
|
* Lazily load a set of [remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).
|
||||||
*
|
*
|
||||||
* **NOTE:** Calling `app.remotes()` multiple times will only ever return a
|
* **NOTE:** Calling `app.remotes()` more than once returns only a single set of remote objects.
|
||||||
* single set of remote objects.
|
|
||||||
* @returns {RemoteObjects}
|
* @returns {RemoteObjects}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@ -88,11 +87,13 @@ app.disuse = function (route) {
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {String} modelName The name of the model to define
|
* @param {String} modelName The name of the model to define.
|
||||||
* @options {Object} config The model's configuration
|
* @options {Object} config The model's configuration.
|
||||||
* @property {String} dataSource The `DataSource` to attach the model to
|
* @property {String|DataSource} dataSource The `DataSource` to which to attach the model.
|
||||||
* @property {Object} [options] an object containing `Model` options
|
* @property {Object} [options] an object containing `Model` options.
|
||||||
* @property {Object} [properties] object defining the `Model` properties in [LoopBack Definition Language](http://docs.strongloop.com/loopback-datasource-juggler/#loopback-definition-language)
|
* @property {ACL[]} [options.acls] an array of `ACL` definitions.
|
||||||
|
* @property {String[]} [options.hidden] **experimental** an array of properties to hide when accessed remotely.
|
||||||
|
* @property {Object} [properties] object defining the `Model` properties in [LoopBack Definition Language](http://docs.strongloop.com/loopback-datasource-juggler/#loopback-definition-language).
|
||||||
* @end
|
* @end
|
||||||
* @returns {ModelConstructor} the model class
|
* @returns {ModelConstructor} the model class
|
||||||
*/
|
*/
|
||||||
|
@ -127,14 +128,11 @@ app.model = function (Model, config) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the models exported by the app. Only models defined using `app.model()`
|
* Get the models exported by the app. Returns only models defined using `app.model()`
|
||||||
* will show up in this list.
|
|
||||||
*
|
*
|
||||||
* There are two ways how to access models.
|
* There are two ways to access models:
|
||||||
*
|
*
|
||||||
* **1. A list of all models**
|
* 1. Call `app.models()` to get a list of all models.
|
||||||
*
|
|
||||||
* Call `app.models()` to get a list of all models.
|
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* var models = app.models();
|
* var models = app.models();
|
||||||
|
@ -144,12 +142,11 @@ app.model = function (Model, config) {
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* **2. By model name**
|
* **2. Use `app.model` to access a model by name.
|
||||||
*
|
|
||||||
* `app.model` has properties for all defined models.
|
* `app.model` has properties for all defined models.
|
||||||
*
|
*
|
||||||
* In the following example the `Product` and `CustomerReceipt` models are
|
* The following example illustrates accessing the `Product` and `CustomerReceipt` models
|
||||||
* accessed using the `models` object.
|
* using the `models` object.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* var loopback = require('loopback');
|
* var loopback = require('loopback');
|
||||||
|
@ -176,7 +173,7 @@ app.model = function (Model, config) {
|
||||||
* var customerReceipt = app.models.customerReceipt;
|
* var customerReceipt = app.models.customerReceipt;
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @returns {Array} a list of model classes
|
* @returns {Array} Array of model classes.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.models = function () {
|
app.models = function () {
|
||||||
|
@ -198,6 +195,7 @@ app.dataSource = function (name, config) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get all remote objects.
|
* Get all remote objects.
|
||||||
|
* @returns {Object} [Remote objects](http://apidocs.strongloop.com/strong-remoting/#remoteobjectsoptions).
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.remoteObjects = function () {
|
app.remoteObjects = function () {
|
||||||
|
@ -218,8 +216,7 @@ app.remoteObjects = function () {
|
||||||
/**
|
/**
|
||||||
* Enable swagger REST API documentation.
|
* Enable swagger REST API documentation.
|
||||||
*
|
*
|
||||||
* > Note: This method is deprecated, use the extension
|
* **Note**: This method is deprecated. Use [loopback-explorer](http://npmjs.org/package/loopback-explorer) instead.
|
||||||
* [loopback-explorer](http://npmjs.org/package/loopback-explorer) instead.
|
|
||||||
*
|
*
|
||||||
* **Options**
|
* **Options**
|
||||||
*
|
*
|
||||||
|
@ -312,29 +309,30 @@ app.enableAuth = function() {
|
||||||
/**
|
/**
|
||||||
* Initialize an application from an options object or a set of JSON and JavaScript files.
|
* Initialize an application from an options object or a set of JSON and JavaScript files.
|
||||||
*
|
*
|
||||||
* **What happens during an app _boot_?**
|
* This function takes an optional argument that is either a string or an object.
|
||||||
*
|
*
|
||||||
* 1. **DataSources** are created from an `options.dataSources` object or `datasources.json` in the current directory
|
* If the argument is a string, then it sets the application root directory based on the string value. Then it:
|
||||||
* 2. **Models** are created from an `options.models` object or `models.json` in the current directory
|
* 1. Creates DataSources from the `datasources.json` file in the application root directory.
|
||||||
* 3. Any JavaScript files in the `./models` directory are loaded with `require()`.
|
* 2. Creates Models from the `models.json` file in the application root directory.
|
||||||
* 4. Any JavaScript files in the `./boot` directory are loaded with `require()`.
|
|
||||||
*
|
*
|
||||||
* **Options**
|
* If the argument is an object, then it looks for `model`, `dataSources`, and `appRootDir` properties of the object.
|
||||||
*
|
* If the object has no `appRootDir` property then it sets the current working directory as the application root directory.
|
||||||
* - `appRootDir` - _optional_ - the directory to use when loading JSON and JavaScript files
|
* Then it:
|
||||||
* - `models` - _optional_ - an object containing `Model` definitions
|
* 1. Creates DataSources from the `options.dataSources` object.
|
||||||
* - `dataSources` - _optional_ - an object containing `DataSource` definitions
|
* 2. Creates Models from the `options.models` object.
|
||||||
*
|
*
|
||||||
* > **NOTE:** mixing `app.boot()` and `app.model(name, config)` in multiple
|
* In both cases, the function loads JavaScript files in the `/models` and `/boot` subdirectories of the application root directory with `require()`.
|
||||||
* > files may result
|
*
|
||||||
* > in models being **undefined** due to race conditions. To avoid this when
|
* **NOTE:** mixing `app.boot()` and `app.model(name, config)` in multiple
|
||||||
* > using `app.boot()`
|
* files may result in models being **undefined** due to race conditions.
|
||||||
* > make sure all models are passed as part of the `models` definition.
|
* To avoid this when using `app.boot()` make sure all models are passed as part of the `models` definition.
|
||||||
|
*
|
||||||
|
* Throws an error if the config object is not valid or if boot fails.
|
||||||
*
|
*
|
||||||
* <a name="model-definition"></a>
|
* <a name="model-definition"></a>
|
||||||
* **Model Definitions**
|
* **Model Definitions**
|
||||||
*
|
*
|
||||||
* The following is an example of an object containing two `Model` definitions: "location" and "inventory".
|
* The following is example JSON for two `Model` definitions: "dealership" and "location".
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* {
|
* {
|
||||||
|
@ -379,20 +377,13 @@ app.enableAuth = function() {
|
||||||
* }
|
* }
|
||||||
* }
|
* }
|
||||||
* ```
|
* ```
|
||||||
*
|
* @options {String|Object} options Boot options; If String, this is the application root directory; if object, has below properties.
|
||||||
* **Model definition properties**
|
* @property {String} appRootDir Directory to use when loading JSON and JavaScript files (optional). Defaults to the current directory (`process.cwd()`).
|
||||||
*
|
* @property {Object} models Object containing `Model` definitions (optional).
|
||||||
* - `dataSource` - **required** - a string containing the name of the data source definition to attach the `Model` to
|
* @property {Object} dataSources Object containing `DataSource` definitions (optional).
|
||||||
* - `options` - _optional_ - an object containing `Model` options
|
* @end
|
||||||
* - `properties` _optional_ - an object defining the `Model` properties in [LoopBack Definition Language](http://docs.strongloop.com/loopback-datasource-juggler/#loopback-definition-language)
|
|
||||||
*
|
|
||||||
* **DataSource definition properties**
|
|
||||||
*
|
|
||||||
* - `connector` - **required** - the name of the [connector](#working-with-data-sources-and-connectors)
|
|
||||||
*
|
*
|
||||||
* @header app.boot([options])
|
* @header app.boot([options])
|
||||||
* @throws {Error} If config is not valid
|
|
||||||
* @throws {Error} If boot fails
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.boot = function(options) {
|
app.boot = function(options) {
|
||||||
|
@ -545,7 +536,11 @@ function dataSourcesFromConfig(config) {
|
||||||
|
|
||||||
function modelFromConfig(name, config, app) {
|
function modelFromConfig(name, config, app) {
|
||||||
var ModelCtor = require('./loopback').createModel(name, config.properties, config.options);
|
var ModelCtor = require('./loopback').createModel(name, config.properties, config.options);
|
||||||
var dataSource = app.dataSources[config.dataSource];
|
var dataSource = config.dataSource;
|
||||||
|
|
||||||
|
if(typeof dataSource === 'string') {
|
||||||
|
dataSource = app.dataSources[dataSource];
|
||||||
|
}
|
||||||
|
|
||||||
assert(isDataSource(dataSource), name + ' is referencing a dataSource that does not exist: "'+ config.dataSource +'"');
|
assert(isDataSource(dataSource), name + ' is referencing a dataSource that does not exist: "'+ config.dataSource +'"');
|
||||||
|
|
||||||
|
@ -644,7 +639,8 @@ function clearHandlerCache(app) {
|
||||||
app._handlers = undefined;
|
app._handlers = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/*!
|
||||||
|
* This function is now deprecated.
|
||||||
* Install all express middleware required by LoopBack.
|
* Install all express middleware required by LoopBack.
|
||||||
*
|
*
|
||||||
* It is possible to inject your own middleware by listening on one of the
|
* It is possible to inject your own middleware by listening on one of the
|
||||||
|
@ -793,7 +789,7 @@ app.installMiddleware = function() {
|
||||||
* This way the port param contains always the real port number, even when
|
* This way the port param contains always the real port number, even when
|
||||||
* listen was called with port number 0.
|
* listen was called with port number 0.
|
||||||
*
|
*
|
||||||
* @param {Function=} cb If specified, the callback will be added as a listener
|
* @param {Function} cb If specified, the callback is added as a listener
|
||||||
* for the server's "listening" event.
|
* for the server's "listening" event.
|
||||||
* @returns {http.Server} A node `http.Server` with this application configured
|
* @returns {http.Server} A node `http.Server` with this application configured
|
||||||
* as the request handler.
|
* as the request handler.
|
||||||
|
|
|
@ -15,27 +15,29 @@ var express = require('express')
|
||||||
, assert = require('assert');
|
, assert = require('assert');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* `loopback` is the main entry for LoopBack core module. It provides static
|
* Main entry for LoopBack core module. It provides static properties and
|
||||||
* methods to create models and data sources. The module itself is a function
|
* methods to create models and data sources. The module itself is a function
|
||||||
* that creates loopback `app`. For example,
|
* that creates loopback `app`. For example,
|
||||||
*
|
*
|
||||||
*
|
|
||||||
* ```js
|
* ```js
|
||||||
* var loopback = require('loopback');
|
* var loopback = require('loopback');
|
||||||
* var app = loopback();
|
* var app = loopback();
|
||||||
* ```
|
* ```
|
||||||
|
*
|
||||||
|
* @class loopback
|
||||||
|
* @header loopback
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var loopback = exports = module.exports = createApplication;
|
var loopback = exports = module.exports = createApplication;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this a browser environment?
|
* True if running in a browser environment; false otherwise.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
loopback.isBrowser = typeof window !== 'undefined';
|
loopback.isBrowser = typeof window !== 'undefined';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Is this a server environment?
|
* True if running in a server environment; false otherwise.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
loopback.isServer = !loopback.isBrowser;
|
loopback.isServer = !loopback.isBrowser;
|
||||||
|
@ -116,11 +118,10 @@ loopback.errorHandler.title = 'Loopback';
|
||||||
/**
|
/**
|
||||||
* Create a data source with passing the provided options to the connector.
|
* Create a data source with passing the provided options to the connector.
|
||||||
*
|
*
|
||||||
* @param {String} name (optional)
|
* @param {String} name Optional name.
|
||||||
* @param {Object} options
|
* @options {Object} Data Source options
|
||||||
*
|
* @property {Object} connector LoopBack connector.
|
||||||
* - connector - an loopback connector
|
* @property {*} Other properties See the relevant connector documentation.
|
||||||
* - other values - see the specified `connector` docs
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
loopback.createDataSource = function (name, options) {
|
loopback.createDataSource = function (name, options) {
|
||||||
|
@ -141,7 +142,7 @@ loopback.createDataSource = function (name, options) {
|
||||||
/**
|
/**
|
||||||
* Create a named vanilla JavaScript class constructor with an attached set of properties and options.
|
* Create a named vanilla JavaScript class constructor with an attached set of properties and options.
|
||||||
*
|
*
|
||||||
* @param {String} name - must be unique
|
* @param {String} name Unique name.
|
||||||
* @param {Object} properties
|
* @param {Object} properties
|
||||||
* @param {Object} options (optional)
|
* @param {Object} options (optional)
|
||||||
*/
|
*/
|
||||||
|
@ -223,7 +224,7 @@ loopback.memory = function (name) {
|
||||||
/**
|
/**
|
||||||
* Look up a model class by name from all models created by loopback.createModel()
|
* Look up a model class by name from all models created by loopback.createModel()
|
||||||
* @param {String} modelName The model name
|
* @param {String} modelName The model name
|
||||||
* @return {Model} The model class
|
* @returns {Model} The model class
|
||||||
*/
|
*/
|
||||||
loopback.getModel = function(modelName) {
|
loopback.getModel = function(modelName) {
|
||||||
return loopback.Model.modelBuilder.models[modelName];
|
return loopback.Model.modelBuilder.models[modelName];
|
||||||
|
@ -233,7 +234,7 @@ loopback.getModel = function(modelName) {
|
||||||
* Look up a model class by the base model class. The method can be used by LoopBack
|
* Look up a model class by the base model class. The method can be used by LoopBack
|
||||||
* to find configured models in models.json over the base model.
|
* to find configured models in models.json over the base model.
|
||||||
* @param {Model} The base model class
|
* @param {Model} The base model class
|
||||||
* @return {Model} The subclass if found or the base class
|
* @returns {Model} The subclass if found or the base class
|
||||||
*/
|
*/
|
||||||
loopback.getModelByType = function(modelType) {
|
loopback.getModelByType = function(modelType) {
|
||||||
assert(typeof modelType === 'function', 'The model type must be a constructor');
|
assert(typeof modelType === 'function', 'The model type must be a constructor');
|
||||||
|
@ -250,7 +251,7 @@ loopback.getModelByType = function(modelType) {
|
||||||
* Set the default `dataSource` for a given `type`.
|
* Set the default `dataSource` for a given `type`.
|
||||||
* @param {String} type The datasource type
|
* @param {String} type The datasource type
|
||||||
* @param {Object|DataSource} dataSource The data source settings or instance
|
* @param {Object|DataSource} dataSource The data source settings or instance
|
||||||
* @return {DataSource} The data source instance
|
* @returns {DataSource} The data source instance
|
||||||
*/
|
*/
|
||||||
|
|
||||||
loopback.setDefaultDataSourceForType = function(type, dataSource) {
|
loopback.setDefaultDataSourceForType = function(type, dataSource) {
|
||||||
|
@ -267,7 +268,7 @@ loopback.setDefaultDataSourceForType = function(type, dataSource) {
|
||||||
/**
|
/**
|
||||||
* Get the default `dataSource` for a given `type`.
|
* Get the default `dataSource` for a given `type`.
|
||||||
* @param {String} type The datasource type
|
* @param {String} type The datasource type
|
||||||
* @return {DataSource} The data source instance
|
* @returns {DataSource} The data source instance
|
||||||
*/
|
*/
|
||||||
|
|
||||||
loopback.getDefaultDataSourceForType = function(type) {
|
loopback.getDefaultDataSourceForType = function(type) {
|
||||||
|
|
|
@ -100,8 +100,9 @@ Model.setup = function () {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Map the prototype method to /:id with data in the body
|
// Map the prototype method to /:id with data in the body
|
||||||
|
var idDesc = ModelCtor.modelName + ' id';
|
||||||
ModelCtor.sharedCtor.accepts = [
|
ModelCtor.sharedCtor.accepts = [
|
||||||
{arg: 'id', type: 'any', http: {source: 'path'}}
|
{arg: 'id', type: 'any', http: {source: 'path'}, description: idDesc}
|
||||||
// {arg: 'instance', type: 'object', http: {source: 'body'}}
|
// {arg: 'instance', type: 'object', http: {source: 'body'}}
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -49,6 +49,7 @@ var properties = {
|
||||||
};
|
};
|
||||||
|
|
||||||
var options = {
|
var options = {
|
||||||
|
hidden: ['password'],
|
||||||
acls: [
|
acls: [
|
||||||
{
|
{
|
||||||
principalType: ACL.ROLE,
|
principalType: ACL.ROLE,
|
||||||
|
@ -90,6 +91,12 @@ var options = {
|
||||||
principalId: Role.OWNER,
|
principalId: Role.OWNER,
|
||||||
permission: ACL.ALLOW,
|
permission: ACL.ALLOW,
|
||||||
property: "updateAttributes"
|
property: "updateAttributes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principalType: ACL.ROLE,
|
||||||
|
principalId: Role.EVERYONE,
|
||||||
|
permission: ACL.ALLOW,
|
||||||
|
property: "confirm"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
relations: {
|
relations: {
|
||||||
|
@ -105,7 +112,7 @@ var options = {
|
||||||
* Extends from the built in `loopback.Model` type.
|
* Extends from the built in `loopback.Model` type.
|
||||||
*
|
*
|
||||||
* Default `User` ACLs.
|
* Default `User` ACLs.
|
||||||
*
|
*
|
||||||
* - DENY EVERYONE `*`
|
* - DENY EVERYONE `*`
|
||||||
* - ALLOW EVERYONE `create`
|
* - ALLOW EVERYONE `create`
|
||||||
* - ALLOW OWNER `removeById`
|
* - ALLOW OWNER `removeById`
|
||||||
|
@ -153,11 +160,11 @@ User.login = function (credentials, include, fn) {
|
||||||
err.statusCode = 400;
|
err.statusCode = 400;
|
||||||
return fn(err);
|
return fn(err);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.findOne({where: query}, function(err, user) {
|
this.findOne({where: query}, function(err, user) {
|
||||||
var defaultError = new Error('login failed');
|
var defaultError = new Error('login failed');
|
||||||
defaultError.statusCode = 401;
|
defaultError.statusCode = 401;
|
||||||
|
|
||||||
if(err) {
|
if(err) {
|
||||||
debug('An error is reported from User.findOne: %j', err);
|
debug('An error is reported from User.findOne: %j', err);
|
||||||
fn(defaultError);
|
fn(defaultError);
|
||||||
|
@ -262,7 +269,7 @@ User.prototype.verify = function (options, fn) {
|
||||||
assert(options.type === 'email', 'Unsupported verification type');
|
assert(options.type === 'email', 'Unsupported verification type');
|
||||||
assert(options.to || this.email, 'Must include options.to when calling user.verify() or the user must have an email property');
|
assert(options.to || this.email, 'Must include options.to when calling user.verify() or the user must have an email property');
|
||||||
assert(options.from, 'Must include options.from when calling user.verify() or the user must have an email property');
|
assert(options.from, 'Must include options.from when calling user.verify() or the user must have an email property');
|
||||||
|
|
||||||
options.redirect = options.redirect || '/';
|
options.redirect = options.redirect || '/';
|
||||||
options.template = path.resolve(options.template || path.join(__dirname, '..', '..', 'templates', 'verify.ejs'));
|
options.template = path.resolve(options.template || path.join(__dirname, '..', '..', 'templates', 'verify.ejs'));
|
||||||
options.user = this;
|
options.user = this;
|
||||||
|
@ -273,13 +280,16 @@ User.prototype.verify = function (options, fn) {
|
||||||
+ '://'
|
+ '://'
|
||||||
+ options.host
|
+ options.host
|
||||||
+ User.http.path
|
+ User.http.path
|
||||||
+ User.confirm.http.path;
|
+ User.confirm.http.path
|
||||||
|
+ '?uid='
|
||||||
|
+ options.user.id
|
||||||
|
+ '&redirect='
|
||||||
|
+ options.redirect;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Email model
|
// Email model
|
||||||
var Email = options.mailer || this.constructor.email || loopback.getModelByType(loopback.Email);
|
var Email = options.mailer || this.constructor.email || loopback.getModelByType(loopback.Email);
|
||||||
|
|
||||||
crypto.randomBytes(64, function(err, buf) {
|
crypto.randomBytes(64, function(err, buf) {
|
||||||
if(err) {
|
if(err) {
|
||||||
fn(err);
|
fn(err);
|
||||||
|
@ -289,23 +299,24 @@ User.prototype.verify = function (options, fn) {
|
||||||
if(err) {
|
if(err) {
|
||||||
fn(err);
|
fn(err);
|
||||||
} else {
|
} else {
|
||||||
sendEmail(user);
|
sendEmail(user);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO - support more verification types
|
// TODO - support more verification types
|
||||||
function sendEmail(user) {
|
function sendEmail(user) {
|
||||||
options.verifyHref += '?token=' + user.verificationToken;
|
options.verifyHref += '&token=' + user.verificationToken;
|
||||||
|
|
||||||
options.text = options.text || 'Please verify your email by opening this link in a web browser:\n\t{href}';
|
options.text = options.text || 'Please verify your email by opening this link in a web browser:\n\t{href}';
|
||||||
|
|
||||||
options.text = options.text.replace('{href}', options.verifyHref);
|
options.text = options.text.replace('{href}', options.verifyHref);
|
||||||
|
|
||||||
var template = loopback.template(options.template);
|
var template = loopback.template(options.template);
|
||||||
Email.send({
|
Email.send({
|
||||||
to: options.to || user.email,
|
to: options.to || user.email,
|
||||||
|
from: options.from,
|
||||||
subject: options.subject || 'Thanks for Registering',
|
subject: options.subject || 'Thanks for Registering',
|
||||||
text: options.text,
|
text: options.text,
|
||||||
html: template(options)
|
html: template(options)
|
||||||
|
@ -404,7 +415,7 @@ User.setup = function () {
|
||||||
// We need to call the base class's setup method
|
// We need to call the base class's setup method
|
||||||
Model.setup.call(this);
|
Model.setup.call(this);
|
||||||
var UserModel = this;
|
var UserModel = this;
|
||||||
|
|
||||||
// max ttl
|
// max ttl
|
||||||
this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL;
|
this.settings.maxTTL = this.settings.maxTTL || DEFAULT_MAX_TTL;
|
||||||
this.settings.ttl = DEFAULT_TTL;
|
this.settings.ttl = DEFAULT_TTL;
|
||||||
|
@ -413,7 +424,7 @@ User.setup = function () {
|
||||||
var salt = bcrypt.genSaltSync(this.constructor.settings.saltWorkFactor || SALT_WORK_FACTOR);
|
var salt = bcrypt.genSaltSync(this.constructor.settings.saltWorkFactor || SALT_WORK_FACTOR);
|
||||||
this.$password = bcrypt.hashSync(plain, salt);
|
this.$password = bcrypt.hashSync(plain, salt);
|
||||||
}
|
}
|
||||||
|
|
||||||
loopback.remoteMethod(
|
loopback.remoteMethod(
|
||||||
UserModel.login,
|
UserModel.login,
|
||||||
{
|
{
|
||||||
|
@ -433,7 +444,7 @@ User.setup = function () {
|
||||||
http: {verb: 'post'}
|
http: {verb: 'post'}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
loopback.remoteMethod(
|
loopback.remoteMethod(
|
||||||
UserModel.logout,
|
UserModel.logout,
|
||||||
{
|
{
|
||||||
|
@ -452,7 +463,7 @@ User.setup = function () {
|
||||||
http: {verb: 'all'}
|
http: {verb: 'all'}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
loopback.remoteMethod(
|
loopback.remoteMethod(
|
||||||
UserModel.confirm,
|
UserModel.confirm,
|
||||||
{
|
{
|
||||||
|
@ -464,7 +475,7 @@ User.setup = function () {
|
||||||
http: {verb: 'get', path: '/confirm'}
|
http: {verb: 'get', path: '/confirm'}
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
loopback.remoteMethod(
|
loopback.remoteMethod(
|
||||||
UserModel.resetPassword,
|
UserModel.resetPassword,
|
||||||
{
|
{
|
||||||
|
@ -484,16 +495,16 @@ User.setup = function () {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// default models
|
// default models
|
||||||
UserModel.email = require('./email');
|
UserModel.email = require('./email');
|
||||||
UserModel.accessToken = require('./access-token');
|
UserModel.accessToken = require('./access-token');
|
||||||
|
|
||||||
UserModel.validatesUniquenessOf('email', {message: 'Email already exists'});
|
UserModel.validatesUniquenessOf('email', {message: 'Email already exists'});
|
||||||
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
var re = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
|
||||||
|
|
||||||
UserModel.validatesFormatOf('email', {with: re, message: 'Must provide a valid email'});
|
UserModel.validatesFormatOf('email', {with: re, message: 'Must provide a valid email'});
|
||||||
|
|
||||||
return UserModel;
|
return UserModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,7 @@
|
||||||
"Platform",
|
"Platform",
|
||||||
"mBaaS"
|
"mBaaS"
|
||||||
],
|
],
|
||||||
"version": "1.7.2",
|
"version": "1.7.6",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha -R spec"
|
"test": "mocha -R spec"
|
||||||
},
|
},
|
||||||
|
@ -29,10 +29,10 @@
|
||||||
"async": "~0.2.10"
|
"async": "~0.2.10"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"loopback-datasource-juggler": "~1.3.3"
|
"loopback-datasource-juggler": "~1.3.11"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"loopback-datasource-juggler": "~1.3.3",
|
"loopback-datasource-juggler": "~1.3.11",
|
||||||
"mocha": "~1.17.1",
|
"mocha": "~1.17.1",
|
||||||
"strong-task-emitter": "0.0.x",
|
"strong-task-emitter": "0.0.x",
|
||||||
"supertest": "~0.9.0",
|
"supertest": "~0.9.0",
|
||||||
|
|
|
@ -55,6 +55,7 @@ describe('access control - integration', function () {
|
||||||
return '/api/accessTokens/' + this.randomToken.id;
|
return '/api/accessTokens/' + this.randomToken.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
*/
|
||||||
|
|
||||||
describe('/users', function () {
|
describe('/users', function () {
|
||||||
|
|
||||||
|
@ -94,6 +95,10 @@ describe('access control - integration', function () {
|
||||||
});
|
});
|
||||||
lt.describe.whenCalledRemotely('GET', '/api/users/:id', function() {
|
lt.describe.whenCalledRemotely('GET', '/api/users/:id', function() {
|
||||||
lt.it.shouldBeAllowed();
|
lt.it.shouldBeAllowed();
|
||||||
|
it('should not include a password', function() {
|
||||||
|
var user = this.res.body;
|
||||||
|
assert.equal(user.password, undefined);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {
|
lt.describe.whenCalledRemotely('PUT', '/api/users/:id', function() {
|
||||||
lt.it.shouldBeAllowed();
|
lt.it.shouldBeAllowed();
|
||||||
|
@ -136,7 +141,6 @@ describe('access control - integration', function () {
|
||||||
return '/api/banks/' + this.bank.id;
|
return '/api/banks/' + this.bank.id;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
*/
|
|
||||||
|
|
||||||
describe('/accounts', function () {
|
describe('/accounts', function () {
|
||||||
lt.beforeEach.givenModel('account');
|
lt.beforeEach.givenModel('account');
|
||||||
|
|
|
@ -30,7 +30,15 @@
|
||||||
"widget": {
|
"widget": {
|
||||||
"properties": {},
|
"properties": {},
|
||||||
"public": true,
|
"public": true,
|
||||||
"dataSource": "db"
|
"dataSource": "db",
|
||||||
|
"options": {
|
||||||
|
"relations": {
|
||||||
|
"store": {
|
||||||
|
"model": "store",
|
||||||
|
"type": "belongsTo"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"store": {
|
"store": {
|
||||||
"properties": {},
|
"properties": {},
|
||||||
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
var loopback = require('../');
|
||||||
|
|
||||||
|
describe('hidden properties', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
var app = this.app = loopback();
|
||||||
|
var Product = this.Product = app.model('product', {
|
||||||
|
options: {hidden: ['secret']},
|
||||||
|
dataSource: loopback.memory()
|
||||||
|
});
|
||||||
|
var Category = this.Category = this.app.model('category', {
|
||||||
|
dataSource: loopback.memory()
|
||||||
|
});
|
||||||
|
Category.hasMany(Product);
|
||||||
|
app.use(loopback.rest());
|
||||||
|
Category.create({
|
||||||
|
name: 'my category'
|
||||||
|
}, function(err, category) {
|
||||||
|
category.products.create({
|
||||||
|
name: 'pencil',
|
||||||
|
secret: 'a secret'
|
||||||
|
}, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function(done) {
|
||||||
|
var Product = this.Product;
|
||||||
|
this.Category.destroyAll(function() {
|
||||||
|
Product.destroyAll(done);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should hide a property remotely', function (done) {
|
||||||
|
request(this.app)
|
||||||
|
.get('/products')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res){
|
||||||
|
if(err) return done(err);
|
||||||
|
var product = res.body[0];
|
||||||
|
assert.equal(product.secret, undefined);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should hide a property of nested models', function (done) {
|
||||||
|
var app = this.app;
|
||||||
|
request(app)
|
||||||
|
.get('/categories?filter[include]=products')
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.expect(200)
|
||||||
|
.end(function(err, res){
|
||||||
|
if(err) return done(err);
|
||||||
|
var category = res.body[0];
|
||||||
|
var product = category.products[0];
|
||||||
|
assert.equal(product.secret, undefined);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
|
@ -624,81 +624,4 @@ describe('Model', function() {
|
||||||
assert.equal(model, acl);
|
assert.equal(model, acl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// describe('Model.hasAndBelongsToMany()', function() {
|
|
||||||
// it("TODO: implement / document", function(done) {
|
|
||||||
// /* example -
|
|
||||||
//
|
|
||||||
// */
|
|
||||||
// done(new Error('test not implemented'));
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// describe('Model.remoteMethods()', function() {
|
|
||||||
// it("Return a list of enabled remote methods", function() {
|
|
||||||
// app.model(User);
|
|
||||||
// User.remoteMethods(); // ['save', ...]
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// describe('Model.availableMethods()', function() {
|
|
||||||
// it("Returns the currently available api of a model as well as descriptions of any modified behavior or methods from attached data sources", function(done) {
|
|
||||||
// /* example -
|
|
||||||
// User.attachTo(oracle);
|
|
||||||
// console.log(User.availableMethods());
|
|
||||||
//
|
|
||||||
// {
|
|
||||||
// 'User.all': {
|
|
||||||
// accepts: [{arg: 'filter', type: 'object', description: '...'}],
|
|
||||||
// returns: [{arg: 'users', type: ['User']}]
|
|
||||||
// },
|
|
||||||
// 'User.find': {
|
|
||||||
// accepts: [{arg: 'id', type: 'any'}],
|
|
||||||
// returns: [{arg: 'items', type: 'User'}]
|
|
||||||
// },
|
|
||||||
// ...
|
|
||||||
// }
|
|
||||||
// var oracle = loopback.createDataSource({
|
|
||||||
// connector: 'oracle',
|
|
||||||
// host: '111.22.333.44',
|
|
||||||
// database: 'MYDB',
|
|
||||||
// username: 'username',
|
|
||||||
// password: 'password'
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// */
|
|
||||||
// done(new Error('test not implemented'));
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// describe('Model.before(name, fn)', function(){
|
|
||||||
// it('Run a function before a method is called', function() {
|
|
||||||
// // User.before('save', function(user, next) {
|
|
||||||
// // console.log('about to save', user);
|
|
||||||
// //
|
|
||||||
// // next();
|
|
||||||
// // });
|
|
||||||
// //
|
|
||||||
// // User.before('delete', function(user, next) {
|
|
||||||
// // // prevent all delete calls
|
|
||||||
// // next(new Error('deleting is disabled'));
|
|
||||||
// // });
|
|
||||||
// // User.beforeRemote('save', function(ctx, user, next) {
|
|
||||||
// // if(ctx.user.id === user.id) {
|
|
||||||
// // next();
|
|
||||||
// // } else {
|
|
||||||
// // next(new Error('must be logged in to update'))
|
|
||||||
// // }
|
|
||||||
// // });
|
|
||||||
//
|
|
||||||
// throw new Error('not implemented');
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
//
|
|
||||||
// describe('Model.after(name, fn)', function(){
|
|
||||||
// it('Run a function after a method is called', function() {
|
|
||||||
//
|
|
||||||
// throw new Error('not implemented');
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,6 +4,7 @@ var path = require('path');
|
||||||
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-integration-app');
|
var SIMPLE_APP = path.join(__dirname, 'fixtures', 'simple-integration-app');
|
||||||
var app = require(path.join(SIMPLE_APP, 'app.js'));
|
var app = require(path.join(SIMPLE_APP, 'app.js'));
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
var expect = require('chai').expect;
|
||||||
|
|
||||||
describe('relations - integration', function () {
|
describe('relations - integration', function () {
|
||||||
|
|
||||||
|
@ -95,4 +96,132 @@ describe('relations - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('/widgets/:id/store', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
var self = this;
|
||||||
|
this.store.widgets.create({
|
||||||
|
name: this.widgetName
|
||||||
|
}, function(err, widget) {
|
||||||
|
self.widget = widget;
|
||||||
|
self.url = '/api/widgets/' + self.widget.id + '/store';
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/api/widgets/:id/store', function () {
|
||||||
|
it('should succeed with statusCode 200', function () {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
assert.equal(this.res.body.id, this.store.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('hasAndBelongsToMany', function() {
|
||||||
|
beforeEach(function defineProductAndCategoryModels() {
|
||||||
|
var product = app.model(
|
||||||
|
'product',
|
||||||
|
{ properties: { id: 'string', name: 'string' }, dataSource: 'db' }
|
||||||
|
|
||||||
|
);
|
||||||
|
var category = app.model(
|
||||||
|
'category',
|
||||||
|
{ properties: { id: 'string', name: 'string' }, dataSource: 'db' }
|
||||||
|
);
|
||||||
|
product.hasAndBelongsToMany(category);
|
||||||
|
category.hasAndBelongsToMany(product);
|
||||||
|
});
|
||||||
|
|
||||||
|
lt.beforeEach.givenModel('category');
|
||||||
|
|
||||||
|
beforeEach(function createProductsInCategory(done) {
|
||||||
|
var test = this;
|
||||||
|
this.category.products.create({
|
||||||
|
name: 'a-product'
|
||||||
|
}, function(err, product) {
|
||||||
|
if (err) return done(err);
|
||||||
|
test.product = product;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
beforeEach(function createAnotherCategoryAndProduct(done) {
|
||||||
|
app.models.category.create({ name: 'another-category' },
|
||||||
|
function(err, cat) {
|
||||||
|
if (err) return done(err);
|
||||||
|
cat.products.create({ name: 'another-product' }, done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(function(done) {
|
||||||
|
this.app.models.product.destroyAll(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('allows to find related objects via where filter', function(done) {
|
||||||
|
//TODO https://github.com/strongloop/loopback-datasource-juggler/issues/94
|
||||||
|
var expectedProduct = this.product;
|
||||||
|
// Note: the URL format is not final
|
||||||
|
this.get('/api/products?filter[where][categoryId]=' + this.category.id)
|
||||||
|
.expect(200, function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res.body).to.eql([
|
||||||
|
{
|
||||||
|
id: expectedProduct.id,
|
||||||
|
name: expectedProduct.name
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows to find related object via URL scope', function(done) {
|
||||||
|
var expectedProduct = this.product;
|
||||||
|
this.get('/api/categories/' + this.category.id + '/products')
|
||||||
|
.expect(200, function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
expect(res.body).to.eql([
|
||||||
|
{
|
||||||
|
id: expectedProduct.id,
|
||||||
|
name: expectedProduct.name
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes requested related models in `find`', function(done) {
|
||||||
|
var expectedProduct = this.product;
|
||||||
|
var url = '/api/categories/findOne?filter[where][id]=' +
|
||||||
|
this.category.id + '&filter[include]=products';
|
||||||
|
|
||||||
|
this.get(url)
|
||||||
|
.expect(200, function(err, res) {
|
||||||
|
expect(res.body).to.have.property('products');
|
||||||
|
expect(res.body.products).to.eql([
|
||||||
|
{
|
||||||
|
id: expectedProduct.id,
|
||||||
|
name: expectedProduct.name
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it.skip('includes requested related models in `findById`', function(done) {
|
||||||
|
//TODO https://github.com/strongloop/loopback-datasource-juggler/issues/93
|
||||||
|
var expectedProduct = this.product;
|
||||||
|
// Note: the URL format is not final
|
||||||
|
var url = '/api/categories/' + this.category.id + '?include=products';
|
||||||
|
|
||||||
|
this.get(url)
|
||||||
|
.expect(200, function(err, res) {
|
||||||
|
expect(res.body).to.have.property('products');
|
||||||
|
expect(res.body.products).to.eql([
|
||||||
|
{
|
||||||
|
id: expectedProduct.id,
|
||||||
|
name: expectedProduct.name
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -66,5 +66,4 @@ describe('remoting - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
})
|
});
|
||||||
;
|
|
||||||
|
|
Loading…
Reference in New Issue