Merge branch 'release/2.2.0' into production
This commit is contained in:
commit
06f1de2346
|
@ -390,13 +390,15 @@ function configureModel(ModelCtor, config, app) {
|
||||||
|
|
||||||
var dataSource = config.dataSource;
|
var dataSource = config.dataSource;
|
||||||
|
|
||||||
if(typeof dataSource === 'string') {
|
if(dataSource) {
|
||||||
dataSource = app.dataSources[dataSource];
|
if(typeof dataSource === 'string') {
|
||||||
}
|
dataSource = app.dataSources[dataSource];
|
||||||
|
}
|
||||||
|
|
||||||
assert(dataSource instanceof DataSource,
|
assert(dataSource instanceof DataSource,
|
||||||
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
|
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
|
||||||
config.dataSource +'"');
|
config.dataSource +'"');
|
||||||
|
}
|
||||||
|
|
||||||
config = extend({}, config);
|
config = extend({}, config);
|
||||||
config.dataSource = dataSource;
|
config.dataSource = dataSource;
|
||||||
|
|
|
@ -18,8 +18,21 @@ module.exports = MailConnector;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
function MailConnector(settings) {
|
function MailConnector(settings) {
|
||||||
|
|
||||||
assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');
|
assert(typeof settings === 'object', 'cannot initialize MailConnector without a settings object');
|
||||||
var transports = settings.transports || [];
|
|
||||||
|
var transports = settings.transports;
|
||||||
|
|
||||||
|
//if transports is not in settings object AND settings.transport exists
|
||||||
|
if(!transports && settings.transport){
|
||||||
|
//then wrap single transport in an array and assign to transports
|
||||||
|
transports = [settings.transport];
|
||||||
|
}
|
||||||
|
|
||||||
|
if(!transports){
|
||||||
|
transports = [];
|
||||||
|
}
|
||||||
|
|
||||||
this.transportsIndex = {};
|
this.transportsIndex = {};
|
||||||
this.transports = [];
|
this.transports = [];
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
*/
|
*/
|
||||||
var registry = require('../registry');
|
var registry = require('../registry');
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
|
var RemoteObjects = require('strong-remoting');
|
||||||
var SharedClass = require('strong-remoting').SharedClass;
|
var SharedClass = require('strong-remoting').SharedClass;
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
var stringUtils = require('underscore.string');
|
var stringUtils = require('underscore.string');
|
||||||
|
@ -28,6 +29,7 @@ var stringUtils = require('underscore.string');
|
||||||
* #### Event: `changed`
|
* #### Event: `changed`
|
||||||
*
|
*
|
||||||
* Emitted after a model has been successfully created, saved, or updated.
|
* Emitted after a model has been successfully created, saved, or updated.
|
||||||
|
* Argument: `inst`, model instance, object
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* MyModel.on('changed', function(inst) {
|
* MyModel.on('changed', function(inst) {
|
||||||
|
@ -39,10 +41,11 @@ var stringUtils = require('underscore.string');
|
||||||
* #### Event: `deleted`
|
* #### Event: `deleted`
|
||||||
*
|
*
|
||||||
* Emitted after an individual model has been deleted.
|
* Emitted after an individual model has been deleted.
|
||||||
|
* Argument: `id`, model ID (number).
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* MyModel.on('deleted', function(inst) {
|
* MyModel.on('deleted', function(id) {
|
||||||
* console.log('model with id %s has been deleted', inst.id);
|
* console.log('model with id %s has been deleted', id);
|
||||||
* // => model with id 1 has been deleted
|
* // => model with id 1 has been deleted
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
|
@ -50,11 +53,12 @@ var stringUtils = require('underscore.string');
|
||||||
* #### Event: `deletedAll`
|
* #### Event: `deletedAll`
|
||||||
*
|
*
|
||||||
* Emitted after an individual model has been deleted.
|
* Emitted after an individual model has been deleted.
|
||||||
|
* Argument: `where` (optional), where filter, JSON object.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* MyModel.on('deletedAll', function(where) {
|
* MyModel.on('deletedAll', function(where) {
|
||||||
* if(where) {
|
* if(where) {
|
||||||
* console.log('all models where', where, 'have been deleted');
|
* console.log('all models where ', where, ' have been deleted');
|
||||||
* // => all models where
|
* // => all models where
|
||||||
* // => {price: {gt: 100}}
|
* // => {price: {gt: 100}}
|
||||||
* // => have been deleted
|
* // => have been deleted
|
||||||
|
@ -70,6 +74,18 @@ var stringUtils = require('underscore.string');
|
||||||
*
|
*
|
||||||
* Emitted after a `Model` has been attached to a `DataSource`.
|
* Emitted after a `Model` has been attached to a `DataSource`.
|
||||||
*
|
*
|
||||||
|
* #### Event: set
|
||||||
|
*
|
||||||
|
* Emitted when model property is set.
|
||||||
|
* Argument: `inst`, model instance, object
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* MyModel.on('set', function(inst) {
|
||||||
|
* console.log('model with id %s has been changed', inst.id);
|
||||||
|
* // => model with id 1 has been changed
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
* @class
|
* @class
|
||||||
* @param {Object} data
|
* @param {Object} data
|
||||||
* @property {String} modelName The name of the model
|
* @property {String} modelName The name of the model
|
||||||
|
@ -85,6 +101,7 @@ var Model = module.exports = registry.modelBuilder.define('Model');
|
||||||
Model.setup = function () {
|
Model.setup = function () {
|
||||||
var ModelCtor = this;
|
var ModelCtor = this;
|
||||||
var options = this.settings;
|
var options = this.settings;
|
||||||
|
var typeName = this.modelName;
|
||||||
|
|
||||||
// create a sharedClass
|
// create a sharedClass
|
||||||
var sharedClass = ModelCtor.sharedClass = new SharedClass(
|
var sharedClass = ModelCtor.sharedClass = new SharedClass(
|
||||||
|
@ -93,6 +110,11 @@ Model.setup = function () {
|
||||||
options.remoting
|
options.remoting
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// setup a remoting type converter for this model
|
||||||
|
RemoteObjects.convert(typeName, function(val) {
|
||||||
|
return val ? new ModelCtor(val) : val;
|
||||||
|
});
|
||||||
|
|
||||||
// support remoting prototype methods
|
// support remoting prototype methods
|
||||||
ModelCtor.sharedCtor = function (data, id, fn) {
|
ModelCtor.sharedCtor = function (data, id, fn) {
|
||||||
var ModelCtor = this;
|
var ModelCtor = this;
|
||||||
|
@ -192,9 +214,12 @@ Model.setup = function () {
|
||||||
for (var relationName in relations) {
|
for (var relationName in relations) {
|
||||||
var relation = relations[relationName];
|
var relation = relations[relationName];
|
||||||
if (relation.type === 'belongsTo') {
|
if (relation.type === 'belongsTo') {
|
||||||
ModelCtor.belongsToRemoting(relationName, relation, define)
|
ModelCtor.belongsToRemoting(relationName, relation, define);
|
||||||
} else if (relation.type === 'hasOne') {
|
} else if (
|
||||||
ModelCtor.hasOneRemoting(relationName, relation, define)
|
relation.type === 'hasOne' ||
|
||||||
|
relation.type === 'embedsOne'
|
||||||
|
) {
|
||||||
|
ModelCtor.hasOneRemoting(relationName, relation, define);
|
||||||
} else if (
|
} else if (
|
||||||
relation.type === 'hasMany' ||
|
relation.type === 'hasMany' ||
|
||||||
relation.type === 'embedsMany' ||
|
relation.type === 'embedsMany' ||
|
||||||
|
@ -385,6 +410,16 @@ Model.hasManyRemoting = function (relationName, relation, define) {
|
||||||
var pathName = (relation.options.http && relation.options.http.path) || relationName;
|
var pathName = (relation.options.http && relation.options.http.path) || relationName;
|
||||||
var toModelName = relation.modelTo.modelName;
|
var toModelName = relation.modelTo.modelName;
|
||||||
|
|
||||||
|
function convertNullToNotFoundError(ctx, cb) {
|
||||||
|
if (ctx.result !== null) return cb();
|
||||||
|
|
||||||
|
var fk = ctx.getArgByName('fk');
|
||||||
|
var msg = 'Unknown "' + toModelName + '" id "' + fk + '".';
|
||||||
|
var error = new Error(msg);
|
||||||
|
error.statusCode = error.status = 404;
|
||||||
|
cb(error);
|
||||||
|
}
|
||||||
|
|
||||||
var findByIdFunc = this.prototype['__findById__' + relationName];
|
var findByIdFunc = this.prototype['__findById__' + relationName];
|
||||||
define('__findById__' + relationName, {
|
define('__findById__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
|
@ -393,7 +428,8 @@ Model.hasManyRemoting = function (relationName, relation, define) {
|
||||||
description: 'Foreign key for ' + relationName, required: true,
|
description: 'Foreign key for ' + relationName, required: true,
|
||||||
http: {source: 'path'}},
|
http: {source: 'path'}},
|
||||||
description: 'Find a related item by id for ' + relationName,
|
description: 'Find a related item by id for ' + relationName,
|
||||||
returns: {arg: 'result', type: toModelName, root: true}
|
returns: {arg: 'result', type: toModelName, root: true},
|
||||||
|
rest: {after: convertNullToNotFoundError}
|
||||||
}, findByIdFunc);
|
}, findByIdFunc);
|
||||||
|
|
||||||
var destroyByIdFunc = this.prototype['__destroyById__' + relationName];
|
var destroyByIdFunc = this.prototype['__destroyById__' + relationName];
|
||||||
|
@ -424,13 +460,19 @@ Model.hasManyRemoting = function (relationName, relation, define) {
|
||||||
if (relation.modelThrough || relation.type === 'referencesMany') {
|
if (relation.modelThrough || relation.type === 'referencesMany') {
|
||||||
var modelThrough = relation.modelThrough || relation.modelTo;
|
var modelThrough = relation.modelThrough || relation.modelTo;
|
||||||
|
|
||||||
|
var accepts = [];
|
||||||
|
if (relation.type === 'hasMany' && relation.modelThrough) {
|
||||||
|
// Restrict: only hasManyThrough relation can have additional properties
|
||||||
|
accepts.push({arg: 'data', type: modelThrough.modelName, http: {source: 'body'}});
|
||||||
|
}
|
||||||
|
|
||||||
var addFunc = this.prototype['__link__' + relationName];
|
var addFunc = this.prototype['__link__' + relationName];
|
||||||
define('__link__' + relationName, {
|
define('__link__' + relationName, {
|
||||||
isStatic: false,
|
isStatic: false,
|
||||||
http: {verb: 'put', path: '/' + pathName + '/rel/:fk'},
|
http: {verb: 'put', path: '/' + pathName + '/rel/:fk'},
|
||||||
accepts: {arg: 'fk', type: 'any',
|
accepts: [{arg: 'fk', type: 'any',
|
||||||
description: 'Foreign key for ' + relationName, required: true,
|
description: 'Foreign key for ' + relationName, required: true,
|
||||||
http: {source: 'path'}},
|
http: {source: 'path'}}].concat(accepts),
|
||||||
description: 'Add a related item by id for ' + relationName,
|
description: 'Add a related item by id for ' + relationName,
|
||||||
returns: {arg: relationName, type: modelThrough.modelName, root: true}
|
returns: {arg: relationName, type: modelThrough.modelName, root: true}
|
||||||
}, addFunc);
|
}, addFunc);
|
||||||
|
@ -507,9 +549,11 @@ Model.scopeRemoting = function(scopeName, scope, define) {
|
||||||
define('__count__' + scopeName, {
|
define('__count__' + scopeName, {
|
||||||
isStatic: isStatic,
|
isStatic: isStatic,
|
||||||
http: {verb: 'get', path: '/' + pathName + '/count'},
|
http: {verb: 'get', path: '/' + pathName + '/count'},
|
||||||
|
accepts: {arg: 'where', type: 'object', description: 'Criteria to match model instances'},
|
||||||
description: 'Counts ' + scopeName + ' of ' + this.modelName + '.',
|
description: 'Counts ' + scopeName + ' of ' + this.modelName + '.',
|
||||||
returns: {arg: 'count', type: 'number', root: true}
|
returns: {arg: 'count', type: 'number'}
|
||||||
});
|
});
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
Model.nestRemoting = function(relationName, options, cb) {
|
Model.nestRemoting = function(relationName, options, cb) {
|
||||||
|
@ -637,12 +681,12 @@ Model.nestRemoting = function(relationName, options, cb) {
|
||||||
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
|
var before = method.isStatic ? beforeListeners : beforeListeners['prototype'];
|
||||||
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
|
var after = method.isStatic ? afterListeners : afterListeners['prototype'];
|
||||||
var m = method.isStatic ? method.name : 'prototype.' + method.name;
|
var m = method.isStatic ? method.name : 'prototype.' + method.name;
|
||||||
if (before[delegateTo]) {
|
if (before && before[delegateTo]) {
|
||||||
self.beforeRemote(m, function(ctx, result, next) {
|
self.beforeRemote(m, function(ctx, result, next) {
|
||||||
before[delegateTo]._listeners.call(null, ctx, next);
|
before[delegateTo]._listeners.call(null, ctx, next);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (after[delegateTo]) {
|
if (after && after[delegateTo]) {
|
||||||
self.afterRemote(m, function(ctx, result, next) {
|
self.afterRemote(m, function(ctx, result, next) {
|
||||||
after[delegateTo]._listeners.call(null, ctx, next);
|
after[delegateTo]._listeners.call(null, ctx, next);
|
||||||
});
|
});
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
|
|
||||||
var Model = require('./model');
|
var Model = require('./model');
|
||||||
var runtime = require('../runtime');
|
var runtime = require('../runtime');
|
||||||
var RemoteObjects = require('strong-remoting');
|
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var async = require('async');
|
var async = require('async');
|
||||||
|
|
||||||
|
@ -37,12 +36,6 @@ PersistedModel.setup = function setupPersistedModel() {
|
||||||
Model.setup.call(this);
|
Model.setup.call(this);
|
||||||
|
|
||||||
var PersistedModel = this;
|
var PersistedModel = this;
|
||||||
var typeName = this.modelName;
|
|
||||||
|
|
||||||
// setup a remoting type converter for this model
|
|
||||||
RemoteObjects.convert(typeName, function(val) {
|
|
||||||
return val ? new PersistedModel(val) : val;
|
|
||||||
});
|
|
||||||
|
|
||||||
// enable change tracking (usually for replication)
|
// enable change tracking (usually for replication)
|
||||||
if(this.settings.trackChanges) {
|
if(this.settings.trackChanges) {
|
||||||
|
@ -438,6 +431,7 @@ PersistedModel.setupRemoting = function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'upsert', {
|
setRemoting(PersistedModel, 'upsert', {
|
||||||
|
aliases: ['updateOrCreate'],
|
||||||
description: 'Update an existing model instance or insert a new one into the data source',
|
description: 'Update an existing model instance or insert a new one into the data source',
|
||||||
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
|
accepts: {arg: 'data', type: 'object', description: 'Model instance data', http: {source: 'body'}},
|
||||||
returns: {arg: 'data', type: typeName, root: true},
|
returns: {arg: 'data', type: typeName, root: true},
|
||||||
|
@ -503,6 +497,7 @@ PersistedModel.setupRemoting = function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'updateAll', {
|
setRemoting(PersistedModel, 'updateAll', {
|
||||||
|
aliases: ['update'],
|
||||||
description: 'Update instances of the model matched by where from the data source',
|
description: 'Update instances of the model matched by where from the data source',
|
||||||
accepts: [
|
accepts: [
|
||||||
{arg: 'where', type: 'object', http: {source: 'query'},
|
{arg: 'where', type: 'object', http: {source: 'query'},
|
||||||
|
@ -514,6 +509,7 @@ PersistedModel.setupRemoting = function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
setRemoting(PersistedModel, 'deleteById', {
|
setRemoting(PersistedModel, 'deleteById', {
|
||||||
|
aliases: ['destroyById', 'removeById'],
|
||||||
description: 'Delete a model instance by id from the data source',
|
description: 'Delete a model instance by id from the data source',
|
||||||
accepts: {arg: 'id', type: 'any', description: 'Model id', required: true,
|
accepts: {arg: 'id', type: 'any', description: 'Model id', required: true,
|
||||||
http: {source: 'path'}},
|
http: {source: 'path'}},
|
||||||
|
|
|
@ -85,6 +85,13 @@ var options = {
|
||||||
principalId: Role.EVERYONE,
|
principalId: Role.EVERYONE,
|
||||||
permission: ACL.ALLOW,
|
permission: ACL.ALLOW,
|
||||||
property: "confirm"
|
property: "confirm"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
principalType: ACL.ROLE,
|
||||||
|
principalId: Role.EVERYONE,
|
||||||
|
permission: ACL.ALLOW,
|
||||||
|
property: "resetPassword",
|
||||||
|
accessType: ACL.EXECUTE
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
relations: {
|
relations: {
|
||||||
|
@ -154,7 +161,16 @@ User.login = function (credentials, include, fn) {
|
||||||
include = undefined;
|
include = undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
include = (include || '').toLowerCase();
|
include = (include || '');
|
||||||
|
if (Array.isArray(include)) {
|
||||||
|
include = include.map(function ( val ) {
|
||||||
|
return val.toLowerCase();
|
||||||
|
});
|
||||||
|
}else {
|
||||||
|
include = include.toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
var query = {};
|
var query = {};
|
||||||
if(credentials.email) {
|
if(credentials.email) {
|
||||||
|
@ -191,7 +207,7 @@ User.login = function (credentials, include, fn) {
|
||||||
} else if(isMatch) {
|
} else if(isMatch) {
|
||||||
user.createAccessToken(credentials.ttl, function(err, token) {
|
user.createAccessToken(credentials.ttl, function(err, token) {
|
||||||
if (err) return fn(err);
|
if (err) return fn(err);
|
||||||
if (include === 'user') {
|
if (Array.isArray(include) ? include.indexOf('user') !== -1 : include === 'user') {
|
||||||
// NOTE(bajtos) We can't set token.user here:
|
// NOTE(bajtos) We can't set token.user here:
|
||||||
// 1. token.user already exists, it's a function injected by
|
// 1. token.user already exists, it's a function injected by
|
||||||
// "AccessToken belongsTo User" relation
|
// "AccessToken belongsTo User" relation
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var extend = require('util')._extend;
|
var extend = require('util')._extend;
|
||||||
var juggler = require('loopback-datasource-juggler');
|
var juggler = require('loopback-datasource-juggler');
|
||||||
|
var debug = require('debug')('loopback:registry');
|
||||||
var DataSource = juggler.DataSource;
|
var DataSource = juggler.DataSource;
|
||||||
var ModelBuilder = juggler.ModelBuilder;
|
var ModelBuilder = juggler.ModelBuilder;
|
||||||
|
|
||||||
|
@ -171,6 +172,18 @@ registry.configureModel = function(ModelCtor, config) {
|
||||||
'Cannot configure ' + ModelCtor.modelName +
|
'Cannot configure ' + ModelCtor.modelName +
|
||||||
': config.dataSource must be an instance of DataSource');
|
': config.dataSource must be an instance of DataSource');
|
||||||
ModelCtor.attachTo(config.dataSource);
|
ModelCtor.attachTo(config.dataSource);
|
||||||
|
debug('Attached model `%s` to dataSource `%s`',
|
||||||
|
ModelCtor.definition.name, config.dataSource.name);
|
||||||
|
} else if (config.dataSource === null) {
|
||||||
|
debug('Model `%s` is not attached to any DataSource by configuration.',
|
||||||
|
ModelCtor.definition.name);
|
||||||
|
} else {
|
||||||
|
debug('Model `%s` is not attached to any DataSource, possibly by a mistake.',
|
||||||
|
ModelCtor.definition.name);
|
||||||
|
console.warn(
|
||||||
|
'The configuration of `%s` is missing `dataSource` property.\n' +
|
||||||
|
'Use `null` or `false` to mark models not attached to any data source.',
|
||||||
|
ModelCtor.definition.name);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
43
package.json
43
package.json
|
@ -3,6 +3,7 @@
|
||||||
"description": "LoopBack: Open Source Framework for Node.js",
|
"description": "LoopBack: Open Source Framework for Node.js",
|
||||||
"homepage": "http://loopback.io",
|
"homepage": "http://loopback.io",
|
||||||
"keywords": [
|
"keywords": [
|
||||||
|
"web",
|
||||||
"restful",
|
"restful",
|
||||||
"rest",
|
"rest",
|
||||||
"api",
|
"api",
|
||||||
|
@ -26,59 +27,59 @@
|
||||||
"mobile",
|
"mobile",
|
||||||
"mBaaS"
|
"mBaaS"
|
||||||
],
|
],
|
||||||
"version": "2.1.3",
|
"version": "2.2.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "grunt mocha-and-karma"
|
"test": "grunt mocha-and-karma"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"async": "~0.9.0",
|
"async": "~0.9.0",
|
||||||
"body-parser": "~1.4.3",
|
"body-parser": "~1.8.1",
|
||||||
"canonical-json": "0.0.4",
|
"canonical-json": "0.0.4",
|
||||||
"ejs": "~1.0.0",
|
"ejs": "~1.0.0",
|
||||||
"express": "4.x",
|
"express": "4.x",
|
||||||
"strong-remoting": "^2.0.0",
|
"strong-remoting": "^2.1.0",
|
||||||
"bcryptjs": "~2.0.1",
|
"bcryptjs": "~2.0.2",
|
||||||
"debug": "~1.0.4",
|
"debug": "~2.0.0",
|
||||||
"inflection": "~1.3.8",
|
"inflection": "~1.4.2",
|
||||||
"nodemailer": "~1.0.1",
|
"nodemailer": "~1.3.0",
|
||||||
"nodemailer-stub-transport": "~0.1.4",
|
"nodemailer-stub-transport": "~0.1.4",
|
||||||
"uid2": "0.0.3",
|
"uid2": "0.0.3",
|
||||||
"underscore": "~1.6.0",
|
"underscore": "~1.7.0",
|
||||||
"underscore.string": "~2.3.3"
|
"underscore.string": "~2.3.3"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"loopback-datasource-juggler": "^2.0.0"
|
"loopback-datasource-juggler": "^2.8.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"browserify": "~4.2.1",
|
"browserify": "~4.2.3",
|
||||||
"chai": "~1.9.1",
|
"chai": "~1.9.1",
|
||||||
"cookie-parser": "~1.3.2",
|
"cookie-parser": "~1.3.3",
|
||||||
"errorhandler": "~1.1.1",
|
"errorhandler": "~1.2.0",
|
||||||
"es5-shim": "^4.0.0",
|
"es5-shim": "^4.0.3",
|
||||||
"grunt": "~0.4.5",
|
"grunt": "~0.4.5",
|
||||||
"grunt-browserify": "~2.1.3",
|
"grunt-browserify": "~3.0.1",
|
||||||
"grunt-cli": "^0.1.13",
|
"grunt-cli": "^0.1.13",
|
||||||
"grunt-contrib-jshint": "~0.10.0",
|
"grunt-contrib-jshint": "~0.10.0",
|
||||||
"grunt-contrib-uglify": "~0.5.0",
|
"grunt-contrib-uglify": "~0.5.1",
|
||||||
"grunt-contrib-watch": "~0.6.1",
|
"grunt-contrib-watch": "~0.6.1",
|
||||||
"grunt-karma": "~0.8.3",
|
"grunt-karma": "~0.9.0",
|
||||||
"grunt-mocha-test": "^0.11.0",
|
"grunt-mocha-test": "^0.11.0",
|
||||||
"karma-browserify": "~0.2.1",
|
"karma-browserify": "~0.2.1",
|
||||||
"karma-chrome-launcher": "~0.1.4",
|
"karma-chrome-launcher": "~0.1.4",
|
||||||
"karma-firefox-launcher": "~0.1.3",
|
"karma-firefox-launcher": "~0.1.3",
|
||||||
"karma-html2js-preprocessor": "~0.1.0",
|
"karma-html2js-preprocessor": "~0.1.0",
|
||||||
"karma-junit-reporter": "^0.2.2",
|
"karma-junit-reporter": "^0.2.2",
|
||||||
"karma-mocha": "^0.1.4",
|
"karma-mocha": "^0.1.9",
|
||||||
"karma-phantomjs-launcher": "~0.1.4",
|
"karma-phantomjs-launcher": "~0.1.4",
|
||||||
"karma-script-launcher": "~0.1.0",
|
"karma-script-launcher": "~0.1.0",
|
||||||
"loopback-boot": "^1.1.0",
|
"loopback-boot": "^1.1.0",
|
||||||
"loopback-datasource-juggler": "^2.0.0",
|
"loopback-datasource-juggler": "^2.8.0",
|
||||||
"loopback-testing": "~0.2.0",
|
"loopback-testing": "~0.2.0",
|
||||||
"mocha": "~1.20.1",
|
"mocha": "~1.21.4",
|
||||||
"serve-favicon": "~2.0.1",
|
"serve-favicon": "~2.1.3",
|
||||||
"strong-task-emitter": "0.0.x",
|
"strong-task-emitter": "0.0.x",
|
||||||
"supertest": "~0.13.0",
|
"supertest": "~0.13.0",
|
||||||
"karma": "~0.12.17"
|
"karma": "~0.12.23"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|
|
@ -69,6 +69,15 @@ describe('app', function() {
|
||||||
request(app).get('/colors').expect(200, done);
|
request(app).get('/colors').expect(200, done);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('accepts null dataSource', function() {
|
||||||
|
app.model('MyTestModel', { dataSource: null });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not require dataSource', function() {
|
||||||
|
app.model('MyTestModel', {});
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('app.model(name, config)', function () {
|
describe('app.model(name, config)', function () {
|
||||||
|
|
|
@ -24,6 +24,17 @@ describe('Email connector', function () {
|
||||||
]});
|
]});
|
||||||
assert(connector.transportForName('stub'));
|
assert(connector.transportForName('stub'));
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it('should set up a single transport for SMTP' , function () {
|
||||||
|
var connector = new MailConnector({transport:
|
||||||
|
{type: 'smtp', service: 'gmail'}
|
||||||
|
});
|
||||||
|
|
||||||
|
assert(connector.transportForName('smtp'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('Email and SMTP', function () {
|
describe('Email and SMTP', function () {
|
||||||
|
@ -74,3 +85,5 @@ describe('Email and SMTP', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -185,6 +185,7 @@ describe('loopback', function() {
|
||||||
var model = loopback.Model.extend(uniqueModelName);
|
var model = loopback.Model.extend(uniqueModelName);
|
||||||
|
|
||||||
loopback.configureModel(model, {
|
loopback.configureModel(model, {
|
||||||
|
dataSource: null,
|
||||||
relations: {
|
relations: {
|
||||||
owner: {
|
owner: {
|
||||||
type: 'belongsTo',
|
type: 'belongsTo',
|
||||||
|
@ -207,6 +208,7 @@ describe('loopback', function() {
|
||||||
});
|
});
|
||||||
|
|
||||||
loopback.configureModel(model, {
|
loopback.configureModel(model, {
|
||||||
|
dataSource: null,
|
||||||
relations: {
|
relations: {
|
||||||
owner: {
|
owner: {
|
||||||
model: 'Application'
|
model: 'Application'
|
||||||
|
|
|
@ -482,4 +482,41 @@ describe.onServer('Remote Methods', function(){
|
||||||
assert.equal(model, acl);
|
assert.equal(model, acl);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PersistelModel remote methods', function() {
|
||||||
|
it('includes all aliases', function() {
|
||||||
|
var app = loopback();
|
||||||
|
var model = PersistedModel.extend('persistedModel');
|
||||||
|
app.dataSource('db', { connector: 'memory' });
|
||||||
|
app.model(model, { dataSource: 'db' });
|
||||||
|
|
||||||
|
// this code is used by loopback-sdk-angular codegen
|
||||||
|
var metadata = app.handler('rest')
|
||||||
|
.adapter
|
||||||
|
.getClasses()
|
||||||
|
.filter(function(c) { return c.name === 'persistedModel'; })[0];
|
||||||
|
|
||||||
|
var methodNames = [];
|
||||||
|
metadata.methods.forEach(function(method) {
|
||||||
|
methodNames.push(method.name);
|
||||||
|
methodNames = methodNames.concat(method.sharedMethod.aliases || []);
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(methodNames).to.have.members([
|
||||||
|
'destroyAll', 'deleteAll', 'remove',
|
||||||
|
'create',
|
||||||
|
'upsert', 'updateOrCreate',
|
||||||
|
'exists',
|
||||||
|
'findById',
|
||||||
|
'find',
|
||||||
|
'findOne',
|
||||||
|
'updateAll', 'update',
|
||||||
|
'deleteById',
|
||||||
|
'destroyById',
|
||||||
|
'removeById',
|
||||||
|
'count',
|
||||||
|
'prototype.updateAttributes'
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -114,6 +114,79 @@ describe('relations - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('/stores/:id/widgets/:fk - 200', function () {
|
||||||
|
beforeEach(function (done) {
|
||||||
|
var self = this;
|
||||||
|
this.store.widgets.create({
|
||||||
|
name: this.widgetName
|
||||||
|
}, function(err, widget) {
|
||||||
|
self.widget = widget;
|
||||||
|
self.url = '/api/stores/' + self.store.id + '/widgets/' + widget.id;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/stores/:id/widgets/:fk', function () {
|
||||||
|
it('should succeed with statusCode 200', function () {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
assert.equal(this.res.body.id, this.widget.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/stores/:id/widgets/:fk - 404', function () {
|
||||||
|
beforeEach(function () {
|
||||||
|
this.url = '/api/stores/' + this.store.id + '/widgets/123456';
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/stores/:id/widgets/:fk', function () {
|
||||||
|
it('should fail with statusCode 404', function () {
|
||||||
|
assert.equal(this.res.statusCode, 404);
|
||||||
|
assert.equal(this.res.body.error.status, 404);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/store/:id/widgets/count', function () {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.url = '/api/stores/' + this.store.id + '/widgets/count';
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count', function() {
|
||||||
|
it('should succeed with statusCode 200', function() {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
});
|
||||||
|
it('should return the count', function() {
|
||||||
|
assert.equal(this.res.body.count, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/store/:id/widgets/count - filtered (matches)', function () {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.url = '/api/stores/' + this.store.id + '/widgets/count?where[name]=foo';
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count?where[name]=foo', function() {
|
||||||
|
it('should succeed with statusCode 200', function() {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
});
|
||||||
|
it('should return the count', function() {
|
||||||
|
assert.equal(this.res.body.count, 1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('/store/:id/widgets/count - filtered (no matches)', function () {
|
||||||
|
beforeEach(function() {
|
||||||
|
this.url = '/api/stores/' + this.store.id + '/widgets/count?where[name]=bar';
|
||||||
|
});
|
||||||
|
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets/count?where[name]=bar', function() {
|
||||||
|
it('should succeed with statusCode 200', function() {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
});
|
||||||
|
it('should return the count', function() {
|
||||||
|
assert.equal(this.res.body.count, 0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('/widgets/:id/store', function () {
|
describe('/widgets/:id/store', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
var self = this;
|
var self = this;
|
||||||
|
@ -223,6 +296,51 @@ describe('relations - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('PUT /physicians/:id/patients/rel/:fk with data', function () {
|
||||||
|
|
||||||
|
before(function (done) {
|
||||||
|
var self = this;
|
||||||
|
setup(false, function (err, root) {
|
||||||
|
self.url = root.relUrl;
|
||||||
|
self.patient = root.patient;
|
||||||
|
self.physician = root.physician;
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
var NOW = Date.now();
|
||||||
|
var data = { date: new Date(NOW) };
|
||||||
|
|
||||||
|
lt.describe.whenCalledRemotely('PUT', '/api/physicians/:id/patients/rel/:fk', data, function () {
|
||||||
|
it('should succeed with statusCode 200', function () {
|
||||||
|
assert.equal(this.res.statusCode, 200);
|
||||||
|
assert.equal(this.res.body.patientId, this.patient.id);
|
||||||
|
assert.equal(this.res.body.physicianId, this.physician.id);
|
||||||
|
assert.equal(new Date(this.res.body.date).getTime(), NOW);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a record in appointment', function (done) {
|
||||||
|
var self = this;
|
||||||
|
app.models.appointment.find(function (err, apps) {
|
||||||
|
assert.equal(apps.length, 1);
|
||||||
|
assert.equal(apps[0].patientId, self.patient.id);
|
||||||
|
assert.equal(apps[0].physicianId, self.physician.id);
|
||||||
|
assert.equal(apps[0].date.getTime(), NOW);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should connect physician to patient', function (done) {
|
||||||
|
var self = this;
|
||||||
|
self.physician.patients(function (err, patients) {
|
||||||
|
assert.equal(patients.length, 1);
|
||||||
|
assert.equal(patients[0].id, self.patient.id);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('HEAD /physicians/:id/patients/rel/:fk', function () {
|
describe('HEAD /physicians/:id/patients/rel/:fk', function () {
|
||||||
|
|
||||||
before(function (done) {
|
before(function (done) {
|
||||||
|
@ -493,6 +611,65 @@ describe('relations - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('embedsOne', function() {
|
||||||
|
|
||||||
|
before(function defineGroupAndPosterModels() {
|
||||||
|
var group = app.model(
|
||||||
|
'group',
|
||||||
|
{ properties: { name: 'string' },
|
||||||
|
dataSource: 'db',
|
||||||
|
plural: 'groups'
|
||||||
|
}
|
||||||
|
);
|
||||||
|
var poster = app.model(
|
||||||
|
'poster',
|
||||||
|
{ properties: { url: 'string' }, dataSource: 'db' }
|
||||||
|
);
|
||||||
|
group.embedsOne(poster, { as: 'cover' });
|
||||||
|
});
|
||||||
|
|
||||||
|
before(function createImage(done) {
|
||||||
|
var test = this;
|
||||||
|
app.models.group.create({ name: 'Group 1' },
|
||||||
|
function(err, group) {
|
||||||
|
if (err) return done(err);
|
||||||
|
test.group = group;
|
||||||
|
group.cover.build({ url: 'http://image.url' });
|
||||||
|
group.save(done);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
after(function(done) {
|
||||||
|
this.app.models.group.destroyAll(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('includes the embedded models', function(done) {
|
||||||
|
var url = '/api/groups/' + this.group.id;
|
||||||
|
|
||||||
|
this.get(url)
|
||||||
|
.expect(200, function(err, res) {
|
||||||
|
expect(res.body.name).to.be.equal('Group 1');
|
||||||
|
expect(res.body.poster).to.be.eql(
|
||||||
|
{ url: 'http://image.url' }
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('returns the embedded model', function(done) {
|
||||||
|
var url = '/api/groups/' + this.group.id + '/cover';
|
||||||
|
|
||||||
|
this.get(url)
|
||||||
|
.expect(200, function(err, res) {
|
||||||
|
expect(res.body).to.be.eql(
|
||||||
|
{ url: 'http://image.url' }
|
||||||
|
);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
describe('embedsMany', function() {
|
describe('embedsMany', function() {
|
||||||
|
|
||||||
before(function defineProductAndCategoryModels() {
|
before(function defineProductAndCategoryModels() {
|
||||||
|
@ -628,7 +805,14 @@ describe('relations - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO - this.head is undefined
|
it('returns a 404 response when embedded model is not found', function(done) {
|
||||||
|
var url = '/api/todo-lists/' + this.todoList.id + '/items/2';
|
||||||
|
this.get(url).expect(404, function(err, res) {
|
||||||
|
expect(res.body.error.status).to.be.equal(404);
|
||||||
|
expect(res.body.error.message).to.be.equal('Unknown "todoItem" id "2".');
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it.skip('checks if an embedded model exists - ok', function(done) {
|
it.skip('checks if an embedded model exists - ok', function(done) {
|
||||||
var url = '/api/todo-lists/' + this.todoList.id + '/items/3';
|
var url = '/api/todo-lists/' + this.todoList.id + '/items/3';
|
||||||
|
@ -933,33 +1117,31 @@ describe('relations - integration', function () {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// TODO - this.head is undefined
|
it.skip('checks if a referenced model exists - ok', function(done) {
|
||||||
|
var url = '/api/recipes/' + this.recipe.id + '/ingredients/';
|
||||||
|
url += this.ingredient1;
|
||||||
|
|
||||||
// it.skip('checks if a referenced model exists - ok', function(done) {
|
this.head(url)
|
||||||
// var url = '/api/recipes/' + this.recipe.id + '/ingredients/';
|
.expect(200, function(err, res) {
|
||||||
// url += this.ingredient1;
|
done();
|
||||||
//
|
});
|
||||||
// this.head(url)
|
});
|
||||||
// .expect(200, function(err, res) {
|
|
||||||
// done();
|
|
||||||
// });
|
|
||||||
// });
|
|
||||||
|
|
||||||
// it.skip('checks if an referenced model exists - fail', function(done) {
|
it.skip('checks if an referenced model exists - fail', function(done) {
|
||||||
// var url = '/api/recipes/' + this.recipe.id + '/ingredients/';
|
var url = '/api/recipes/' + this.recipe.id + '/ingredients/';
|
||||||
// url += this.ingredient3;
|
url += this.ingredient3;
|
||||||
//
|
|
||||||
// this.head(url)
|
this.head(url)
|
||||||
// .expect(404, function(err, res) {
|
.expect(404, function(err, res) {
|
||||||
// done();
|
done();
|
||||||
// });
|
});
|
||||||
// });
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('nested relations', function() {
|
describe('nested relations', function() {
|
||||||
|
|
||||||
before(function defineProductAndCategoryModels() {
|
before(function defineModels() {
|
||||||
var Book = app.model(
|
var Book = app.model(
|
||||||
'Book',
|
'Book',
|
||||||
{ properties: { name: 'string' }, dataSource: 'db',
|
{ properties: { name: 'string' }, dataSource: 'db',
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
var loopback = require('../');
|
||||||
|
var request = require('supertest');
|
||||||
|
|
||||||
|
describe('remoting coercion', function() {
|
||||||
|
it('should coerce arguments based on the type', function(done) {
|
||||||
|
var called = false;
|
||||||
|
var app = loopback();
|
||||||
|
app.use(loopback.rest());
|
||||||
|
|
||||||
|
var TestModel = app.model('TestModel', {base: 'Model', dataSource: null, public: true});
|
||||||
|
TestModel.test = function(inst, cb) {
|
||||||
|
called = true;
|
||||||
|
assert(inst instanceof TestModel);
|
||||||
|
assert(inst.foo === 'bar');
|
||||||
|
cb();
|
||||||
|
}
|
||||||
|
TestModel.remoteMethod('test', {
|
||||||
|
accepts: {arg: 'inst', type: 'TestModel', http: {source: 'body'}},
|
||||||
|
http: {path: '/test', verb: 'post'}
|
||||||
|
});
|
||||||
|
|
||||||
|
request(app)
|
||||||
|
.post('/TestModels/test')
|
||||||
|
.set('Content-Type', 'application/json')
|
||||||
|
.send({
|
||||||
|
foo: 'bar'
|
||||||
|
})
|
||||||
|
.end(function(err) {
|
||||||
|
if(err) return done(err);
|
||||||
|
assert(called);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
})
|
|
@ -265,6 +265,22 @@ describe('User', function(){
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle multiple `include`', function(done) {
|
||||||
|
request(app)
|
||||||
|
.post('/users/login?include=USER&include=Post')
|
||||||
|
.send(validCredentials)
|
||||||
|
.expect(200)
|
||||||
|
.expect('Content-Type', /json/)
|
||||||
|
.end(function(err, res) {
|
||||||
|
if (err) return done(err);
|
||||||
|
var token = res.body;
|
||||||
|
expect(token.user, 'body.user').to.not.equal(undefined);
|
||||||
|
expect(token.user, 'body.user')
|
||||||
|
.to.have.property('email', validCredentials.email);
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
it('Login should only allow correct credentials', function(done) {
|
it('Login should only allow correct credentials', function(done) {
|
||||||
User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) {
|
User.create({email: 'foo22@bar.com', password: 'bar'}, function(user, err) {
|
||||||
User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, accessToken) {
|
User.login({email: 'foo44@bar.com', password: 'bar'}, function(err, accessToken) {
|
||||||
|
|
Loading…
Reference in New Issue