Merge branch 'release/1.9.0' into production
This commit is contained in:
commit
78869baa10
|
@ -64,7 +64,7 @@ as illustrated below:
|
||||||
|
|
||||||
## Resources
|
## Resources
|
||||||
|
|
||||||
* [Documentation](http://docs.strongloop.com/display/DOC/LoopBack).
|
* [Documentation](http://docs.strongloop.com/display/LB/LoopBack).
|
||||||
* [API documentation](http://apidocs.strongloop.com/loopback).
|
* [API documentation](http://apidocs.strongloop.com/loopback).
|
||||||
* [LoopBack Google Group](https://groups.google.com/forum/#!forum/loopbackjs).
|
* [LoopBack Google Group](https://groups.google.com/forum/#!forum/loopbackjs).
|
||||||
* [GitHub issues](https://github.com/strongloop/loopback/issues).
|
* [GitHub issues](https://github.com/strongloop/loopback/issues).
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
"content": [
|
"content": [
|
||||||
"lib/application.js",
|
"lib/application.js",
|
||||||
"lib/loopback.js",
|
"lib/loopback.js",
|
||||||
|
"lib/runtime.js",
|
||||||
|
"lib/registry.js",
|
||||||
{ "title": "Base model", "depth": 2 },
|
{ "title": "Base model", "depth": 2 },
|
||||||
"lib/models/model.js",
|
"lib/models/model.js",
|
||||||
"lib/models/data-model.js",
|
"lib/models/data-model.js",
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var DataSource = require('loopback-datasource-juggler').DataSource
|
var DataSource = require('loopback-datasource-juggler').DataSource
|
||||||
, ModelBuilder = require('loopback-datasource-juggler').ModelBuilder
|
, registry = require('./registry')
|
||||||
, compat = require('./compat')
|
, compat = require('./compat')
|
||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, fs = require('fs')
|
, fs = require('fs')
|
||||||
|
@ -82,60 +82,94 @@ app.disuse = function (route) {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Define and attach a model to the app. The `Model` will be available on the
|
* Attach a model to the app. The `Model` will be available on the
|
||||||
* `app.models` object.
|
* `app.models` object.
|
||||||
*
|
*
|
||||||
* ```js
|
* ```js
|
||||||
* var Widget = app.model('Widget', {dataSource: 'db'});
|
* // Attach an existing model
|
||||||
* Widget.create({name: 'pencil'});
|
* var User = loopback.User;
|
||||||
* app.models.Widget.find(function(err, widgets) {
|
* app.model(User);
|
||||||
* console.log(widgets[0]); // => {name: 'pencil'}
|
*
|
||||||
|
* // Attach an existing model, alter some aspects of the model
|
||||||
|
* var User = loopback.User;
|
||||||
|
* app.model(User, { dataSource: 'db' });
|
||||||
|
*
|
||||||
|
* // The old way: create and attach a new model (deprecated)
|
||||||
|
* var Widget = app.model('Widget', {
|
||||||
|
* dataSource: 'db',
|
||||||
|
* properties: {
|
||||||
|
* name: 'string'
|
||||||
|
* }
|
||||||
* });
|
* });
|
||||||
* ```
|
* ```
|
||||||
*
|
*
|
||||||
* @param {String} modelName The name of the model to define.
|
* @param {Object|String} Model The model to attach.
|
||||||
* @options {Object} config The model's configuration.
|
* @options {Object} config The model's configuration.
|
||||||
* @property {String|DataSource} dataSource The `DataSource` to which to attach the model.
|
* @property {String|DataSource} dataSource The `DataSource` to which to
|
||||||
* @property {Object} [options] an object containing `Model` options.
|
* attach the model.
|
||||||
* @property {ACL[]} [options.acls] an array of `ACL` definitions.
|
* @property {Boolean} [public] whether the model should be exposed via REST API
|
||||||
* @property {String[]} [options.hidden] **experimental** an array of properties to hide when accessed remotely.
|
* @property {Object} [relations] relations to add/update
|
||||||
* @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
|
||||||
*/
|
*/
|
||||||
|
|
||||||
app.model = function (Model, config) {
|
app.model = function (Model, config) {
|
||||||
if(arguments.length === 1) {
|
var isPublic = true;
|
||||||
assert(typeof Model === 'function', 'app.model(Model) => Model must be a function / constructor');
|
if (arguments.length > 1) {
|
||||||
assert(Model.modelName, 'Model must have a "modelName" property');
|
|
||||||
var remotingClassName = compat.getClassNameForRemoting(Model);
|
|
||||||
this.remotes().exports[remotingClassName] = Model;
|
|
||||||
this.models().push(Model);
|
|
||||||
clearHandlerCache(this);
|
|
||||||
Model.shared = true;
|
|
||||||
Model.app = this;
|
|
||||||
Model.emit('attached', this);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
var modelName = Model;
|
|
||||||
config = config || {};
|
config = config || {};
|
||||||
assert(typeof modelName === 'string', 'app.model(name, config) => "name" name must be a string');
|
if (typeof Model === 'string') {
|
||||||
|
// create & attach the model - backwards compatibility
|
||||||
|
|
||||||
Model =
|
// create config for loopback.modelFromConfig
|
||||||
|
var modelConfig = extend({}, config);
|
||||||
|
modelConfig.options = extend({}, config.options);
|
||||||
|
modelConfig.name = Model;
|
||||||
|
|
||||||
|
// modeller does not understand `dataSource` option
|
||||||
|
delete modelConfig.dataSource;
|
||||||
|
|
||||||
|
Model = registry.createModel(modelConfig);
|
||||||
|
|
||||||
|
// delete config options already applied
|
||||||
|
['relations', 'base', 'acls', 'hidden'].forEach(function(prop) {
|
||||||
|
delete config[prop];
|
||||||
|
if (config.options) delete config.options[prop];
|
||||||
|
});
|
||||||
|
delete config.properties;
|
||||||
|
}
|
||||||
|
|
||||||
|
configureModel(Model, config, this);
|
||||||
|
isPublic = config.public !== false;
|
||||||
|
} else {
|
||||||
|
assert(Model.prototype instanceof registry.Model,
|
||||||
|
'Model must be a descendant of loopback.Model');
|
||||||
|
}
|
||||||
|
|
||||||
|
var modelName = Model.modelName;
|
||||||
this.models[modelName] =
|
this.models[modelName] =
|
||||||
this.models[classify(modelName)] =
|
this.models[classify(modelName)] =
|
||||||
this.models[camelize(modelName)] = modelFromConfig(modelName, config, this);
|
this.models[camelize(modelName)] = Model;
|
||||||
|
|
||||||
if(config.public !== false) {
|
this.models().push(Model);
|
||||||
this.model(Model);
|
|
||||||
|
if (isPublic) {
|
||||||
|
var remotingClassName = compat.getClassNameForRemoting(Model);
|
||||||
|
this.remotes().exports[remotingClassName] = Model;
|
||||||
|
clearHandlerCache(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Model.shared = isPublic; // The base Model has shared = true
|
||||||
|
Model.app = this;
|
||||||
|
Model.emit('attached', this);
|
||||||
return Model;
|
return Model;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the models exported by the app. Returns only models defined using `app.model()`
|
* Get the models exported by the app. Returns only models defined using `app.model()`
|
||||||
*
|
*
|
||||||
|
* **Deprecated. Use the package
|
||||||
|
* [loopback-boot](https://github.com/strongloop/loopback-boot) instead.**
|
||||||
|
|
||||||
* There are two ways to access models:
|
* There are two ways to access models:
|
||||||
*
|
*
|
||||||
* 1. Call `app.models()` to get a list of all models.
|
* 1. Call `app.models()` to get a list of all models.
|
||||||
|
@ -295,6 +329,7 @@ app.dataSources = app.datasources = {};
|
||||||
|
|
||||||
app.enableAuth = function() {
|
app.enableAuth = function() {
|
||||||
var remotes = this.remotes();
|
var remotes = this.remotes();
|
||||||
|
var app = this;
|
||||||
|
|
||||||
remotes.before('**', function(ctx, next, method) {
|
remotes.before('**', function(ctx, next, method) {
|
||||||
var req = ctx.req;
|
var req = ctx.req;
|
||||||
|
@ -302,6 +337,12 @@ app.enableAuth = function() {
|
||||||
var modelInstance = ctx.instance;
|
var modelInstance = ctx.instance;
|
||||||
var modelId = modelInstance && modelInstance.id || req.param('id');
|
var modelId = modelInstance && modelInstance.id || req.param('id');
|
||||||
|
|
||||||
|
var modelSettings = Model.settings || {};
|
||||||
|
var errStatusCode = modelSettings.aclErrorStatus || app.get('aclErrorStatus') || 401;
|
||||||
|
if(!req.accessToken){
|
||||||
|
errStatusCode = 401;
|
||||||
|
}
|
||||||
|
|
||||||
if(Model.checkAccess) {
|
if(Model.checkAccess) {
|
||||||
// Pause the request before checking access
|
// Pause the request before checking access
|
||||||
// See https://github.com/strongloop/loopback-storage-service/issues/7
|
// See https://github.com/strongloop/loopback-storage-service/issues/7
|
||||||
|
@ -319,8 +360,15 @@ app.enableAuth = function() {
|
||||||
} else if(allowed) {
|
} else if(allowed) {
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
var e = new Error('Access Denied');
|
|
||||||
e.statusCode = 401;
|
var messages = {
|
||||||
|
403:'Access Denied',
|
||||||
|
404: ('could not find a model with id ' + modelId),
|
||||||
|
401:'Authorization Required'
|
||||||
|
};
|
||||||
|
|
||||||
|
var e = new Error(messages[errStatusCode] || messages[403]);
|
||||||
|
e.statusCode = errStatusCode;
|
||||||
next(e);
|
next(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -500,7 +548,7 @@ app.boot = function(options) {
|
||||||
|
|
||||||
// try to attach models to dataSources by type
|
// try to attach models to dataSources by type
|
||||||
try {
|
try {
|
||||||
require('./loopback').autoAttach();
|
registry.autoAttach();
|
||||||
} catch(e) {
|
} catch(e) {
|
||||||
if(e.name === 'AssertionError') {
|
if(e.name === 'AssertionError') {
|
||||||
console.warn(e);
|
console.warn(e);
|
||||||
|
@ -563,44 +611,27 @@ function dataSourcesFromConfig(config, connectorRegistry) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return require('./loopback').createDataSource(config);
|
return registry.createDataSource(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
function modelFromConfig(name, config, app) {
|
function configureModel(ModelCtor, config, app) {
|
||||||
var options = buildModelOptionsFromConfig(config);
|
assert(ModelCtor.prototype instanceof registry.Model,
|
||||||
var properties = config.properties;
|
'Model must be a descendant of loopback.Model');
|
||||||
|
|
||||||
var ModelCtor = require('./loopback').createModel(name, properties, options);
|
|
||||||
var dataSource = config.dataSource;
|
var dataSource = config.dataSource;
|
||||||
|
|
||||||
if(typeof dataSource === 'string') {
|
if(typeof dataSource === 'string') {
|
||||||
dataSource = app.dataSources[dataSource];
|
dataSource = app.dataSources[dataSource];
|
||||||
}
|
}
|
||||||
|
|
||||||
assert(isDataSource(dataSource), name + ' is referencing a dataSource that does not exist: "'+ config.dataSource +'"');
|
assert(dataSource instanceof DataSource,
|
||||||
|
ModelCtor.modelName + ' is referencing a dataSource that does not exist: "' +
|
||||||
|
config.dataSource +'"');
|
||||||
|
|
||||||
ModelCtor.attachTo(dataSource);
|
config = extend({}, config);
|
||||||
return ModelCtor;
|
config.dataSource = dataSource;
|
||||||
}
|
|
||||||
|
|
||||||
function buildModelOptionsFromConfig(config) {
|
registry.configureModel(ModelCtor, config);
|
||||||
var options = extend({}, config.options);
|
|
||||||
for (var key in config) {
|
|
||||||
if (['properties', 'options', 'dataSource'].indexOf(key) !== -1) {
|
|
||||||
// Skip items which have special meaning
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (options[key] !== undefined) {
|
|
||||||
// When both `config.key` and `config.options.key` are set,
|
|
||||||
// use the latter one to preserve backwards compatibility
|
|
||||||
// with loopback 1.x
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
options[key] = config[key];
|
|
||||||
}
|
|
||||||
return options;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function requireDir(dir, basenames) {
|
function requireDir(dir, basenames) {
|
||||||
|
@ -672,14 +703,6 @@ function tryReadDir() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function isModelCtor(obj) {
|
|
||||||
return typeof obj === 'function' && obj.modelName && obj.name === 'ModelCtor';
|
|
||||||
}
|
|
||||||
|
|
||||||
function isDataSource(obj) {
|
|
||||||
return obj instanceof DataSource;
|
|
||||||
}
|
|
||||||
|
|
||||||
function tryReadConfig(cwd, fileName) {
|
function tryReadConfig(cwd, fileName) {
|
||||||
try {
|
try {
|
||||||
return require(path.join(cwd, fileName + '.json'));
|
return require(path.join(cwd, fileName + '.json'));
|
||||||
|
|
|
@ -1,7 +1,25 @@
|
||||||
module.exports = browserExpress;
|
module.exports = browserExpress;
|
||||||
|
|
||||||
function browserExpress() {
|
function browserExpress() {
|
||||||
return {};
|
return new BrowserExpress();
|
||||||
}
|
}
|
||||||
|
|
||||||
browserExpress.errorHandler = {};
|
browserExpress.errorHandler = {};
|
||||||
|
|
||||||
|
function BrowserExpress() {
|
||||||
|
this.settings = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
BrowserExpress.prototype.set = function(key, value) {
|
||||||
|
if (arguments.length == 1) {
|
||||||
|
return this.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.settings[key] = value;
|
||||||
|
|
||||||
|
return this; // fluent API
|
||||||
|
};
|
||||||
|
|
||||||
|
BrowserExpress.prototype.get = function(key) {
|
||||||
|
return this.settings[key];
|
||||||
|
};
|
||||||
|
|
|
@ -4,9 +4,8 @@
|
||||||
|
|
||||||
var mailer = require('nodemailer')
|
var mailer = require('nodemailer')
|
||||||
, assert = require('assert')
|
, assert = require('assert')
|
||||||
, debug = require('debug')
|
, debug = require('debug')('loopback:connector:mail')
|
||||||
, loopback = require('../loopback')
|
, loopback = require('../loopback')
|
||||||
, STUB = 'STUB';
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Export the MailConnector class.
|
* Export the MailConnector class.
|
||||||
|
|
194
lib/loopback.js
194
lib/loopback.js
|
@ -3,11 +3,11 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
var express = require('express')
|
var express = require('express')
|
||||||
|
, proto = require('./application')
|
||||||
, fs = require('fs')
|
, fs = require('fs')
|
||||||
, ejs = require('ejs')
|
, ejs = require('ejs')
|
||||||
, EventEmitter = require('events').EventEmitter
|
, EventEmitter = require('events').EventEmitter
|
||||||
, path = require('path')
|
, path = require('path')
|
||||||
, proto = require('./application')
|
|
||||||
, DataSource = require('loopback-datasource-juggler').DataSource
|
, DataSource = require('loopback-datasource-juggler').DataSource
|
||||||
, ModelBuilder = require('loopback-datasource-juggler').ModelBuilder
|
, ModelBuilder = require('loopback-datasource-juggler').ModelBuilder
|
||||||
, i8n = require('inflection')
|
, i8n = require('inflection')
|
||||||
|
@ -30,18 +30,6 @@ var express = require('express')
|
||||||
|
|
||||||
var loopback = exports = module.exports = createApplication;
|
var loopback = exports = module.exports = createApplication;
|
||||||
|
|
||||||
/**
|
|
||||||
* True if running in a browser environment; false otherwise.
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.isBrowser = typeof window !== 'undefined';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* True if running in a server environment; false otherwise.
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.isServer = !loopback.isBrowser;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Framework version.
|
* Framework version.
|
||||||
*/
|
*/
|
||||||
|
@ -71,6 +59,8 @@ function createApplication() {
|
||||||
|
|
||||||
merge(app, proto);
|
merge(app, proto);
|
||||||
|
|
||||||
|
app.loopback = loopback;
|
||||||
|
|
||||||
// Create a new instance of models registry per each app instance
|
// Create a new instance of models registry per each app instance
|
||||||
app.models = function() {
|
app.models = function() {
|
||||||
return proto.models.apply(this, arguments);
|
return proto.models.apply(this, arguments);
|
||||||
|
@ -91,17 +81,23 @@ function createApplication() {
|
||||||
return app;
|
return app;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function mixin(source) {
|
||||||
|
for (var key in source) {
|
||||||
|
var desc = Object.getOwnPropertyDescriptor(source, key);
|
||||||
|
Object.defineProperty(loopback, key, desc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mixin(require('./runtime'));
|
||||||
|
mixin(require('./registry'));
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Expose express.middleware as loopback.*
|
* Expose express.middleware as loopback.*
|
||||||
* for example `loopback.errorHandler` etc.
|
* for example `loopback.errorHandler` etc.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
for (var key in express) {
|
mixin(express);
|
||||||
Object.defineProperty(
|
|
||||||
loopback
|
|
||||||
, key
|
|
||||||
, Object.getOwnPropertyDescriptor(express, key));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Expose additional loopback middleware
|
* Expose additional loopback middleware
|
||||||
|
@ -127,58 +123,6 @@ if (loopback.isServer) {
|
||||||
|
|
||||||
loopback.errorHandler.title = 'Loopback';
|
loopback.errorHandler.title = 'Loopback';
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a data source with passing the provided options to the connector.
|
|
||||||
*
|
|
||||||
* @param {String} name Optional name.
|
|
||||||
* @options {Object} Data Source options
|
|
||||||
* @property {Object} connector LoopBack connector.
|
|
||||||
* @property {*} Other properties See the relevant connector documentation.
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.createDataSource = function (name, options) {
|
|
||||||
var ds = new DataSource(name, options, loopback.Model.modelBuilder);
|
|
||||||
ds.createModel = function (name, properties, settings) {
|
|
||||||
var ModelCtor = loopback.createModel(name, properties, settings);
|
|
||||||
ModelCtor.attachTo(ds);
|
|
||||||
return ModelCtor;
|
|
||||||
};
|
|
||||||
|
|
||||||
if(ds.settings && ds.settings.defaultForType) {
|
|
||||||
loopback.setDefaultDataSourceForType(ds.settings.defaultForType, ds);
|
|
||||||
}
|
|
||||||
|
|
||||||
return ds;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create a named vanilla JavaScript class constructor with an attached set of properties and options.
|
|
||||||
*
|
|
||||||
* @param {String} name Unique name.
|
|
||||||
* @param {Object} properties
|
|
||||||
* @param {Object} options (optional)
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.createModel = function (name, properties, options) {
|
|
||||||
options = options || {};
|
|
||||||
var BaseModel = options.base || options.super;
|
|
||||||
|
|
||||||
if(typeof BaseModel === 'string') {
|
|
||||||
BaseModel = loopback.getModel(BaseModel);
|
|
||||||
}
|
|
||||||
|
|
||||||
BaseModel = BaseModel || loopback.Model;
|
|
||||||
|
|
||||||
var model = BaseModel.extend(name, properties, options);
|
|
||||||
|
|
||||||
// try to attach
|
|
||||||
try {
|
|
||||||
loopback.autoAttachModel(model);
|
|
||||||
} catch(e) {}
|
|
||||||
|
|
||||||
return model;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Add a remote method to a model.
|
* Add a remote method to a model.
|
||||||
* @param {Function} fn
|
* @param {Function} fn
|
||||||
|
@ -211,119 +155,11 @@ loopback.template = function (file) {
|
||||||
return ejs.compile(str);
|
return ejs.compile(str);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
|
||||||
* Get an in-memory data source. Use one if it already exists.
|
|
||||||
*
|
|
||||||
* @param {String} [name] The name of the data source. If not provided, the `'default'` is used.
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.memory = function (name) {
|
|
||||||
name = name || 'default';
|
|
||||||
var memory = (
|
|
||||||
this._memoryDataSources
|
|
||||||
|| (this._memoryDataSources = {})
|
|
||||||
)[name];
|
|
||||||
|
|
||||||
if(!memory) {
|
|
||||||
memory = this._memoryDataSources[name] = loopback.createDataSource({
|
|
||||||
connector: loopback.Memory
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return memory;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Look up a model class by name from all models created by loopback.createModel()
|
|
||||||
* @param {String} modelName The model name
|
|
||||||
* @returns {Model} The model class
|
|
||||||
*/
|
|
||||||
loopback.getModel = function(modelName) {
|
|
||||||
return loopback.Model.modelBuilder.models[modelName];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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.
|
|
||||||
* @param {Model} The base model class
|
|
||||||
* @returns {Model} The subclass if found or the base class
|
|
||||||
*/
|
|
||||||
loopback.getModelByType = function(modelType) {
|
|
||||||
assert(typeof modelType === 'function', 'The model type must be a constructor');
|
|
||||||
var models = loopback.Model.modelBuilder.models;
|
|
||||||
for(var m in models) {
|
|
||||||
if(models[m].prototype instanceof modelType) {
|
|
||||||
return models[m];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return modelType;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the default `dataSource` for a given `type`.
|
|
||||||
* @param {String} type The datasource type
|
|
||||||
* @param {Object|DataSource} dataSource The data source settings or instance
|
|
||||||
* @returns {DataSource} The data source instance
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.setDefaultDataSourceForType = function(type, dataSource) {
|
|
||||||
var defaultDataSources = this.defaultDataSources || (this.defaultDataSources = {});
|
|
||||||
|
|
||||||
if(!(dataSource instanceof DataSource)) {
|
|
||||||
dataSource = this.createDataSource(dataSource);
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultDataSources[type] = dataSource;
|
|
||||||
return dataSource;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the default `dataSource` for a given `type`.
|
|
||||||
* @param {String} type The datasource type
|
|
||||||
* @returns {DataSource} The data source instance
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.getDefaultDataSourceForType = function(type) {
|
|
||||||
return this.defaultDataSources && this.defaultDataSources[type];
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attach any model that does not have a dataSource to
|
|
||||||
* the default dataSource for the type the Model requests
|
|
||||||
*/
|
|
||||||
|
|
||||||
loopback.autoAttach = function() {
|
|
||||||
var models = this.Model.modelBuilder.models;
|
|
||||||
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
|
|
||||||
|
|
||||||
Object.keys(models).forEach(function(modelName) {
|
|
||||||
var ModelCtor = models[modelName];
|
|
||||||
|
|
||||||
// Only auto attach if the model doesn't have an explicit data source
|
|
||||||
if(ModelCtor && (!(ModelCtor.dataSource instanceof DataSource))) {
|
|
||||||
loopback.autoAttachModel(ModelCtor);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
loopback.autoAttachModel = function(ModelCtor) {
|
|
||||||
if(ModelCtor.autoAttach) {
|
|
||||||
var ds = loopback.getDefaultDataSourceForType(ModelCtor.autoAttach);
|
|
||||||
|
|
||||||
assert(ds instanceof DataSource, 'cannot autoAttach model "'
|
|
||||||
+ ModelCtor.modelName
|
|
||||||
+ '". No dataSource found of type ' + ModelCtor.autoAttach);
|
|
||||||
|
|
||||||
ModelCtor.attachTo(ds);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/*!
|
/*!
|
||||||
* Built in models / services
|
* Built in models / services
|
||||||
*/
|
*/
|
||||||
|
|
||||||
loopback.Model = require('./models/model');
|
|
||||||
loopback.DataModel = require('./models/data-model');
|
|
||||||
loopback.Email = require('./models/email');
|
loopback.Email = require('./models/email');
|
||||||
loopback.User = require('./models/user');
|
loopback.User = require('./models/user');
|
||||||
loopback.Application = require('./models/application');
|
loopback.Application = require('./models/application');
|
||||||
|
|
|
@ -191,11 +191,9 @@ function tokenIdForRequest(req, options) {
|
||||||
var length;
|
var length;
|
||||||
var id;
|
var id;
|
||||||
|
|
||||||
params.push('access_token');
|
params = params.concat(['access_token']);
|
||||||
headers.push('X-Access-Token');
|
headers = headers.concat(['X-Access-Token', 'authorization']);
|
||||||
headers.push('authorization');
|
cookies = cookies.concat(['access_token', 'authorization']);
|
||||||
cookies.push('access_token');
|
|
||||||
cookies.push('authorization');
|
|
||||||
|
|
||||||
for(length = params.length; i < length; i++) {
|
for(length = params.length; i < length; i++) {
|
||||||
id = req.param(params[i]);
|
id = req.param(params[i]);
|
||||||
|
|
|
@ -61,6 +61,7 @@ var ACLSchema = {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Name of the access type - READ/WRITE/EXEC
|
* Name of the access type - READ/WRITE/EXEC
|
||||||
|
* @property accessType {String} Name of the access type - READ/WRITE/EXEC
|
||||||
*/
|
*/
|
||||||
accessType: String,
|
accessType: String,
|
||||||
|
|
||||||
|
@ -114,7 +115,7 @@ ACL.SCOPE = Principal.SCOPE;
|
||||||
* Calculate the matching score for the given rule and request
|
* Calculate the matching score for the given rule and request
|
||||||
* @param {ACL} rule The ACL entry
|
* @param {ACL} rule The ACL entry
|
||||||
* @param {AccessRequest} req The request
|
* @param {AccessRequest} req The request
|
||||||
* @returns {number}
|
* @returns {Number}
|
||||||
*/
|
*/
|
||||||
ACL.getMatchingScore = function getMatchingScore(rule, req) {
|
ACL.getMatchingScore = function getMatchingScore(rule, req) {
|
||||||
var props = ['model', 'property', 'accessType'];
|
var props = ['model', 'property', 'accessType'];
|
||||||
|
@ -296,14 +297,12 @@ ACL.getStaticACLs = function getStaticACLs(model, property) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the given principal is allowed to access the model/property
|
* Check if the given principal is allowed to access the model/property
|
||||||
* @param {String} principalType The principal type
|
* @param {String} principalType The principal type.
|
||||||
* @param {String} principalId The principal id
|
* @param {String} principalId The principal ID.
|
||||||
* @param {String} model The model name
|
* @param {String} model The model name.
|
||||||
* @param {String} property The property/method/relation name
|
* @param {String} property The property/method/relation name.
|
||||||
* @param {String} accessType The access type
|
* @param {String} accessType The access type.
|
||||||
* @param {Function} callback The callback function
|
* @callback {Function} callback Callback function.
|
||||||
*
|
|
||||||
* @callback callback
|
|
||||||
* @param {String|Error} err The error object
|
* @param {String|Error} err The error object
|
||||||
* @param {AccessRequest} result The access permission
|
* @param {AccessRequest} result The access permission
|
||||||
*/
|
*/
|
||||||
|
@ -364,14 +363,14 @@ ACL.prototype.debug = function() {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if the request has the permission to access
|
* Check if the request has the permission to access.
|
||||||
* @param {Object} context
|
* @options {Object} context See below.
|
||||||
* @property {Object[]} principals An array of principals
|
* @property {Object[]} principals An array of principals.
|
||||||
* @property {String|Model} model The model name or model class
|
* @property {String|Model} model The model name or model class.
|
||||||
* @property {*} id The model instance id
|
* @property {*} id The model instance ID.
|
||||||
* @property {String} property The property/method/relation name
|
* @property {String} property The property/method/relation name.
|
||||||
* @property {String} accessType The access type
|
* @property {String} accessType The access type: READE, WRITE, or EXEC.
|
||||||
* @param {Function} callback
|
* @param {Function} callback Callback function
|
||||||
*/
|
*/
|
||||||
|
|
||||||
ACL.checkAccessForContext = function (context, callback) {
|
ACL.checkAccessForContext = function (context, callback) {
|
||||||
|
@ -452,8 +451,7 @@ ACL.checkAccessForContext = function (context, callback) {
|
||||||
* @param {String} model The model name
|
* @param {String} model The model name
|
||||||
* @param {*} modelId The model id
|
* @param {*} modelId The model id
|
||||||
* @param {String} method The method name
|
* @param {String} method The method name
|
||||||
* @end
|
* @callback {Function} callback Callback function
|
||||||
* @callback {Function} callback
|
|
||||||
* @param {String|Error} err The error object
|
* @param {String|Error} err The error object
|
||||||
* @param {Boolean} allowed is the request allowed
|
* @param {Boolean} allowed is the request allowed
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -46,7 +46,7 @@ var PushNotificationSettingSchema = {
|
||||||
gcm: GcmSettingsSchema
|
gcm: GcmSettingsSchema
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/*!
|
||||||
* Data model for Application
|
* Data model for Application
|
||||||
*/
|
*/
|
||||||
var ApplicationSchema = {
|
var ApplicationSchema = {
|
||||||
|
@ -133,10 +133,10 @@ Application.beforeCreate = function (next) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a new application
|
* Register a new application
|
||||||
* @param owner Owner's user id
|
* @param {String} owner Owner's user ID.
|
||||||
* @param name Name of the application
|
* @param {String} name Name of the application
|
||||||
* @param options Other options
|
* @param {Object} options Other options
|
||||||
* @param cb Callback function
|
* @param {Function} callback Callback function
|
||||||
*/
|
*/
|
||||||
Application.register = function (owner, name, options, cb) {
|
Application.register = function (owner, name, options, cb) {
|
||||||
assert(owner, 'owner is required');
|
assert(owner, 'owner is required');
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
/*!
|
/*!
|
||||||
* Module Dependencies.
|
* Module Dependencies.
|
||||||
*/
|
*/
|
||||||
var loopback = require('../loopback');
|
var registry = require('../registry');
|
||||||
var compat = require('../compat');
|
var compat = require('../compat');
|
||||||
var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;
|
var ModelBuilder = require('loopback-datasource-juggler').ModelBuilder;
|
||||||
var modeler = new ModelBuilder();
|
var modeler = new ModelBuilder();
|
||||||
|
@ -128,7 +128,7 @@ Model._ACL = function getACL(ACL) {
|
||||||
return _aclModel;
|
return _aclModel;
|
||||||
}
|
}
|
||||||
var aclModel = require('./acl').ACL;
|
var aclModel = require('./acl').ACL;
|
||||||
_aclModel = loopback.getModelByType(aclModel);
|
_aclModel = registry.getModelByType(aclModel);
|
||||||
return _aclModel;
|
return _aclModel;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -136,13 +136,11 @@ Model._ACL = function getACL(ACL) {
|
||||||
* Check if the given access token can invoke the method
|
* Check if the given access token can invoke the method
|
||||||
*
|
*
|
||||||
* @param {AccessToken} token The access token
|
* @param {AccessToken} token The access token
|
||||||
* @param {*} modelId The model id
|
* @param {*} modelId The model ID.
|
||||||
* @param {SharedMethod} sharedMethod
|
* @param {SharedMethod} sharedMethod The method in question
|
||||||
* @param callback The callback function
|
* @callback {Function} callback The callback function
|
||||||
*
|
|
||||||
* @callback {Function} callback
|
|
||||||
* @param {String|Error} err The error object
|
* @param {String|Error} err The error object
|
||||||
* @param {Boolean} allowed is the request allowed
|
* @param {Boolean} allowed True if the request is allowed; false otherwise.
|
||||||
*/
|
*/
|
||||||
Model.checkAccess = function(token, modelId, sharedMethod, callback) {
|
Model.checkAccess = function(token, modelId, sharedMethod, callback) {
|
||||||
var ANONYMOUS = require('./access-token').ANONYMOUS;
|
var ANONYMOUS = require('./access-token').ANONYMOUS;
|
||||||
|
|
|
@ -7,7 +7,7 @@ var AccessContext = require('./access-context').AccessContext;
|
||||||
|
|
||||||
// Role model
|
// Role model
|
||||||
var RoleSchema = {
|
var RoleSchema = {
|
||||||
id: {type: String, id: true}, // Id
|
id: {type: String, id: true, generated: true}, // Id
|
||||||
name: {type: String, required: true}, // The name of a role
|
name: {type: String, required: true}, // The name of a role
|
||||||
description: String, // Description
|
description: String, // Description
|
||||||
|
|
||||||
|
@ -20,8 +20,8 @@ var RoleSchema = {
|
||||||
* Map principals to roles
|
* Map principals to roles
|
||||||
*/
|
*/
|
||||||
var RoleMappingSchema = {
|
var RoleMappingSchema = {
|
||||||
id: {type: String, id: true}, // Id
|
id: {type: String, id: true, generated: true}, // Id
|
||||||
roleId: String, // The role id
|
// roleId: String, // The role id, to be injected by the belongsTo relation
|
||||||
principalType: String, // The principal type, such as user, application, or role
|
principalType: String, // The principal type, such as user, application, or role
|
||||||
principalId: String // The principal id
|
principalId: String // The principal id
|
||||||
};
|
};
|
||||||
|
|
|
@ -0,0 +1,329 @@
|
||||||
|
/*
|
||||||
|
* This file exports methods and objects for manipulating
|
||||||
|
* Models and DataSources.
|
||||||
|
*
|
||||||
|
* It is an internal file that should not be used outside of loopback.
|
||||||
|
* All exported entities can be accessed via the `loopback` object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var assert = require('assert');
|
||||||
|
var extend = require('util')._extend;
|
||||||
|
var DataSource = require('loopback-datasource-juggler').DataSource;
|
||||||
|
|
||||||
|
var registry = module.exports;
|
||||||
|
|
||||||
|
registry.defaultDataSources = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a named vanilla JavaScript class constructor with an attached
|
||||||
|
* set of properties and options.
|
||||||
|
*
|
||||||
|
* This function comes with two variants:
|
||||||
|
* * `loopback.createModel(name, properties, options)`
|
||||||
|
* * `loopback.createModel(config)`
|
||||||
|
*
|
||||||
|
* In the second variant, the parameters `name`, `properties` and `options`
|
||||||
|
* are provided in the config object. Any additional config entries are
|
||||||
|
* interpreted as `options`, i.e. the following two configs are identical:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* { name: 'Customer', base: 'User' }
|
||||||
|
* { name: 'Customer', options: { base: 'User' } }
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* **Example**
|
||||||
|
*
|
||||||
|
* Create an `Author` model using the three-parameter variant:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* loopback.createModel(
|
||||||
|
* 'Author',
|
||||||
|
* {
|
||||||
|
* firstName: 'string',
|
||||||
|
* lastName: 'string
|
||||||
|
* },
|
||||||
|
* {
|
||||||
|
* relations: {
|
||||||
|
* books: {
|
||||||
|
* model: 'Book',
|
||||||
|
* type: 'hasAndBelongsToMany'
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* );
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* Create the same model using a config object:
|
||||||
|
*
|
||||||
|
* ```js
|
||||||
|
* loopback.createModel({
|
||||||
|
* name: 'Author',
|
||||||
|
* properties: {
|
||||||
|
* firstName: 'string',
|
||||||
|
* lastName: 'string
|
||||||
|
* },
|
||||||
|
* relations: {
|
||||||
|
* books: {
|
||||||
|
* model: 'Book',
|
||||||
|
* type: 'hasAndBelongsToMany'
|
||||||
|
* }
|
||||||
|
* }
|
||||||
|
* });
|
||||||
|
* ```
|
||||||
|
*
|
||||||
|
* @param {String} name Unique name.
|
||||||
|
* @param {Object} properties
|
||||||
|
* @param {Object} options (optional)
|
||||||
|
*
|
||||||
|
* @header loopback.createModel
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.createModel = function (name, properties, options) {
|
||||||
|
if (arguments.length === 1 && typeof name === 'object') {
|
||||||
|
var config = name;
|
||||||
|
name = config.name;
|
||||||
|
properties = config.properties;
|
||||||
|
options = buildModelOptionsFromConfig(config);
|
||||||
|
|
||||||
|
assert(typeof name === 'string',
|
||||||
|
'The model-config property `name` must be a string');
|
||||||
|
}
|
||||||
|
|
||||||
|
options = options || {};
|
||||||
|
var BaseModel = options.base || options.super;
|
||||||
|
|
||||||
|
if(typeof BaseModel === 'string') {
|
||||||
|
var baseName = BaseModel;
|
||||||
|
BaseModel = this.getModel(BaseModel);
|
||||||
|
|
||||||
|
if (BaseModel === undefined) {
|
||||||
|
if (baseName === 'DataModel') {
|
||||||
|
console.warn('Model `%s` is extending deprecated `DataModel. ' +
|
||||||
|
'Use `PeristedModel` instead.', name);
|
||||||
|
BaseModel = this.PersistedModel;
|
||||||
|
} else {
|
||||||
|
console.warn('Model `%s` is extending an unknown model `%s`. ' +
|
||||||
|
'Using `PersistedModel` as the base.', name, baseName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseModel = BaseModel || this.Model;
|
||||||
|
|
||||||
|
var model = BaseModel.extend(name, properties, options);
|
||||||
|
|
||||||
|
// try to attach
|
||||||
|
try {
|
||||||
|
this.autoAttachModel(model);
|
||||||
|
} catch(e) {}
|
||||||
|
|
||||||
|
return model;
|
||||||
|
};
|
||||||
|
|
||||||
|
function buildModelOptionsFromConfig(config) {
|
||||||
|
var options = extend({}, config.options);
|
||||||
|
for (var key in config) {
|
||||||
|
if (['name', 'properties', 'options'].indexOf(key) !== -1) {
|
||||||
|
// Skip items which have special meaning
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (options[key] !== undefined) {
|
||||||
|
// When both `config.key` and `config.options.key` are set,
|
||||||
|
// use the latter one
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
options[key] = config[key];
|
||||||
|
}
|
||||||
|
return options;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Alter an existing Model class.
|
||||||
|
* @param {Model} ModelCtor The model constructor to alter.
|
||||||
|
* @options {Object} config Additional configuration to apply
|
||||||
|
* @property {DataSource} dataSource Attach the model to a dataSource.
|
||||||
|
* @property {Object} [relations] Model relations to add/update.
|
||||||
|
*
|
||||||
|
* @header loopback.configureModel(ModelCtor, config)
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.configureModel = function(ModelCtor, config) {
|
||||||
|
var settings = ModelCtor.settings;
|
||||||
|
|
||||||
|
if (config.relations) {
|
||||||
|
var relations = settings.relations = settings.relations || {};
|
||||||
|
Object.keys(config.relations).forEach(function(key) {
|
||||||
|
relations[key] = extend(relations[key] || {}, config.relations[key]);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// It's important to attach the datasource after we have updated
|
||||||
|
// configuration, so that the datasource picks up updated relations
|
||||||
|
if (config.dataSource) {
|
||||||
|
assert(config.dataSource instanceof DataSource,
|
||||||
|
'Cannot configure ' + ModelCtor.modelName +
|
||||||
|
': config.dataSource must be an instance of DataSource');
|
||||||
|
ModelCtor.attachTo(config.dataSource);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Look up a model class by name from all models created by
|
||||||
|
* `loopback.createModel()`
|
||||||
|
* @param {String} modelName The model name
|
||||||
|
* @returns {Model} The model class
|
||||||
|
*
|
||||||
|
* @header loopback.getModel(modelName)
|
||||||
|
*/
|
||||||
|
registry.getModel = function(modelName) {
|
||||||
|
return this.Model.modelBuilder.models[modelName];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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.
|
||||||
|
* @param {Model} modelType The base model class
|
||||||
|
* @returns {Model} The subclass if found or the base class
|
||||||
|
*
|
||||||
|
* @header loopback.getModelByType(modelType)
|
||||||
|
*/
|
||||||
|
registry.getModelByType = function(modelType) {
|
||||||
|
assert(typeof modelType === 'function',
|
||||||
|
'The model type must be a constructor');
|
||||||
|
var models = this.Model.modelBuilder.models;
|
||||||
|
for(var m in models) {
|
||||||
|
if(models[m].prototype instanceof modelType) {
|
||||||
|
return models[m];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return modelType;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a data source with passing the provided options to the connector.
|
||||||
|
*
|
||||||
|
* @param {String} name Optional name.
|
||||||
|
* @options {Object} options Data Source options
|
||||||
|
* @property {Object} connector LoopBack connector.
|
||||||
|
* @property {*} [*] Other connector properties.
|
||||||
|
* See the relevant connector documentation.
|
||||||
|
*
|
||||||
|
* @header loopback.createDataSource(name, options)
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.createDataSource = function (name, options) {
|
||||||
|
var loopback = this;
|
||||||
|
var ds = new DataSource(name, options, loopback.Model.modelBuilder);
|
||||||
|
ds.createModel = function (name, properties, settings) {
|
||||||
|
var ModelCtor = loopback.createModel(name, properties, settings);
|
||||||
|
ModelCtor.attachTo(ds);
|
||||||
|
return ModelCtor;
|
||||||
|
};
|
||||||
|
|
||||||
|
if(ds.settings && ds.settings.defaultForType) {
|
||||||
|
this.setDefaultDataSourceForType(ds.settings.defaultForType, ds);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ds;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get an in-memory data source. Use one if it already exists.
|
||||||
|
*
|
||||||
|
* @param {String} [name] The name of the data source.
|
||||||
|
* If not provided, the `'default'` is used.
|
||||||
|
*
|
||||||
|
* @header loopback.memory()
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.memory = function (name) {
|
||||||
|
name = name || 'default';
|
||||||
|
var memory = (
|
||||||
|
this._memoryDataSources || (this._memoryDataSources = {})
|
||||||
|
)[name];
|
||||||
|
|
||||||
|
if(!memory) {
|
||||||
|
memory = this._memoryDataSources[name] = this.createDataSource({
|
||||||
|
connector: loopback.Memory
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return memory;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the default `dataSource` for a given `type`.
|
||||||
|
* @param {String} type The datasource type
|
||||||
|
* @param {Object|DataSource} dataSource The data source settings or instance
|
||||||
|
* @returns {DataSource} The data source instance
|
||||||
|
*
|
||||||
|
* @header loopback.setDefaultDataSourceForType(type, dataSource)
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.setDefaultDataSourceForType = function(type, dataSource) {
|
||||||
|
var defaultDataSources = this.defaultDataSources;
|
||||||
|
|
||||||
|
if(!(dataSource instanceof DataSource)) {
|
||||||
|
dataSource = this.createDataSource(dataSource);
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultDataSources[type] = dataSource;
|
||||||
|
return dataSource;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the default `dataSource` for a given `type`.
|
||||||
|
* @param {String} type The datasource type
|
||||||
|
* @returns {DataSource} The data source instance
|
||||||
|
* @header loopback.getDefaultDataSourceForType()
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.getDefaultDataSourceForType = function(type) {
|
||||||
|
return this.defaultDataSources && this.defaultDataSources[type];
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Attach any model that does not have a dataSource to
|
||||||
|
* the default dataSource for the type the Model requests
|
||||||
|
* @header loopback.autoAttach()
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.autoAttach = function() {
|
||||||
|
var models = this.Model.modelBuilder.models;
|
||||||
|
assert.equal(typeof models, 'object', 'Cannot autoAttach without a models object');
|
||||||
|
|
||||||
|
Object.keys(models).forEach(function(modelName) {
|
||||||
|
var ModelCtor = models[modelName];
|
||||||
|
|
||||||
|
// Only auto attach if the model doesn't have an explicit data source
|
||||||
|
if(ModelCtor && (!(ModelCtor.dataSource instanceof DataSource))) {
|
||||||
|
this.autoAttachModel(ModelCtor);
|
||||||
|
}
|
||||||
|
}, this);
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.autoAttachModel = function(ModelCtor) {
|
||||||
|
if(ModelCtor.autoAttach) {
|
||||||
|
var ds = this.getDefaultDataSourceForType(ModelCtor.autoAttach);
|
||||||
|
|
||||||
|
assert(ds instanceof DataSource, 'cannot autoAttach model "'
|
||||||
|
+ ModelCtor.modelName
|
||||||
|
+ '". No dataSource found of type ' + ModelCtor.autoAttach);
|
||||||
|
|
||||||
|
ModelCtor.attachTo(ds);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
registry.DataSource = DataSource;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Core models
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
registry.Model = require('./models/model');
|
||||||
|
registry.DataModel = require('./models/data-model');
|
|
@ -0,0 +1,22 @@
|
||||||
|
/*
|
||||||
|
* This is an internal file that should not be used outside of loopback.
|
||||||
|
* All exported entities can be accessed via the `loopback` object.
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
|
||||||
|
var runtime = exports;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if running in a browser environment; false otherwise.
|
||||||
|
* @header loopback.isBrowser
|
||||||
|
*/
|
||||||
|
|
||||||
|
runtime.isBrowser = typeof window !== 'undefined';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* True if running in a server environment; false otherwise.
|
||||||
|
* @header loopback.isServer
|
||||||
|
*/
|
||||||
|
|
||||||
|
runtime.isServer = !runtime.isBrowser;
|
||||||
|
|
30
package.json
30
package.json
|
@ -26,47 +26,47 @@
|
||||||
"mobile",
|
"mobile",
|
||||||
"mBaaS"
|
"mBaaS"
|
||||||
],
|
],
|
||||||
"version": "1.8.7",
|
"version": "1.9.0",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"test": "mocha -R spec"
|
"test": "mocha -R spec"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"debug": "~0.8.1",
|
"debug": "~1.0.2",
|
||||||
"express": "~3.5.0",
|
"express": "~3.5.0",
|
||||||
"strong-remoting": "~1.5.0",
|
"strong-remoting": "~1.5.0",
|
||||||
"inflection": "~1.3.5",
|
"inflection": "~1.3.7",
|
||||||
"nodemailer": "~0.6.5",
|
"nodemailer": "~0.7.0",
|
||||||
"ejs": "~1.0.0",
|
"ejs": "~1.0.0",
|
||||||
"bcryptjs": "~0.7.12",
|
"bcryptjs": "~1.0.3",
|
||||||
"underscore.string": "~2.3.3",
|
"underscore.string": "~2.3.3",
|
||||||
"underscore": "~1.6.0",
|
"underscore": "~1.6.0",
|
||||||
"uid2": "0.0.3",
|
"uid2": "0.0.3",
|
||||||
"async": "~0.9.0"
|
"async": "~0.9.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"loopback-datasource-juggler": ">=1.4.0 <1.6.0"
|
"loopback-datasource-juggler": ">=1.4.0 <1.7.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"loopback-datasource-juggler": ">=1.4.0 <1.6.0",
|
"loopback-datasource-juggler": ">=1.4.0 <1.7.0",
|
||||||
"mocha": "~1.18.0",
|
"mocha": "~1.20.1",
|
||||||
"strong-task-emitter": "0.0.x",
|
"strong-task-emitter": "0.0.x",
|
||||||
"supertest": "~0.12.1",
|
"supertest": "~0.13.0",
|
||||||
"chai": "~1.9.1",
|
"chai": "~1.9.1",
|
||||||
"loopback-testing": "~0.1.2",
|
"loopback-testing": "~0.2.0",
|
||||||
"browserify": "~4.1.5",
|
"browserify": "~4.1.11",
|
||||||
"grunt": "~0.4.5",
|
"grunt": "~0.4.5",
|
||||||
"grunt-browserify": "~2.1.0",
|
"grunt-browserify": "~2.1.0",
|
||||||
"grunt-contrib-uglify": "~0.4.0",
|
"grunt-contrib-uglify": "~0.5.0",
|
||||||
"grunt-contrib-jshint": "~0.10.0",
|
"grunt-contrib-jshint": "~0.10.0",
|
||||||
"grunt-contrib-watch": "~0.6.1",
|
"grunt-contrib-watch": "~0.6.1",
|
||||||
"karma-script-launcher": "~0.1.0",
|
"karma-script-launcher": "~0.1.0",
|
||||||
"karma-chrome-launcher": "~0.1.3",
|
"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-phantomjs-launcher": "~0.1.4",
|
"karma-phantomjs-launcher": "~0.1.4",
|
||||||
"karma": "~0.12.16",
|
"karma": "~0.12.16",
|
||||||
"karma-browserify": "~0.2.0",
|
"karma-browserify": "~0.2.1",
|
||||||
"karma-mocha": "~0.1.3",
|
"karma-mocha": "~0.1.4",
|
||||||
"grunt-karma": "~0.8.3"
|
"grunt-karma": "~0.8.3"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
|
|
@ -6,6 +6,7 @@ var app = require(path.join(ACCESS_CONTROL_APP, 'app.js'));
|
||||||
var assert = require('assert');
|
var assert = require('assert');
|
||||||
var USER = {email: 'test@test.test', password: 'test'};
|
var USER = {email: 'test@test.test', password: 'test'};
|
||||||
var CURRENT_USER = {email: 'current@test.test', password: 'test'};
|
var CURRENT_USER = {email: 'current@test.test', password: 'test'};
|
||||||
|
var debug = require('debug')('loopback:test:access-control.integration');
|
||||||
|
|
||||||
describe('access control - integration', function () {
|
describe('access control - integration', function () {
|
||||||
|
|
||||||
|
@ -69,8 +70,11 @@ describe('access control - integration', function () {
|
||||||
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForUser);
|
lt.it.shouldBeDeniedWhenCalledUnauthenticated('GET', urlForUser);
|
||||||
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER,'GET', urlForUser);
|
lt.it.shouldBeDeniedWhenCalledByUser(CURRENT_USER,'GET', urlForUser);
|
||||||
|
|
||||||
lt.it.shouldBeAllowedWhenCalledAnonymously('POST', '/api/users');
|
lt.it.shouldBeAllowedWhenCalledAnonymously(
|
||||||
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users');
|
'POST', '/api/users', newUserData());
|
||||||
|
|
||||||
|
lt.it.shouldBeAllowedWhenCalledByUser(
|
||||||
|
CURRENT_USER, 'POST', '/api/users', newUserData());
|
||||||
|
|
||||||
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout');
|
lt.it.shouldBeAllowedWhenCalledByUser(CURRENT_USER, 'POST', '/api/users/logout');
|
||||||
|
|
||||||
|
@ -96,6 +100,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() {
|
it('should not include a password', function() {
|
||||||
|
debug('GET /api/users/:id response: %s\nheaders: %j\nbody string: %s',
|
||||||
|
this.res.statusCode,
|
||||||
|
this.res.headers,
|
||||||
|
this.res.text);
|
||||||
var user = this.res.body;
|
var user = this.res.body;
|
||||||
assert.equal(user.password, undefined);
|
assert.equal(user.password, undefined);
|
||||||
});
|
});
|
||||||
|
@ -112,6 +120,15 @@ describe('access control - integration', function () {
|
||||||
function urlForUser() {
|
function urlForUser() {
|
||||||
return '/api/users/' + this.randomUser.id;
|
return '/api/users/' + this.randomUser.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var userCounter;
|
||||||
|
function newUserData() {
|
||||||
|
userCounter = userCounter ? ++userCounter : 1;
|
||||||
|
return {
|
||||||
|
email: 'new-' + userCounter + '@test.test',
|
||||||
|
password: 'test'
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('/banks', function () {
|
describe('/banks', function () {
|
||||||
|
|
|
@ -81,13 +81,38 @@ describe('app.enableAuth()', function() {
|
||||||
|
|
||||||
beforeEach(createTestingToken);
|
beforeEach(createTestingToken);
|
||||||
|
|
||||||
it('should prevent remote method calls if the accessToken doesnt have access', function (done) {
|
it('prevents remote call with 401 status on denied ACL', function (done) {
|
||||||
createTestAppAndRequest(this.token, done)
|
createTestAppAndRequest(this.token, done)
|
||||||
.del('/tests/123')
|
.del('/tests/123')
|
||||||
.expect(401)
|
.expect(401)
|
||||||
.set('authorization', this.token.id)
|
.set('authorization', this.token.id)
|
||||||
.end(done);
|
.end(done);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('prevent remote call with app setting status on denied ACL', function (done) {
|
||||||
|
createTestAppAndRequest(this.token, {app:{aclErrorStatus:403}}, done)
|
||||||
|
.del('/tests/123')
|
||||||
|
.expect(403)
|
||||||
|
.set('authorization', this.token.id)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevent remote call with app setting status on denied ACL', function (done) {
|
||||||
|
createTestAppAndRequest(this.token, {model:{aclErrorStatus:404}}, done)
|
||||||
|
.del('/tests/123')
|
||||||
|
.expect(404)
|
||||||
|
.set('authorization', this.token.id)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prevent remote call if the accessToken is missing and required', function (done) {
|
||||||
|
createTestAppAndRequest(null, done)
|
||||||
|
.del('/tests/123')
|
||||||
|
.expect(401)
|
||||||
|
.set('authorization', null)
|
||||||
|
.end(done);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
function createTestingToken(done) {
|
function createTestingToken(done) {
|
||||||
|
@ -99,12 +124,19 @@ function createTestingToken(done) {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTestAppAndRequest(testToken, done) {
|
function createTestAppAndRequest(testToken, settings, done) {
|
||||||
var app = createTestApp(testToken, done);
|
var app = createTestApp(testToken, settings, done);
|
||||||
return request(app);
|
return request(app);
|
||||||
}
|
}
|
||||||
|
|
||||||
function createTestApp(testToken, done) {
|
function createTestApp(testToken, settings, done) {
|
||||||
|
done = arguments[arguments.length-1];
|
||||||
|
if(settings == done) settings = {};
|
||||||
|
settings = settings || {};
|
||||||
|
|
||||||
|
var appSettings = settings.app || {};
|
||||||
|
var modelSettings = settings.model || {};
|
||||||
|
|
||||||
var app = loopback();
|
var app = loopback();
|
||||||
|
|
||||||
app.use(loopback.cookieParser('secret'));
|
app.use(loopback.cookieParser('secret'));
|
||||||
|
@ -125,7 +157,11 @@ function createTestApp(testToken, done) {
|
||||||
app.use(loopback.rest());
|
app.use(loopback.rest());
|
||||||
app.enableAuth();
|
app.enableAuth();
|
||||||
|
|
||||||
var TestModel = loopback.Model.extend('test', {}, {
|
Object.keys(appSettings).forEach(function(key){
|
||||||
|
app.set(key, appSettings[key]);
|
||||||
|
});
|
||||||
|
|
||||||
|
var modelOptions = {
|
||||||
acls: [
|
acls: [
|
||||||
{
|
{
|
||||||
principalType: "ROLE",
|
principalType: "ROLE",
|
||||||
|
@ -135,8 +171,14 @@ function createTestApp(testToken, done) {
|
||||||
property: 'removeById'
|
property: 'removeById'
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
Object.keys(modelSettings).forEach(function(key){
|
||||||
|
modelOptions[key] = modelSettings[key];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
var TestModel = loopback.Model.extend('test', {}, modelOptions);
|
||||||
|
|
||||||
TestModel.attachTo(loopback.memory());
|
TestModel.attachTo(loopback.memory());
|
||||||
app.model(TestModel);
|
app.model(TestModel);
|
||||||
|
|
||||||
|
|
|
@ -29,6 +29,15 @@ describe('app', function() {
|
||||||
expect(app.remotes().exports).to.eql({ color: Color });
|
expect(app.remotes().exports).to.eql({ color: Color });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('registers existing models to app.models', function() {
|
||||||
|
var Color = db.createModel('color', {name: String});
|
||||||
|
app.model(Color);
|
||||||
|
expect(Color.app).to.be.equal(app);
|
||||||
|
expect(Color.shared).to.equal(true);
|
||||||
|
expect(app.models.color).to.equal(Color);
|
||||||
|
expect(app.models.Color).to.equal(Color);
|
||||||
|
});
|
||||||
|
|
||||||
it('updates REST API when a new model is added', function(done) {
|
it('updates REST API when a new model is added', function(done) {
|
||||||
app.use(loopback.rest());
|
app.use(loopback.rest());
|
||||||
request(app).get('/colors').expect(404, function(err, res) {
|
request(app).get('/colors').expect(404, function(err, res) {
|
||||||
|
@ -107,6 +116,38 @@ describe('app', function() {
|
||||||
|
|
||||||
expect(app.models.foo.definition.settings.base).to.equal('Application');
|
expect(app.models.foo.definition.settings.base).to.equal('Application');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('honors config.public options', function() {
|
||||||
|
app.model('foo', {
|
||||||
|
dataSource: 'db',
|
||||||
|
public: false
|
||||||
|
});
|
||||||
|
expect(app.models.foo.app).to.equal(app);
|
||||||
|
expect(app.models.foo.shared).to.equal(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('defaults config.public to be true', function() {
|
||||||
|
app.model('foo', {
|
||||||
|
dataSource: 'db'
|
||||||
|
});
|
||||||
|
expect(app.models.foo.app).to.equal(app);
|
||||||
|
expect(app.models.foo.shared).to.equal(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('app.model(ModelCtor, config)', function() {
|
||||||
|
it('attaches the model to a datasource', function() {
|
||||||
|
app.dataSource('db', { connector: 'memory' });
|
||||||
|
var TestModel = loopback.Model.extend('TestModel');
|
||||||
|
// TestModel was most likely already defined in a different test,
|
||||||
|
// thus TestModel.dataSource may be already set
|
||||||
|
delete TestModel.dataSource;
|
||||||
|
|
||||||
|
app.model(TestModel, { dataSource: 'db' });
|
||||||
|
|
||||||
|
expect(app.models.TestModel.dataSource).to.equal(app.dataSources.db);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('app.models', function() {
|
describe('app.models', function() {
|
||||||
|
@ -544,4 +585,9 @@ describe('app', function() {
|
||||||
expect(app.connectors.FOOBAR).to.equal(loopback.Memory);
|
expect(app.connectors.FOOBAR).to.equal(loopback.Memory);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('exposes loopback as a property', function() {
|
||||||
|
var app = loopback();
|
||||||
|
expect(app.loopback).to.equal(loopback);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -23,7 +23,6 @@ describe('DataSource', function() {
|
||||||
assert.isFunc(Color, 'destroyAll');
|
assert.isFunc(Color, 'destroyAll');
|
||||||
assert.isFunc(Color, 'count');
|
assert.isFunc(Color, 'count');
|
||||||
assert.isFunc(Color, 'include');
|
assert.isFunc(Color, 'include');
|
||||||
assert.isFunc(Color, 'relationNameFor');
|
|
||||||
assert.isFunc(Color, 'hasMany');
|
assert.isFunc(Color, 'hasMany');
|
||||||
assert.isFunc(Color, 'belongsTo');
|
assert.isFunc(Color, 'belongsTo');
|
||||||
assert.isFunc(Color, 'hasAndBelongsToMany');
|
assert.isFunc(Color, 'hasAndBelongsToMany');
|
||||||
|
@ -53,7 +52,6 @@ describe('DataSource', function() {
|
||||||
existsAndShared('destroyAll', false);
|
existsAndShared('destroyAll', false);
|
||||||
existsAndShared('count', true);
|
existsAndShared('count', true);
|
||||||
existsAndShared('include', false);
|
existsAndShared('include', false);
|
||||||
existsAndShared('relationNameFor', false);
|
|
||||||
existsAndShared('hasMany', false);
|
existsAndShared('hasMany', false);
|
||||||
existsAndShared('belongsTo', false);
|
existsAndShared('belongsTo', false);
|
||||||
existsAndShared('hasAndBelongsToMany', false);
|
existsAndShared('hasAndBelongsToMany', false);
|
||||||
|
|
|
@ -1,4 +1,11 @@
|
||||||
describe('loopback', function() {
|
describe('loopback', function() {
|
||||||
|
var nameCounter = 0;
|
||||||
|
var uniqueModelName;
|
||||||
|
|
||||||
|
beforeEach(function() {
|
||||||
|
uniqueModelName = 'TestModel-' + (++nameCounter);
|
||||||
|
});
|
||||||
|
|
||||||
describe('exports', function() {
|
describe('exports', function() {
|
||||||
it('ValidationError', function() {
|
it('ValidationError', function() {
|
||||||
expect(loopback.ValidationError).to.be.a('function')
|
expect(loopback.ValidationError).to.be.a('function')
|
||||||
|
@ -119,4 +126,95 @@ describe('loopback', function() {
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('loopback.createModel(config)', function() {
|
||||||
|
it('creates the model', function() {
|
||||||
|
var model = loopback.createModel({
|
||||||
|
name: uniqueModelName
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(model.prototype).to.be.instanceof(loopback.Model);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('interprets extra first-level keys as options', function() {
|
||||||
|
var model = loopback.createModel({
|
||||||
|
name: uniqueModelName,
|
||||||
|
base: 'User'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(model.prototype).to.be.instanceof(loopback.User);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('prefers config.options.key over config.key', function() {
|
||||||
|
var model = loopback.createModel({
|
||||||
|
name: uniqueModelName,
|
||||||
|
base: 'User',
|
||||||
|
options: {
|
||||||
|
base: 'Application'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(model.prototype).to.be.instanceof(loopback.Application);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('loopback.configureModel(ModelCtor, config)', function() {
|
||||||
|
it('adds new relations', function() {
|
||||||
|
var model = loopback.Model.extend(uniqueModelName);
|
||||||
|
|
||||||
|
loopback.configureModel(model, {
|
||||||
|
relations: {
|
||||||
|
owner: {
|
||||||
|
type: 'belongsTo',
|
||||||
|
model: 'User'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(model.settings.relations).to.have.property('owner');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates existing relations', function() {
|
||||||
|
var model = loopback.Model.extend(uniqueModelName, {}, {
|
||||||
|
relations: {
|
||||||
|
owner: {
|
||||||
|
type: 'belongsTo',
|
||||||
|
model: 'User'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
loopback.configureModel(model, {
|
||||||
|
relations: {
|
||||||
|
owner: {
|
||||||
|
model: 'Application'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(model.settings.relations.owner).to.eql({
|
||||||
|
type: 'belongsTo',
|
||||||
|
model: 'Application'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('updates relations before attaching to a dataSource', function() {
|
||||||
|
var db = loopback.createDataSource({ connector: loopback.Memory });
|
||||||
|
var model = loopback.Model.extend(uniqueModelName);
|
||||||
|
|
||||||
|
loopback.configureModel(model, {
|
||||||
|
dataSource: db,
|
||||||
|
relations: {
|
||||||
|
owner: {
|
||||||
|
type: 'belongsTo',
|
||||||
|
model: 'User'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var owner = model.prototype.owner;
|
||||||
|
expect(owner, 'model.prototype.owner').to.be.a('function');
|
||||||
|
expect(owner._targetClass).to.equal('User');
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -5,6 +5,7 @@ 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;
|
var expect = require('chai').expect;
|
||||||
|
var debug = require('debug')('loopback:test:relations.integration');
|
||||||
|
|
||||||
describe('relations - integration', function () {
|
describe('relations - integration', function () {
|
||||||
|
|
||||||
|
@ -28,13 +29,19 @@ describe('relations - integration', function () {
|
||||||
this.url = '/api/stores/' + this.store.id + '/widgets';
|
this.url = '/api/stores/' + this.store.id + '/widgets';
|
||||||
});
|
});
|
||||||
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() {
|
lt.describe.whenCalledRemotely('GET', '/api/stores/:id/widgets', function() {
|
||||||
|
|
||||||
it('should succeed with statusCode 200', function() {
|
it('should succeed with statusCode 200', function() {
|
||||||
assert.equal(this.res.statusCode, 200);
|
assert.equal(this.res.statusCode, 200);
|
||||||
});
|
});
|
||||||
describe('widgets (response.body)', function() {
|
describe('widgets (response.body)', function() {
|
||||||
beforeEach(function() {
|
beforeEach(function() {
|
||||||
|
debug('GET /api/stores/:id/widgets response: %s' +
|
||||||
|
'\nheaders: %j\nbody string: %s',
|
||||||
|
this.res.statusCode,
|
||||||
|
this.res.headers,
|
||||||
|
this.res.text);
|
||||||
this.widgets = this.res.body;
|
this.widgets = this.res.body;
|
||||||
this.widget = this.res.body[0];
|
this.widget = this.res.body && this.res.body[0];
|
||||||
});
|
});
|
||||||
it('should be an array', function() {
|
it('should be an array', function() {
|
||||||
assert(Array.isArray(this.widgets));
|
assert(Array.isArray(this.widgets));
|
||||||
|
|
|
@ -77,6 +77,40 @@ describe('role model', function () {
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
it("should automatically generate role id", function () {
|
||||||
|
|
||||||
|
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {
|
||||||
|
// console.log('User: ', user.id);
|
||||||
|
Role.create({name: 'userRole'}, function (err, role) {
|
||||||
|
assert(role.id);
|
||||||
|
role.principals.create({principalType: RoleMapping.USER, principalId: user.id}, function (err, p) {
|
||||||
|
assert(p.id);
|
||||||
|
assert.equal(p.roleId, role.id);
|
||||||
|
Role.find(function (err, roles) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(roles.length, 1);
|
||||||
|
assert.equal(roles[0].name, 'userRole');
|
||||||
|
});
|
||||||
|
role.principals(function (err, principals) {
|
||||||
|
assert(!err);
|
||||||
|
// console.log(principals);
|
||||||
|
assert.equal(principals.length, 1);
|
||||||
|
assert.equal(principals[0].principalType, RoleMapping.USER);
|
||||||
|
assert.equal(principals[0].principalId, user.id);
|
||||||
|
});
|
||||||
|
role.users(function (err, users) {
|
||||||
|
assert(!err);
|
||||||
|
assert.equal(users.length, 1);
|
||||||
|
assert.equal(users[0].principalType, RoleMapping.USER);
|
||||||
|
assert.equal(users[0].principalId, user.id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
it("should support getRoles() and isInRole()", function () {
|
it("should support getRoles() and isInRole()", function () {
|
||||||
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {
|
User.create({name: 'Raymond', email: 'x@y.com', password: 'foobar'}, function (err, user) {
|
||||||
// console.log('User: ', user.id);
|
// console.log('User: ', user.id);
|
||||||
|
|
Loading…
Reference in New Issue